Skip to content

Commit

Permalink
refactor private address detection and unify approach for ipv4 and ipv6.
Browse files Browse the repository at this point in the history
Fixes #2825
  • Loading branch information
magiconair committed Sep 25, 2017
1 parent a2b4176 commit f499265
Show file tree
Hide file tree
Showing 2 changed files with 79 additions and 34 deletions.
65 changes: 31 additions & 34 deletions ipaddr/detect.go
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -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)
Expand All @@ -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
Expand All @@ -85,18 +57,43 @@ 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)
}
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
}
Expand Down
48 changes: 48 additions & 0 deletions ipaddr/detect_test.go
Original file line number Diff line number Diff line change
@@ -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)
}
})
}
}

0 comments on commit f499265

Please sign in to comment.