package manet import ( "net" "strings" ma "github.com/multiformats/go-multiaddr" ) // Private4 and Private6 are well-known private networks var Private4, Private6 []*net.IPNet var privateCIDR4 = []string{ // localhost "127.0.0.0/8", // private networks "10.0.0.0/8", "100.64.0.0/10", "172.16.0.0/12", "192.168.0.0/16", // link local "169.254.0.0/16", } var privateCIDR6 = []string{ // localhost "::1/128", // ULA reserved "fc00::/7", // link local "fe80::/10", } // Unroutable4 and Unroutable6 are well known unroutable address ranges var Unroutable4, Unroutable6 []*net.IPNet var unroutableCIDR4 = []string{ "0.0.0.0/8", "192.0.0.0/26", "192.0.2.0/24", "192.88.99.0/24", "198.18.0.0/15", "198.51.100.0/24", "203.0.113.0/24", "224.0.0.0/4", "240.0.0.0/4", "255.255.255.255/32", } 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: No central authority for .test names ".test", } // RFC 6761: Users may assume that IPv4 and IPv6 address queries for localhost names will // always resolve to the respective IP loopback address const localHostDomain = ".localhost" func init() { Private4 = parseCIDR(privateCIDR4) Private6 = parseCIDR(privateCIDR6) Unroutable4 = parseCIDR(unroutableCIDR4) Unroutable6 = parseCIDR(unroutableCIDR6) } func parseCIDR(cidrs []string) []*net.IPNet { ipnets := make([]*net.IPNet, len(cidrs)) for i, cidr := range cidrs { _, ipnet, err := net.ParseCIDR(cidr) if err != nil { panic(err) } ipnets[i] = ipnet } return ipnets } // 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 { switch c.Protocol().Code { case ma.P_IP6ZONE: return true case ma.P_IP4: ip := net.IP(c.RawValue()) isPublic = !inAddrRange(ip, Private4) && !inAddrRange(ip, Unroutable4) 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 if isSubdomain(dnsAddr, localHostDomain) { isPublic = false return false } for _, ud := range unResolvableDomains { if isSubdomain(dnsAddr, ud) { isPublic = false return false } } for _, pd := range privateUseDomains { if isSubdomain(dnsAddr, pd) { isPublic = false break } } } return false }) return isPublic } // isSubdomain checks if child is sub domain of parent. It also returns true if child and parent are // the same domain. // Parent must have a "." prefix. func isSubdomain(child, parent string) bool { return strings.HasSuffix(child, parent) || child == parent[1:] } // IsPrivateAddr returns true if the IP part of the mutiaddr is in a private network func IsPrivateAddr(a ma.Multiaddr) bool { isPrivate := false ma.ForEach(a, func(c ma.Component) bool { switch c.Protocol().Code { case ma.P_IP6ZONE: return true case ma.P_IP4: isPrivate = inAddrRange(net.IP(c.RawValue()), Private4) case ma.P_IP6: isPrivate = inAddrRange(net.IP(c.RawValue()), Private6) case ma.P_DNS, ma.P_DNS4, ma.P_DNS6, ma.P_DNSADDR: dnsAddr := c.Value() if isSubdomain(dnsAddr, localHostDomain) { isPrivate = true } // We don't check for privateUseDomains because private use domains can // resolve to public IP addresses } return false }) return isPrivate } func inAddrRange(ip net.IP, ipnets []*net.IPNet) bool { for _, ipnet := range ipnets { if ipnet.Contains(ip) { return true } } return false }