diff --git a/ipaddr/detect.go b/ipaddr/detect.go index 84879542fabc..6cf6de45d86e 100644 --- a/ipaddr/detect.go +++ b/ipaddr/detect.go @@ -5,30 +5,6 @@ import ( "net" ) -// privateIPv4CIDRs contains the IPv4 address blocks which -// are used for private networks. -var privateIPv4CIDRs = []string{ - "10.0.0.0/8", - "100.64.0.0/10", - "127.0.0.0/8", - "169.254.0.0/16", - "172.16.0.0/12", - "192.168.0.0/16", -} - -// privateIPv4Blocks contains the parsed privateCIDRs -var privateIPv4Blocks []*net.IPNet - -func init() { - for _, cidr := range privateIPv4CIDRs { - _, block, err := net.ParseCIDR(cidr) - if err != nil { - panic(fmt.Sprintf("Bad cidr %s. Got %v", cidr, err)) - } - privateIPv4Blocks = append(privateIPv4Blocks, block) - } -} - // GetPrivateIPv4 returns the list of private network IPv4 addresses on // all active interfaces. func GetPrivateIPv4() ([]net.IP, error) { @@ -51,7 +27,7 @@ func GetPrivateIPv4() ([]net.IP, error) { if ip.To4() == nil { continue } - if !isPrivateIPv4(ip.String()) { + if !isPrivate(ip) { continue } addrs = append(addrs, ip) @@ -67,10 +43,6 @@ func GetPublicIPv6() ([]net.IP, error) { return nil, fmt.Errorf("Failed to get interface addresses: %v", err) } - isUniqueLocalAddress := func(ip net.IP) bool { - return len(ip) == net.IPv6len && ip[0] == 0xfc && ip[1] == 0x00 - } - var addrs []net.IP for _, rawAddr := range addresses { var ip net.IP @@ -85,7 +57,7 @@ func GetPublicIPv6() ([]net.IP, error) { if ip.To4() != nil { continue } - if ip.IsLinkLocalUnicast() || isUniqueLocalAddress(ip) || ip.IsLoopback() { + if isPrivate(ip) { continue } addrs = append(addrs, ip) @@ -93,10 +65,35 @@ func GetPublicIPv6() ([]net.IP, error) { return addrs, nil } -// Returns if the given IP is in a private block -func isPrivateIPv4(ip_str string) bool { - ip := net.ParseIP(ip_str) - for _, priv := range privateIPv4Blocks { +// privateBlocks contains non-forwardable address blocks which are used +// for private networks. RFC 6890 provides an overview of special +// address blocks. +var privateBlocks = []*net.IPNet{ + parseCIDR("10.0.0.0/8"), // RFC 1918 IPv4 private network address + parseCIDR("100.64.0.0/10"), // RFC 6598 IPv4 shared address space + parseCIDR("127.0.0.0/8"), // RFC 1122 IPv4 loopback address + parseCIDR("169.254.0.0/16"), // RFC 3927 IPv4 link local address + parseCIDR("172.16.0.0/12"), // RFC 1918 IPv4 private network address + parseCIDR("192.0.0.0/24"), // RFC 6890 IPv4 IANA address + parseCIDR("192.0.2.0/24"), // RFC 5737 IPv4 documentation address + parseCIDR("192.168.0.0/16"), // RFC 1918 IPv4 private network address + parseCIDR("::1/128"), // RFC 1884 IPv6 loopback address + parseCIDR("fe80::/10"), // RFC 4291 IPv6 link local addresses + parseCIDR("fc00::/7"), // RFC 4193 IPv6 unique local addresses + parseCIDR("fec0::/10"), // RFC 1884 IPv6 site-local addresses + parseCIDR("2001:db8::/32"), // RFC 3849 IPv6 documentation address +} + +func parseCIDR(s string) *net.IPNet { + _, block, err := net.ParseCIDR(s) + if err != nil { + panic(fmt.Sprintf("Bad CIDR %s: %s", s, err)) + } + return block +} + +func isPrivate(ip net.IP) bool { + for _, priv := range privateBlocks { if priv.Contains(ip) { return true } diff --git a/ipaddr/detect_test.go b/ipaddr/detect_test.go new file mode 100644 index 000000000000..91eea1b9f664 --- /dev/null +++ b/ipaddr/detect_test.go @@ -0,0 +1,48 @@ +package ipaddr + +import ( + "net" + "testing" +) + +func TestIsPrivateIP(t *testing.T) { + tests := []struct { + ip string + private bool + }{ + // IPv4 private addresses + {"10.0.0.1", true}, // private network address + {"100.64.0.1", true}, // shared address space + {"172.16.0.1", true}, // private network address + {"192.168.0.1", true}, // private network address + {"192.0.0.1", true}, // IANA address + {"192.0.2.1", true}, // documentation address + {"127.0.0.1", true}, // loopback address + {"169.254.0.1", true}, // link local address + + // IPv4 public addresses + {"1.2.3.4", false}, + + // IPv6 private addresses + {"::1", true}, // loopback address + {"fe80::1", true}, // link local address + {"fc00::1", true}, // unique local address + {"fec0::1", true}, // site local address + {"2001:db8::1", true}, // documentation address + + // IPv6 public addresses + {"2004:db6::1", false}, + } + + for _, tt := range tests { + t.Run(tt.ip, func(t *testing.T) { + ip := net.ParseIP(tt.ip) + if ip == nil { + t.Fatalf("%s is not a valid ip address", tt.ip) + } + if got, want := isPrivate(ip), tt.private; got != want { + t.Fatalf("got %v for %v want %v", got, ip, want) + } + }) + } +}