mirror of
https://github.com/btcsuite/btcd.git
synced 2026-08-13 12:32:51 +02:00
addrmgr: fix IsRoutable for IPv6 addresses starting with 0
In this commit, we fix `IsRoutable` to correctly reject IPv6 addresses whose first 16-bit group is zero (i.e., in the `0000::/16` reserved block per RFC 4291). Differential fuzzing between btcd and Bitcoin Core revealed that addresses like `0:9881:8181:8181:fe00🅰️9e:9801` were slipping through as routable when they shouldn't be. We add a new `zero6Net` (`0000::/16`) definition alongside the existing `zero4Net`, and introduce an `IsZero` helper that checks both. The /16 prefix width is intentional: the broader /8 reservation would incorrectly catch allocated sub-ranges like `0064:ff9b::/96` (RFC 6052, NAT64). We also carve out an exception for RFC 6145 translated IPv4 addresses (`::ffff:0:0:0/96`), which live within `0000::/16` but are valid for routing. Test coverage includes the original bug report address, various zero-prefix IPv6 addresses, the RFC 6145 exclusion, and corresponding `GroupKey` entries to confirm they land in the "unroutable" bucket. Fixes #2431
This commit is contained in:
parent
50f056f3c2
commit
4a4e2d2495
2 changed files with 125 additions and 2 deletions
|
|
@ -92,6 +92,13 @@ var (
|
|||
// (0.0.0.0/8).
|
||||
zero4Net = ipNet("0.0.0.0", 8, 32)
|
||||
|
||||
// zero6Net defines the IPv6 address block for addresses with a zero
|
||||
// first 16-bit group (0000::/16). These addresses are in the IANA
|
||||
// reserved range and should not be routable on the public internet.
|
||||
// We use /16 rather than the full /8 reservation to avoid catching
|
||||
// allocated sub-ranges like 0064:ff9b::/96 (RFC 6052).
|
||||
zero6Net = ipNet("::", 16, 128)
|
||||
|
||||
// heNet defines the Hurricane Electric IPv6 address block.
|
||||
heNet = ipNet("2001:470::", 32, 128)
|
||||
)
|
||||
|
|
@ -113,6 +120,29 @@ func IsLocal(na *wire.NetAddress) bool {
|
|||
return na.IP.IsLoopback() || zero4Net.Contains(na.IP)
|
||||
}
|
||||
|
||||
// IsZero returns whether or not the given address is in a reserved zero
|
||||
// address block. This includes IPv4 addresses starting with 0 (0.0.0.0/8)
|
||||
// and IPv6 addresses with a zero first group (0000::/16). Addresses in
|
||||
// the RFC 6145 sub-range (::ffff:0:0:0/96) are excluded since they are
|
||||
// valid translated IPv4 addresses used for routing.
|
||||
func IsZero(na *wire.NetAddress) bool {
|
||||
if zero4Net.Contains(na.IP) {
|
||||
return true
|
||||
}
|
||||
|
||||
if zero6Net.Contains(na.IP) {
|
||||
// Exclude RFC 6145 (::ffff:0:0:0/96) translated IPv4
|
||||
// addresses which are allocated within the zero block.
|
||||
if rfc6145Net.Contains(na.IP) {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// IsOnionCatTor returns whether or not the passed address is in the IPv6 range
|
||||
// used by bitcoin to support Tor (fd87:d87e:eb43::/48). Note that this range
|
||||
// is the same range used by OnionCat, which is part of the RFC4193 unique local
|
||||
|
|
@ -244,7 +274,8 @@ func IsRoutable(na *wire.NetAddressV2) bool {
|
|||
return IsValid(lna) && !(IsRFC1918(lna) || IsRFC2544(lna) ||
|
||||
IsRFC3927(lna) || IsRFC4862(lna) || IsRFC3849(lna) ||
|
||||
IsRFC4843(lna) || IsRFC7343(lna) || IsRFC5737(lna) ||
|
||||
IsRFC6598(lna) || IsLocal(lna) || (IsRFC4193(lna) &&
|
||||
IsRFC6598(lna) || IsLocal(lna) || IsZero(lna) ||
|
||||
(IsRFC4193(lna) &&
|
||||
!IsOnionCatTor(lna)))
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -79,7 +79,7 @@ func TestIPTypes(t *testing.T) {
|
|||
newIPTest("64:ff9b::1", false, false, false, false, false, false,
|
||||
false, false, false, false, true, false, false, false, false, true, true),
|
||||
newIPTest("::ffff:abcd:ef12:1", false, false, false, false, false, false,
|
||||
false, false, false, false, false, false, false, false, false, true, true),
|
||||
false, false, false, false, false, false, false, false, false, true, false),
|
||||
newIPTest("::1", false, false, false, false, false, false, false, false,
|
||||
false, false, false, false, false, false, true, true, false),
|
||||
newIPTest("198.18.0.1", false, true, false, false, false, false, false,
|
||||
|
|
@ -88,6 +88,27 @@ func TestIPTypes(t *testing.T) {
|
|||
false, false, false, false, false, true, false, false, true, false),
|
||||
newIPTest("203.0.113.1", false, false, false, false, false, false, false,
|
||||
false, false, false, false, false, false, false, false, true, false),
|
||||
|
||||
// IPv6 zero-prefix addresses (0000::/16 reserved range).
|
||||
// These should NOT be routable per RFC 4291.
|
||||
newIPTest("0:9881:8181:8181:fe00:a:9e:9801", false, false, false,
|
||||
false, false, false, false, false, false, false, false,
|
||||
false, false, false, false, true, false),
|
||||
newIPTest("0::1", false, false, false, false, false, false, false,
|
||||
false, false, false, false, false, false, false, true,
|
||||
true, false),
|
||||
newIPTest("0:1::1", false, false, false, false, false, false,
|
||||
false, false, false, false, false, false, false, false,
|
||||
false, true, false),
|
||||
newIPTest("0:ffff::1", false, false, false, false, false, false,
|
||||
false, false, false, false, false, false, false, false,
|
||||
false, true, false),
|
||||
|
||||
// RFC 6145 translated IPv4 addresses (::ffff:0:0:0/96) are
|
||||
// within 0000::/16 but should still be routable.
|
||||
newIPTest("::ffff:0:0c01:0203", false, false, false, false,
|
||||
false, false, false, false, false, false, false, true,
|
||||
false, false, false, true, true),
|
||||
}
|
||||
|
||||
t.Logf("Running %d tests", len(tests))
|
||||
|
|
@ -153,6 +174,75 @@ func TestIPTypes(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
// TestIsZero ensures the IsZero function correctly identifies addresses in the
|
||||
// reserved zero address blocks while excluding allocated sub-ranges.
|
||||
func TestIsZero(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
ip string
|
||||
want bool
|
||||
}{
|
||||
// IPv4 zero addresses (0.0.0.0/8).
|
||||
{name: "ipv4 zero", ip: "0.0.0.0", want: true},
|
||||
{name: "ipv4 zero prefix", ip: "0.1.2.3", want: true},
|
||||
{name: "ipv4 zero 0.255.255.255", ip: "0.255.255.255", want: true},
|
||||
|
||||
// IPv4 non-zero addresses.
|
||||
{name: "ipv4 normal", ip: "1.2.3.4", want: false},
|
||||
{name: "ipv4 loopback", ip: "127.0.0.1", want: false},
|
||||
{name: "ipv4 private", ip: "192.168.1.1", want: false},
|
||||
|
||||
// IPv6 zero-prefix addresses (0000::/16 reserved).
|
||||
{name: "ipv6 zero all", ip: "::", want: true},
|
||||
{name: "ipv6 loopback", ip: "::1", want: true},
|
||||
{name: "ipv6 zero prefix 0::2", ip: "0::2", want: true},
|
||||
{name: "ipv6 zero prefix 0:1::1", ip: "0:1::1", want: true},
|
||||
{name: "ipv6 zero prefix 0:ffff::1", ip: "0:ffff::1", want: true},
|
||||
{
|
||||
name: "ipv6 zero prefix from bug report",
|
||||
ip: "0:9881:8181:8181:fe00:a:9e:9801",
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "ipv6 zero prefix unallocated",
|
||||
ip: "::ffff:abcd:ef12:1",
|
||||
want: true,
|
||||
},
|
||||
|
||||
// RFC 6145 addresses (::ffff:0:0:0/96) — allocated sub-range
|
||||
// of 0000::/16 that should NOT be flagged as zero.
|
||||
{
|
||||
name: "ipv6 rfc6145 translated ipv4",
|
||||
ip: "::ffff:0:0c01:0203",
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "ipv6 rfc6145 another",
|
||||
ip: "::ffff:0:1.2.3.4",
|
||||
want: false,
|
||||
},
|
||||
|
||||
// IPv6 non-zero-prefix addresses.
|
||||
{name: "ipv6 normal", ip: "2602:100::1", want: false},
|
||||
{name: "ipv6 rfc6052", ip: "64:ff9b::1", want: false},
|
||||
{name: "ipv6 private", ip: "fd00:dead::1", want: false},
|
||||
{name: "ipv6 teredo", ip: "2001::1", want: false},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
nip := net.ParseIP(test.ip)
|
||||
na := wire.NewNetAddressIPPort(
|
||||
nip, 8333, wire.SFNodeNetwork,
|
||||
)
|
||||
if got := addrmgr.IsZero(na); got != test.want {
|
||||
t.Errorf("IsZero(%s) = %v, want %v",
|
||||
test.ip, got, test.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestGroupKey tests the GroupKey function to ensure it properly groups various
|
||||
// IP addresses.
|
||||
func TestGroupKey(t *testing.T) {
|
||||
|
|
@ -178,6 +268,8 @@ func TestGroupKey(t *testing.T) {
|
|||
{name: "ipv6 rfc4843 2001:10::/28", ip: "2001:10::1234", expected: "unroutable"},
|
||||
{name: "ipv6 rfc7343 2001:20::/28", ip: "2001:20::1234", expected: "unroutable"},
|
||||
{name: "ipv6 rfc4862 fe80::/64", ip: "fe80::1234", expected: "unroutable"},
|
||||
{name: "ipv6 zero prefix 0::/16", ip: "0:9881:8181:8181:fe00:a:9e:9801", expected: "unroutable"},
|
||||
{name: "ipv6 zero prefix 0:1::1", ip: "0:1::1", expected: "unroutable"},
|
||||
|
||||
// IPv4 normal.
|
||||
{name: "ipv4 normal class a", ip: "12.1.2.3", expected: "12.1.0.0"},
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue