Skip to content

Commit

Permalink
net: restrict unicast ip6 public address space (#235)
Browse files Browse the repository at this point in the history
Global Unicast IPv6 addresses only belong to prefix 2000::/3. This
change also classifies NAT64 prefixes as Public Addresses as they
may reference public IPv4 addresses.
  • Loading branch information
sukunrt authored May 1, 2024
1 parent cece70d commit 4ef8656
Show file tree
Hide file tree
Showing 4 changed files with 54 additions and 13 deletions.
12 changes: 1 addition & 11 deletions net/ip.go
Original file line number Diff line number Diff line change
Expand Up @@ -123,20 +123,10 @@ func zoneless(m ma.Multiaddr) ma.Multiaddr {
}
}

var nat64WellKnownPrefix net.IPNet

func init() {
_, np, err := net.ParseCIDR("64:ff9b::/96")
if err != nil {
panic(err)
}
nat64WellKnownPrefix = *np
}

// IsNAT64IPv4ConvertedIPv6Addr returns whether addr is a well-known prefix "64:ff9b::/96" addr
// used for NAT64 Translation. See RFC 6052
func IsNAT64IPv4ConvertedIPv6Addr(addr ma.Multiaddr) bool {
c, _ := ma.SplitFirst(addr)
return c != nil && c.Protocol().Code == ma.P_IP6 &&
nat64WellKnownPrefix.Contains(net.IP(c.RawValue()))
inAddrRange(c.RawValue(), nat64)
}
5 changes: 5 additions & 0 deletions net/ip_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,11 @@ func TestIsWellKnownPrefixIPv4ConvertedIPv6Address(t *testing.T) {
want: false,
failureReason: "64:ff9b::1 is not well-known prefix",
},
{
addr: ma.StringCast("/ip6/64:ff9b:1::1:192.0.1.2/tcp/1234"),
want: true,
failureReason: "64:ff9b:1::1 is allowed for NAT64 translation",
},
}
for i, tc := range cases {
t.Run(fmt.Sprintf("%d", i), func(t *testing.T) {
Expand Down
35 changes: 33 additions & 2 deletions net/private.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,9 +44,22 @@ var unroutableCIDR4 = []string{
"255.255.255.255/32",
}
var unroutableCIDR6 = []string{
"ff00::/8",
"ff00::/8", // multicast
"2001:db8::/32", // documentation
}

var globalUnicast []*net.IPNet
var globalUnicastCIDR6 = []string{
"2000::/3",
}

var nat64CIDRs = []string{
"64:ff9b:1::/48", // RFC 8215
"64:ff9b::/96", // RFC 6052
}

var nat64 []*net.IPNet

// unResolvableDomains do not resolve to an IP address.
// Ref: https://en.wikipedia.org/wiki/Special-use_domain_name#Reserved_domain_names
var unResolvableDomains = []string{
Expand Down Expand Up @@ -82,6 +95,8 @@ func init() {
Private6 = parseCIDR(privateCIDR6)
Unroutable4 = parseCIDR(unroutableCIDR4)
Unroutable6 = parseCIDR(unroutableCIDR6)
globalUnicast = parseCIDR(globalUnicastCIDR6)
nat64 = parseCIDR(nat64CIDRs)
}

func parseCIDR(cidrs []string) []*net.IPNet {
Expand Down Expand Up @@ -109,7 +124,23 @@ func IsPublicAddr(a ma.Multiaddr) bool {
isPublic = !inAddrRange(ip, Private4) && !inAddrRange(ip, Unroutable4)
case ma.P_IP6:
ip := net.IP(c.RawValue())
isPublic = !inAddrRange(ip, Private6) && !inAddrRange(ip, Unroutable6)
// IP6 documentation prefix(part of Unroutable6) is a subset of the ip6
// global unicast allocation so we ensure that it's not a documentation
// prefix by diffing with Unroutable6
isPublicUnicastAddr := inAddrRange(ip, globalUnicast) && !inAddrRange(ip, Unroutable6)
if isPublicUnicastAddr {
isPublic = true
return false
}
// The WellKnown NAT64 prefix(RFC 6052) can only reference a public IPv4
// address.
// The Local use NAT64 prefix(RFC 8215) can reference private IPv4
// addresses. But since the translation from Local use NAT64 prefix to IPv4
// address is left to the user we have no way of knowing which IPv4 address
// is referenced. We count these as Public addresses because a false
// negative for this method here is generally worse than a false positive.
isPublic = inAddrRange(ip, nat64)
return false
case ma.P_DNS, ma.P_DNS4, ma.P_DNS6, ma.P_DNSADDR:
dnsAddr := c.Value()
isPublic = true
Expand Down
15 changes: 15 additions & 0 deletions net/private_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,21 @@ func TestIsPublicAddr(t *testing.T) {
isPublic: false,
isPrivate: true,
},
{
addr: ma.StringCast("/ip6/2400::1/tcp/10"),
isPublic: true,
isPrivate: false,
},
{
addr: ma.StringCast("/ip6/2001:db8::42/tcp/10"),
isPublic: false,
isPrivate: false,
},
{
addr: ma.StringCast("/ip6/64:ff9b::1.1.1.1/tcp/10"),
isPublic: true,
isPrivate: false,
},
}
for i, tt := range tests {
t.Run(fmt.Sprintf("%d", i), func(t *testing.T) {
Expand Down

0 comments on commit 4ef8656

Please sign in to comment.