diff --git a/net/private.go b/net/private.go index ba57f00..c0b6acb 100644 --- a/net/private.go +++ b/net/private.go @@ -2,6 +2,7 @@ package manet import ( "net" + "strings" ma "github.com/multiformats/go-multiaddr" ) @@ -46,6 +47,35 @@ var unroutableCIDR6 = []string{ "ff00::/8", } +// unResolvableDomains do not resolve to an IP address. +// Ref: https://en.wikipedia.org/wiki/Special-use_domain_name#Reserved_domain_names +var unResolvableDomains = []string{ + // Reverse DNS Lookup + ".in-addr.arpa", + ".ip6.arpa", + + // RFC 6761: Users MAY assume that queries for "invalid" names will always return NXDOMAIN + // responses + ".invalid", +} + +// privateUseDomains are reserved for private use and have no central authority for consistent +// address resolution +// Ref: https://en.wikipedia.org/wiki/Special-use_domain_name#Reserved_domain_names +var privateUseDomains = []string{ + // RFC 8375: Reserved for home networks + ".home.arpa", + + // MDNS + ".local", + + // RFC 6761: Users may assume that IPv4 and IPv6 address queries for localhost names will + // always resolve to the respective IP loopback address + ".localhost", + // RFC 6761: No central authority for .test names + ".test", +} + func init() { Private4 = parseCIDR(privateCIDR4) Private6 = parseCIDR(privateCIDR6) @@ -65,7 +95,8 @@ func parseCIDR(cidrs []string) []*net.IPNet { return ipnets } -// IsPublicAddr retruns true if the IP part of the multiaddr is a publicly routable address +// IsPublicAddr returns true if the IP part of the multiaddr is a publicly routable address +// or if it's a dns address without a special use domain e.g. .local. func IsPublicAddr(a ma.Multiaddr) bool { isPublic := false ma.ForEach(a, func(c ma.Component) bool { @@ -78,6 +109,21 @@ func IsPublicAddr(a ma.Multiaddr) bool { case ma.P_IP6: ip := net.IP(c.RawValue()) isPublic = !inAddrRange(ip, Private6) && !inAddrRange(ip, Unroutable6) + case ma.P_DNS, ma.P_DNS4, ma.P_DNS6, ma.P_DNSADDR: + dnsAddr := c.Value() + isPublic = true + for _, ud := range unResolvableDomains { + if strings.HasSuffix(dnsAddr, ud) { + isPublic = false + return false + } + } + for _, pd := range privateUseDomains { + if strings.HasSuffix(dnsAddr, pd) { + isPublic = false + break + } + } } return false }) diff --git a/net/private_test.go b/net/private_test.go index a4380a5..04e1ddb 100644 --- a/net/private_test.go +++ b/net/private_test.go @@ -1,48 +1,59 @@ package manet import ( + "fmt" "testing" ma "github.com/multiformats/go-multiaddr" ) func TestIsPublicAddr(t *testing.T) { - a, err := ma.NewMultiaddr("/ip4/192.168.1.1/tcp/80") - if err != nil { - t.Fatal(err) - } - - if IsPublicAddr(a) { - t.Fatal("192.168.1.1 is not a public address!") - } - - if !IsPrivateAddr(a) { - t.Fatal("192.168.1.1 is a private address!") - } - - a, err = ma.NewMultiaddr("/ip4/1.1.1.1/tcp/80") - if err != nil { - t.Fatal(err) - } - - if !IsPublicAddr(a) { - t.Fatal("1.1.1.1 is a public address!") - } - - if IsPrivateAddr(a) { - t.Fatal("1.1.1.1 is not a private address!") - } - - a, err = ma.NewMultiaddr("/tcp/80/ip4/1.1.1.1") - if err != nil { - t.Fatal(err) - } - - if IsPublicAddr(a) { - t.Fatal("shouldn't consider an address that starts with /tcp/ as *public*") - } - - if IsPrivateAddr(a) { - t.Fatal("shouldn't consider an address that starts with /tcp/ as *private*") + tests := []struct { + addr ma.Multiaddr + isPublic bool + isPrivate bool + }{ + { + addr: ma.StringCast("/ip4/192.168.1.1/tcp/80"), + isPublic: false, + isPrivate: true, + }, + { + addr: ma.StringCast("/ip4/1.1.1.1/tcp/80"), + isPublic: true, + isPrivate: false, + }, + { + addr: ma.StringCast("/tcp/80/ip4/1.1.1.1"), + isPublic: false, + isPrivate: false, + }, + { + addr: ma.StringCast("/dns/node.libp2p.io/udp/1/quic-v1"), + isPublic: true, + isPrivate: false, + }, + { + addr: ma.StringCast("/dnsaddr/node.libp2p.io/udp/1/quic-v1"), + isPublic: true, + isPrivate: false, + }, + { + addr: ma.StringCast("/dns/node.libp2p.local/udp/1/quic-v1"), + isPublic: false, + isPrivate: false, // You can configure .local domains in local networks to return public addrs + }, + } + for i, tt := range tests { + t.Run(fmt.Sprintf("%d", i), func(t *testing.T) { + isPublic := IsPublicAddr(tt.addr) + isPrivate := IsPrivateAddr(tt.addr) + if isPublic != tt.isPublic { + t.Errorf("IsPublicAddr check failed for %s: expected %t, got %t", tt.addr, tt.isPublic, isPublic) + } + if isPrivate != tt.isPrivate { + t.Errorf("IsPrivateAddr check failed for %s: expected %t, got %t", tt.addr, tt.isPrivate, isPrivate) + } + }) } }