diff --git a/private.go b/private.go new file mode 100644 index 0000000..26e547c --- /dev/null +++ b/private.go @@ -0,0 +1,116 @@ +package manet + +import ( + "net" + + 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", +} + +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 retruns true if the IP part of the multiaddr is a publicly routable address +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 + default: + return false + 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) + } + return false + }) + return isPublic +} + +// 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 + default: + return false + case ma.P_IP4: + isPrivate = inAddrRange(net.IP(c.RawValue()), Private4) + case ma.P_IP6: + isPrivate = inAddrRange(net.IP(c.RawValue()), Private6) + } + 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 +} diff --git a/private_test.go b/private_test.go new file mode 100644 index 0000000..a4380a5 --- /dev/null +++ b/private_test.go @@ -0,0 +1,48 @@ +package manet + +import ( + "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*") + } +}