diff --git a/CHANGELOG_PENDING.md b/CHANGELOG_PENDING.md index e93d77d19..c4f2b7a79 100644 --- a/CHANGELOG_PENDING.md +++ b/CHANGELOG_PENDING.md @@ -83,3 +83,4 @@ Friendly reminder, we have a [bug bounty program](https://hackerone.com/tendermi - [rpc] \#4805 Attempt to handle panics during panic recovery (@erikgrinaker) - [types] [\#4764](https://github.com/tendermint/tendermint/pull/4764) Return an error if voting power overflows in `VerifyCommitTrusting` (@melekes) - [privval] [\#4812](https://github.com/tendermint/tendermint/pull/4812) Retry `GetPubKey/SignVote/SignProposal` a few times before returning an error (@melekes) +- [p2p] [\#4847](https://github.com/tendermint/tendermint/pull/4847) Return masked IP (not the actual IP) in addrbook#groupKey (@melekes) diff --git a/p2p/netaddress.go b/p2p/netaddress.go index 056e59069..299696056 100644 --- a/p2p/netaddress.go +++ b/p2p/netaddress.go @@ -312,21 +312,43 @@ var rfc4862 = net.IPNet{IP: net.ParseIP("FE80::"), Mask: net.CIDRMask(64, 128)} var rfc6052 = net.IPNet{IP: net.ParseIP("64:FF9B::"), Mask: net.CIDRMask(96, 128)} var rfc6145 = net.IPNet{IP: net.ParseIP("::FFFF:0:0:0"), Mask: net.CIDRMask(96, 128)} var zero4 = net.IPNet{IP: net.ParseIP("0.0.0.0"), Mask: net.CIDRMask(8, 32)} +var ( + // onionCatNet defines the IPv6 address block used to support Tor. + // bitcoind encodes a .onion address as a 16 byte number by decoding the + // address prior to the .onion (i.e. the key hash) base32 into a ten + // byte number. It then stores the first 6 bytes of the address as + // 0xfd, 0x87, 0xd8, 0x7e, 0xeb, 0x43. + // + // This is the same range used by OnionCat, which is part part of the + // RFC4193 unique local IPv6 range. + // + // In summary the format is: + // { magic 6 bytes, 10 bytes base32 decode of key hash } + onionCatNet = ipNet("fd87:d87e:eb43::", 48, 128) +) + +// ipNet returns a net.IPNet struct given the passed IP address string, number +// of one bits to include at the start of the mask, and the total number of bits +// for the mask. +func ipNet(ip string, ones, bits int) net.IPNet { + return net.IPNet{IP: net.ParseIP(ip), Mask: net.CIDRMask(ones, bits)} +} func (na *NetAddress) RFC1918() bool { return rfc1918_10.Contains(na.IP) || rfc1918_192.Contains(na.IP) || rfc1918_172.Contains(na.IP) } -func (na *NetAddress) RFC3849() bool { return rfc3849.Contains(na.IP) } -func (na *NetAddress) RFC3927() bool { return rfc3927.Contains(na.IP) } -func (na *NetAddress) RFC3964() bool { return rfc3964.Contains(na.IP) } -func (na *NetAddress) RFC4193() bool { return rfc4193.Contains(na.IP) } -func (na *NetAddress) RFC4380() bool { return rfc4380.Contains(na.IP) } -func (na *NetAddress) RFC4843() bool { return rfc4843.Contains(na.IP) } -func (na *NetAddress) RFC4862() bool { return rfc4862.Contains(na.IP) } -func (na *NetAddress) RFC6052() bool { return rfc6052.Contains(na.IP) } -func (na *NetAddress) RFC6145() bool { return rfc6145.Contains(na.IP) } +func (na *NetAddress) RFC3849() bool { return rfc3849.Contains(na.IP) } +func (na *NetAddress) RFC3927() bool { return rfc3927.Contains(na.IP) } +func (na *NetAddress) RFC3964() bool { return rfc3964.Contains(na.IP) } +func (na *NetAddress) RFC4193() bool { return rfc4193.Contains(na.IP) } +func (na *NetAddress) RFC4380() bool { return rfc4380.Contains(na.IP) } +func (na *NetAddress) RFC4843() bool { return rfc4843.Contains(na.IP) } +func (na *NetAddress) RFC4862() bool { return rfc4862.Contains(na.IP) } +func (na *NetAddress) RFC6052() bool { return rfc6052.Contains(na.IP) } +func (na *NetAddress) RFC6145() bool { return rfc6145.Contains(na.IP) } +func (na *NetAddress) OnionCatTor() bool { return onionCatNet.Contains(na.IP) } func removeProtocolIfDefined(addr string) string { if strings.Contains(addr, "://") { diff --git a/p2p/pex/addrbook.go b/p2p/pex/addrbook.go index 670de9167..a5a408f10 100644 --- a/p2p/pex/addrbook.go +++ b/p2p/pex/addrbook.go @@ -870,31 +870,36 @@ func (a *addrBook) calcOldBucket(addr *p2p.NetAddress) (int, error) { } // Return a string representing the network group of this address. -// This is the /16 for IPv4, the /32 (/36 for he.net) for IPv6, the string +// This is the /16 for IPv4 (e.g. 1.2.0.0), the /32 (/36 for he.net) for IPv6, the string // "local" for a local address and the string "unroutable" for an unroutable // address. func (a *addrBook) groupKey(na *p2p.NetAddress) string { - if a.routabilityStrict && na.Local() { + return groupKeyFor(na, a.routabilityStrict) +} + +func groupKeyFor(na *p2p.NetAddress, routabilityStrict bool) string { + if routabilityStrict && na.Local() { return "local" } - if a.routabilityStrict && !na.Routable() { + if routabilityStrict && !na.Routable() { return "unroutable" } if ipv4 := na.IP.To4(); ipv4 != nil { - return (&net.IPNet{IP: na.IP, Mask: net.CIDRMask(16, 32)}).String() + return na.IP.Mask(net.CIDRMask(16, 32)).String() } + if na.RFC6145() || na.RFC6052() { // last four bytes are the ip address ip := na.IP[12:16] - return (&net.IPNet{IP: ip, Mask: net.CIDRMask(16, 32)}).String() + return ip.Mask(net.CIDRMask(16, 32)).String() } if na.RFC3964() { - ip := na.IP[2:7] - return (&net.IPNet{IP: ip, Mask: net.CIDRMask(16, 32)}).String() - + ip := na.IP[2:6] + return ip.Mask(net.CIDRMask(16, 32)).String() } + if na.RFC4380() { // teredo tunnels have the last 4 bytes as the v4 address XOR // 0xff. @@ -902,20 +907,24 @@ func (a *addrBook) groupKey(na *p2p.NetAddress) string { for i, byte := range na.IP[12:16] { ip[i] = byte ^ 0xff } - return (&net.IPNet{IP: ip, Mask: net.CIDRMask(16, 32)}).String() + return ip.Mask(net.CIDRMask(16, 32)).String() + } + + if na.OnionCatTor() { + // group is keyed off the first 4 bits of the actual onion key. + return fmt.Sprintf("tor:%d", na.IP[6]&((1<<4)-1)) } // OK, so now we know ourselves to be a IPv6 address. // bitcoind uses /32 for everything, except for Hurricane Electric's // (he.net) IP range, which it uses /36 for. bits := 32 - heNet := &net.IPNet{IP: net.ParseIP("2001:470::"), - Mask: net.CIDRMask(32, 128)} + heNet := &net.IPNet{IP: net.ParseIP("2001:470::"), Mask: net.CIDRMask(32, 128)} if heNet.Contains(na.IP) { bits = 36 } - - return (&net.IPNet{IP: na.IP, Mask: net.CIDRMask(bits, 128)}).String() + ipv6Mask := net.CIDRMask(bits, 128) + return na.IP.Mask(ipv6Mask).String() } func (a *addrBook) hash(b []byte) ([]byte, error) { diff --git a/p2p/pex/addrbook_test.go b/p2p/pex/addrbook_test.go index 739fff185..e50b7be37 100644 --- a/p2p/pex/addrbook_test.go +++ b/p2p/pex/addrbook_test.go @@ -5,6 +5,7 @@ import ( "fmt" "io/ioutil" "math" + "net" "os" "testing" "time" @@ -572,6 +573,73 @@ func TestMultipleAddrBookAddressSelection(t *testing.T) { } } +func TestAddrBookGroupKey(t *testing.T) { + // non-strict routability + testCases := []struct { + name string + ip string + expKey string + }{ + // IPv4 normal. + {"ipv4 normal class a", "12.1.2.3", "12.1.0.0"}, + {"ipv4 normal class b", "173.1.2.3", "173.1.0.0"}, + {"ipv4 normal class c", "196.1.2.3", "196.1.0.0"}, + + // IPv6/IPv4 translations. + {"ipv6 rfc3964 with ipv4 encap", "2002:0c01:0203::", "12.1.0.0"}, + {"ipv6 rfc4380 toredo ipv4", "2001:0:1234::f3fe:fdfc", "12.1.0.0"}, + {"ipv6 rfc6052 well-known prefix with ipv4", "64:ff9b::0c01:0203", "12.1.0.0"}, + {"ipv6 rfc6145 translated ipv4", "::ffff:0:0c01:0203", "12.1.0.0"}, + + // Tor. + {"ipv6 tor onioncat", "fd87:d87e:eb43:1234::5678", "tor:2"}, + {"ipv6 tor onioncat 2", "fd87:d87e:eb43:1245::6789", "tor:2"}, + {"ipv6 tor onioncat 3", "fd87:d87e:eb43:1345::6789", "tor:3"}, + + // IPv6 normal. + {"ipv6 normal", "2602:100::1", "2602:100::"}, + {"ipv6 normal 2", "2602:0100::1234", "2602:100::"}, + {"ipv6 hurricane electric", "2001:470:1f10:a1::2", "2001:470:1000::"}, + {"ipv6 hurricane electric 2", "2001:0470:1f10:a1::2", "2001:470:1000::"}, + } + + for i, tc := range testCases { + nip := net.ParseIP(tc.ip) + key := groupKeyFor(p2p.NewNetAddressIPPort(nip, 26656), false) + assert.Equal(t, tc.expKey, key, "#%d", i) + } + + // strict routability + testCases = []struct { + name string + ip string + expKey string + }{ + // Local addresses. + {"ipv4 localhost", "127.0.0.1", "local"}, + {"ipv6 localhost", "::1", "local"}, + {"ipv4 zero", "0.0.0.0", "local"}, + {"ipv4 first octet zero", "0.1.2.3", "local"}, + + // Unroutable addresses. + {"ipv4 invalid bcast", "255.255.255.255", "unroutable"}, + {"ipv4 rfc1918 10/8", "10.1.2.3", "unroutable"}, + {"ipv4 rfc1918 172.16/12", "172.16.1.2", "unroutable"}, + {"ipv4 rfc1918 192.168/16", "192.168.1.2", "unroutable"}, + {"ipv6 rfc3849 2001:db8::/32", "2001:db8::1234", "unroutable"}, + {"ipv4 rfc3927 169.254/16", "169.254.1.2", "unroutable"}, + {"ipv6 rfc4193 fc00::/7", "fc00::1234", "unroutable"}, + {"ipv6 rfc4843 2001:10::/28", "2001:10::1234", "unroutable"}, + {"ipv6 rfc4862 fe80::/64", "fe80::1234", "unroutable"}, + } + + for i, tc := range testCases { + nip := net.ParseIP(tc.ip) + key := groupKeyFor(p2p.NewNetAddressIPPort(nip, 26656), true) + assert.Equal(t, tc.expKey, key, "#%d", i) + } +} + func assertMOldAndNNewAddrsInSelection(t *testing.T, m, n int, addrs []*p2p.NetAddress, book *addrBook) { nOld, nNew := countOldAndNewAddrsInSelection(addrs, book) assert.Equal(t, m, nOld, "old addresses")