From 1ec4a1e77ffb1c64fea6a87112ba58db9fda6cfe Mon Sep 17 00:00:00 2001 From: eugene Date: Thu, 3 Feb 2022 13:38:41 -0500 Subject: [PATCH 1/3] wire: netaddressv2 and tests --- wire/netaddressv2.go | 588 ++++++++++++++++++++++++++++++++++++++ wire/netaddressv2_test.go | 285 ++++++++++++++++++ 2 files changed, 873 insertions(+) create mode 100644 wire/netaddressv2.go create mode 100644 wire/netaddressv2_test.go diff --git a/wire/netaddressv2.go b/wire/netaddressv2.go new file mode 100644 index 0000000000..15f7916456 --- /dev/null +++ b/wire/netaddressv2.go @@ -0,0 +1,588 @@ +package wire + +import ( + "encoding/base32" + "encoding/binary" + "fmt" + "io" + "net" + "strings" + "time" + + "golang.org/x/crypto/sha3" +) + +const ( + // maxAddrV2Size is the maximum size an address may be in the addrv2 + // message. + maxAddrV2Size = 512 +) + +var ( + // ErrInvalidAddressSize is an error that means an incorrect address + // size was decoded for a networkID or that the address exceeded the + // maximum size for an unknown networkID. + ErrInvalidAddressSize = fmt.Errorf("invalid address size") + + // ErrSkippedNetworkID is returned when the cjdns, i2p, or unknown + // networks are encountered during decoding. btcd does not support i2p + // or cjdns addresses. In the case of an unknown networkID, this is so + // that a future BIP reserving a new networkID does not cause older + // addrv2-supporting btcd software to disconnect upon receiving the new + // addresses. This error can also be returned when an OnionCat-encoded + // torv2 address is received with the ipv6 networkID. This error + // signals to the caller to continue reading. + ErrSkippedNetworkID = fmt.Errorf("skipped networkID") +) + +// maxNetAddressV2Payload returns the max payload size for an address used in +// the addrv2 message. +func maxNetAddressV2Payload() uint32 { + // The timestamp takes up four bytes. + plen := uint32(4) + + // The ServiceFlag is a varint and its maximum size is 9 bytes. + plen += 9 + + // The netID is a single byte. + plen += 1 + + // The largest address is 512 bytes. Even though it will not be a valid + // address, we should read and ignore it. The preceeding varint to + // store 512 bytes is 3 bytes long. This gives us a total of 515 bytes. + plen += 515 + + // The port is 2 bytes. + plen += 2 + + return plen +} + +// isOnionCatTor returns whether a given ip address is actually an encoded tor +// v2 address. The wire package is unable to use the addrmgr's IsOnionCatTor as +// doing so would give an import cycle. +func isOnionCatTor(ip net.IP) bool { + onionCatNet := net.IPNet{ + IP: net.ParseIP("fd87:d87e:eb43::"), + Mask: net.CIDRMask(48, 128), + } + return onionCatNet.Contains(ip) +} + +// NetAddressV2 defines information about a peer on the network including the +// last time it was seen, the services it supports, its address, and port. This +// struct is used in the addrv2 message (MsgAddrV2) and can contain larger +// addresses, like Tor. Additionally, it can contain any NetAddress address. +type NetAddressV2 struct { + // Last time the address was seen. This is, unfortunately, encoded as a + // uint32 on the wire and therefore is limited to 2106. This field is + // not present in the bitcoin version message (MsgVersion) nor was it + // added until protocol version >= NetAddressTimeVersion. + Timestamp time.Time + + // Services is a bitfield which identifies the services supported by + // the address. This is encoded in CompactSize. + Services ServiceFlag + + // Addr is the network address of the peer. This is a variable-length + // address. Network() returns the BIP-155 networkID which is a uint8 + // encoded as a string. String() returns the address as a string. + Addr net.Addr + + // Port is the port of the address. This is 0 if the network doesn't + // use ports. + Port uint16 +} + +// HasService returns whether the specified service is supported by the +// address. +func (na *NetAddressV2) HasService(service ServiceFlag) bool { + return na.Services&service == service +} + +// AddService adds a service to the Services bitfield. +func (na *NetAddressV2) AddService(service ServiceFlag) { + na.Services |= service +} + +// ToLegacy attempts to convert a NetAddressV2 to a legacy NetAddress. This +// only works for ipv4, ipv6, or torv2 addresses as they can be encoded with +// the OnionCat encoding. If this method is called on a torv3 address, nil will +// be returned. +func (na *NetAddressV2) ToLegacy() *NetAddress { + legacyNa := &NetAddress{ + Timestamp: na.Timestamp, + Services: na.Services, + Port: na.Port, + } + + switch a := na.Addr.(type) { + case *ipv4Addr: + legacyNa.IP = a.addr[:] + case *ipv6Addr: + legacyNa.IP = a.addr[:] + case *torv2Addr: + legacyNa.IP = a.onionCatEncoding() + case *torv3Addr: + return nil + } + + return legacyNa +} + +// IsTorV3 returns a bool that signals to the caller whether or not this is a +// torv3 address. +func (na *NetAddressV2) IsTorV3() bool { + _, ok := na.Addr.(*torv3Addr) + return ok +} + +// TorV3Key returns the first byte of the v3 public key. This is used in the +// addrmgr to calculate a key from a network group. +func (na *NetAddressV2) TorV3Key() byte { + // This should never be called on a non-torv3 address. + addr, ok := na.Addr.(*torv3Addr) + if !ok { + panic("unexpected TorV3Key call on non-torv3 address") + } + + return addr.addr[0] +} + +// NetAddressV2FromBytes creates a NetAddressV2 from a byte slice. It will +// also handle a torv2 address using the OnionCat encoding. +func NetAddressV2FromBytes(timestamp time.Time, services ServiceFlag, + addrBytes []byte, port uint16) *NetAddressV2 { + + var netAddr net.Addr + switch len(addrBytes) { + case ipv4Size: + addr := &ipv4Addr{} + addr.netID = ipv4 + copy(addr.addr[:], addrBytes) + netAddr = addr + case ipv6Size: + if isOnionCatTor(addrBytes) { + addr := &torv2Addr{} + addr.netID = torv2 + copy(addr.addr[:], addrBytes[6:]) + netAddr = addr + break + } + + addr := &ipv6Addr{} + addr.netID = ipv6 + copy(addr.addr[:], addrBytes) + netAddr = addr + case torv2Size: + addr := &torv2Addr{} + addr.netID = torv2 + copy(addr.addr[:], addrBytes) + netAddr = addr + case TorV3Size: + addr := &torv3Addr{} + addr.netID = torv3 + copy(addr.addr[:], addrBytes) + netAddr = addr + } + + return &NetAddressV2{ + Timestamp: timestamp, + Services: services, + Addr: netAddr, + Port: port, + } +} + +// writeNetAddressV2 writes a NetAddressV2 to a writer. +func writeNetAddressV2(w io.Writer, pver uint32, na *NetAddressV2) error { + err := writeElement(w, uint32(na.Timestamp.Unix())) + if err != nil { + return err + } + + if err := WriteVarInt(w, pver, uint64(na.Services)); err != nil { + return err + } + + var ( + netID networkID + address []byte + ) + + switch a := na.Addr.(type) { + case *ipv4Addr: + netID = a.netID + address = a.addr[:] + case *ipv6Addr: + netID = a.netID + address = a.addr[:] + case *torv2Addr: + netID = a.netID + address = a.addr[:] + case *torv3Addr: + netID = a.netID + address = a.addr[:] + default: + // This should not occur. + return fmt.Errorf("unexpected address type") + } + + if err := writeElement(w, netID); err != nil { + return err + } + + addressSize := uint64(len(address)) + if err := WriteVarInt(w, pver, addressSize); err != nil { + return err + } + + if err := writeElement(w, address); err != nil { + return err + } + + return binary.Write(w, bigEndian, na.Port) +} + +// readNetAddressV2 reads a NetAddressV2 from a reader. This function has +// checks that the corresponding write function doesn't. This is because +// reading from the peer is untrusted whereas writing assumes we have already +// validated the NetAddressV2. +func readNetAddressV2(r io.Reader, pver uint32, na *NetAddressV2) error { + err := readElement(r, (*uint32Time)(&na.Timestamp)) + if err != nil { + return err + } + + // Services is encoded as a variable length integer in addrv2. + services, err := ReadVarInt(r, pver) + if err != nil { + return err + } + na.Services = ServiceFlag(services) + + var netID uint8 + if err := readElement(r, &netID); err != nil { + return err + } + + decodedSize, err := ReadVarInt(r, pver) + if err != nil { + return err + } + + if !isKnownNetworkID(netID) { + // In the case of an unknown networkID, we'll read the address + // size and error with ErrInvalidAddressSize if it's greater + // than maxAddrV2Size. If the address size is within the valid + // range, we'll just read and discard the address. In this + // case, ErrSkippedNetworkID will be returned to signal to the + // caller to continue reading. + if decodedSize > maxAddrV2Size { + return ErrInvalidAddressSize + } + + // The +2 is the port field. + discardedAddrPort := make([]byte, decodedSize+2) + if err := readElement(r, &discardedAddrPort); err != nil { + return err + } + + return ErrSkippedNetworkID + } + + // If the netID is an i2p or cjdns address, we'll advance the reader + // and return a special error to signal to the caller to not use the + // passed NetAddressV2 struct. Otherwise, we'll just read the address + // and port without returning an error. + switch networkID(netID) { + case ipv4: + addr := &ipv4Addr{} + addr.netID = ipv4 + if decodedSize != uint64(ipv4Size) { + return ErrInvalidAddressSize + } + + if err := readElement(r, &addr.addr); err != nil { + return err + } + + na.Port, err = binarySerializer.Uint16(r, bigEndian) + if err != nil { + return err + } + + na.Addr = addr + case ipv6: + addr := &ipv6Addr{} + addr.netID = ipv6 + if decodedSize != uint64(ipv6Size) { + return ErrInvalidAddressSize + } + + if err := readElement(r, &addr.addr); err != nil { + return err + } + + na.Port, err = binarySerializer.Uint16(r, bigEndian) + if err != nil { + return err + } + + na.Addr = addr + + // BIP-155 says to ignore OnionCat addresses in addrv2 + // messages. + if isOnionCatTor(addr.addr[:]) { + return ErrSkippedNetworkID + } + case torv2: + addr := &torv2Addr{} + addr.netID = torv2 + if decodedSize != uint64(torv2Size) { + return ErrInvalidAddressSize + } + + if err := readElement(r, &addr.addr); err != nil { + return err + } + + na.Port, err = binarySerializer.Uint16(r, bigEndian) + if err != nil { + return err + } + + na.Addr = addr + case torv3: + addr := &torv3Addr{} + addr.netID = torv3 + if decodedSize != uint64(TorV3Size) { + return ErrInvalidAddressSize + } + + if err := readElement(r, &addr.addr); err != nil { + return err + } + + na.Port, err = binarySerializer.Uint16(r, bigEndian) + if err != nil { + return err + } + + // BIP-155 does not specify to validate the public key here. + // bitcoind does not validate the ed25519 pubkey. + na.Addr = addr + case i2p: + addr := &i2pAddr{} + addr.netID = i2p + if decodedSize != uint64(i2pSize) { + return ErrInvalidAddressSize + } + + if err := readElement(r, &addr.addr); err != nil { + return err + } + + na.Port, err = binarySerializer.Uint16(r, bigEndian) + if err != nil { + return err + } + + return ErrSkippedNetworkID + case cjdns: + addr := &cjdnsAddr{} + addr.netID = cjdns + if decodedSize != uint64(cjdnsSize) { + return ErrInvalidAddressSize + } + + if err := readElement(r, &addr.addr); err != nil { + return err + } + + na.Port, err = binarySerializer.Uint16(r, bigEndian) + if err != nil { + return err + } + + return ErrSkippedNetworkID + } + + return nil +} + +// networkID represents the network that a given address is in. CJDNS and I2P +// addresses are not included. +type networkID uint8 + +const ( + // ipv4 means the following address is ipv4. + ipv4 networkID = iota + 1 + + // ipv6 means the following address is ipv6. + ipv6 + + // torv2 means the following address is a torv2 hidden service address. + torv2 + + // torv3 means the following address is a torv3 hidden service address. + torv3 + + // i2p means the following address is an i2p address. + i2p + + // cjdns means the following address is a cjdns address. + cjdns +) + +const ( + // ipv4Size is the size of an ipv4 address. + ipv4Size = 4 + + // ipv6Size is the size of an ipv6 address. + ipv6Size = 16 + + // torv2Size is the size of a torv2 address. + torv2Size = 10 + + // TorV3Size is the size of a torv3 address in bytes. + TorV3Size = 32 + + // i2pSize is the size of an i2p address. + i2pSize = 32 + + // cjdnsSize is the size of a cjdns address. + cjdnsSize = 16 +) + +const ( + // TorV2EncodedSize is the size of a torv2 address encoded in base32 + // with the ".onion" suffix. + TorV2EncodedSize = 22 + + // TorV3EncodedSize is the size of a torv3 address encoded in base32 + // with the ".onion" suffix. + TorV3EncodedSize = 62 +) + +// isKnownNetworkID returns true if the networkID is one listed above and false +// otherwise. +func isKnownNetworkID(netID uint8) bool { + return uint8(ipv4) <= netID && netID <= uint8(cjdns) +} + +type ipv4Addr struct { + addr [ipv4Size]byte + netID networkID +} + +// Part of the net.Addr interface. +func (a *ipv4Addr) String() string { + return net.IP(a.addr[:]).String() +} + +// Part of the net.Addr interface. +func (a *ipv4Addr) Network() string { + return string(a.netID) +} + +// Compile-time constraints to check that ipv4Addr meets the net.Addr +// interface. +var _ net.Addr = (*ipv4Addr)(nil) + +type ipv6Addr struct { + addr [ipv6Size]byte + netID networkID +} + +// Part of the net.Addr interface. +func (a *ipv6Addr) String() string { + return net.IP(a.addr[:]).String() +} + +// Part of the net.Addr interface. +func (a *ipv6Addr) Network() string { + return string(a.netID) +} + +// Compile-time constraints to check that ipv6Addr meets the net.Addr +// interface. +var _ net.Addr = (*ipv4Addr)(nil) + +type torv2Addr struct { + addr [torv2Size]byte + netID networkID +} + +// Part of the net.Addr interface. +func (a *torv2Addr) String() string { + base32Hash := base32.StdEncoding.EncodeToString(a.addr[:]) + return strings.ToLower(base32Hash) + ".onion" +} + +// Part of the net.Addr interface. +func (a *torv2Addr) Network() string { + return string(a.netID) +} + +// onionCatEncoding returns a torv2 address as an ipv6 address. +func (a *torv2Addr) onionCatEncoding() net.IP { + prefix := []byte{0xfd, 0x87, 0xd8, 0x7e, 0xeb, 0x43} + return net.IP(append(prefix, a.addr[:]...)) +} + +// Compile-time constraints to check that torv2Addr meets the net.Addr +// interface. +var _ net.Addr = (*torv2Addr)(nil) + +type torv3Addr struct { + addr [TorV3Size]byte + netID networkID +} + +// Part of the net.Addr interface. +func (a *torv3Addr) String() string { + // BIP-155 describes the torv3 address format: + // onion_address = base32(PUBKEY | CHECKSUM | VERSION) + ".onion" + // CHECKSUM = H(".onion checksum" | PUBKEY | VERSION)[:2] + // PUBKEY = addr, which is the ed25519 pubkey of the hidden service. + // VERSION = '\x03' + // H() is the SHA3-256 cryptographic hash function. + + torV3Version := []byte("\x03") + checksumConst := []byte(".onion checksum") + + // Write never returns an error so there is no need to handle it. + h := sha3.New256() + h.Write(checksumConst) + h.Write(a.addr[:]) + h.Write(torV3Version) + truncatedChecksum := h.Sum(nil)[:2] + + var base32Input [35]byte + copy(base32Input[:32], a.addr[:]) + copy(base32Input[32:34], truncatedChecksum) + copy(base32Input[34:], torV3Version) + + base32Hash := base32.StdEncoding.EncodeToString(base32Input[:]) + return strings.ToLower(base32Hash) + ".onion" +} + +// Part of the net.Addr interface. +func (a *torv3Addr) Network() string { + return string(a.netID) +} + +// Compile-time constraints to check that torv3Addr meets the net.Addr +// interface. +var _ net.Addr = (*torv3Addr)(nil) + +type i2pAddr struct { + addr [i2pSize]byte + netID networkID +} + +type cjdnsAddr struct { + addr [cjdnsSize]byte + netID networkID +} diff --git a/wire/netaddressv2_test.go b/wire/netaddressv2_test.go new file mode 100644 index 0000000000..bd5084b009 --- /dev/null +++ b/wire/netaddressv2_test.go @@ -0,0 +1,285 @@ +package wire + +import ( + "bytes" + "io" + "testing" + "time" +) + +// TestNetAddressV2FromBytes tests that NetAddressV2FromBytes works as +// expected. +func TestNetAddressV2FromBytes(t *testing.T) { + tests := []struct { + addrBytes []byte + expectedString string + expectedNetwork string + }{ + // Ipv4 encoding + { + []byte{0x7f, 0x00, 0x00, 0x01}, + "127.0.0.1", + string(ipv4), + }, + + // Ipv6 encoding + { + []byte{ + 0x20, 0x01, 0x09, 0xe8, 0x26, 0x15, 0x73, 0x00, + 0x09, 0x54, 0x12, 0x63, 0xef, 0xc8, 0x2e, 0x34, + }, + "2001:9e8:2615:7300:954:1263:efc8:2e34", + string(ipv6), + }, + + // OnionCat encoding + { + []byte{ + 0xfd, 0x87, 0xd8, 0x7e, 0xeb, 0x43, 0xff, 0xfe, + 0xcc, 0x39, 0xa8, 0x73, 0x69, 0x15, 0xff, 0xff, + }, + "777myonionurl777.onion", + string(torv2), + }, + + // Torv2 encoding + { + []byte{ + 0xff, 0xfe, 0xcc, 0x39, 0xa8, 0x73, 0x69, 0x15, + 0xff, 0xff, + }, + "777myonionurl777.onion", + string(torv2), + }, + + // Torv3 encoding + { + []byte{ + 0xca, 0xd2, 0xd3, 0xc8, 0xdc, 0x9c, 0xc4, 0xd3, + 0x70, 0x33, 0x30, 0xc5, 0x23, 0xaf, 0x02, 0xed, + 0xc4, 0x9d, 0xf8, 0xc6, 0xb0, 0x4e, 0x74, 0x6d, + 0x3b, 0x51, 0x57, 0xa7, 0x15, 0xfe, 0x98, 0x35, + }, + "zljnhsg4ttcng4btgdcshlyc5xcj36ggwbhhi3j3kfl2ofp6ta26jlid.onion", + string(torv3), + }, + } + + t.Logf("Running %d tests", len(tests)) + for i, test := range tests { + na := NetAddressV2FromBytes(time.Time{}, 0, test.addrBytes, 0) + + if test.expectedNetwork != string(torv3) { + if na.ToLegacy() == nil { + t.Errorf("Test #%d has nil legacy encoding", i) + } + } else { + if !na.IsTorV3() { + t.Errorf("Test #%d is not torv3 address", i) + } + } + + if na.Addr.String() != test.expectedString { + t.Errorf("Test #%d did not match expected string", i) + } + + if na.Addr.Network() != test.expectedNetwork { + t.Errorf("Test #%d did not match expected network", i) + } + + var b bytes.Buffer + if err := writeNetAddressV2(&b, 0, na); err != nil { + t.Errorf("Test #%d failed writing address %v", i, err) + } + + // Assert that the written netID is equivalent to the above. + if string(b.Bytes()[5]) != test.expectedNetwork { + t.Errorf("Test #%d did not match expected network", i) + } + } +} + +// TestReadNetAddressV2 tests that readNetAddressV2 behaves as expected in +// different scenarios. +func TestReadNetAddressV2(t *testing.T) { + tests := []struct { + buf []byte + expectedNetwork string + expectedError error + }{ + // Invalid address size for unknown netID. + { + []byte{ + 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xfd, 0xff, + 0xff, + }, + "", + ErrInvalidAddressSize, + }, + + // Valid address size for unknown netID. + { + []byte{ + 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x20, 0x10, + 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, + 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, + 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, + 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x22, + 0x22, + }, + "", + ErrSkippedNetworkID, + }, + + // Invalid ipv4 size. + { + []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x05}, + "", + ErrInvalidAddressSize, + }, + + // Valid ipv4 encoding. + { + []byte{ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x04, 0x7f, + 0x00, 0x00, 0x01, 0x22, 0x22, + }, + string(ipv4), + nil, + }, + + // Invalid ipv6 size. + { + []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xfc}, + "", + ErrInvalidAddressSize, + }, + + // OnionCat encoding is skipped. + { + []byte{ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x10, 0xfd, + 0x87, 0xd8, 0x7e, 0xeb, 0x43, 0xff, 0xfe, 0xcc, + 0x39, 0xa8, 0x73, 0x69, 0x15, 0xff, 0xff, 0x22, + 0x22, + }, + "", + ErrSkippedNetworkID, + }, + + // Valid ipv6 encoding. + { + []byte{ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x10, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x22, + 0x22, + }, + string(ipv6), + nil, + }, + + // Invalid torv2 size. + { + []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x02}, + "", + ErrInvalidAddressSize, + }, + + // Valid torv2 encoding. + { + []byte{ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x0a, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x22, 0x22, + }, + string(torv2), + nil, + }, + + // Invalid torv3 size. + { + []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x02}, + "", + ErrInvalidAddressSize, + }, + + // Valid torv3 encoding. + { + []byte{ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x20, 0x10, + 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, + 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, + 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, + 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x22, + 0x22, + }, + string(torv3), + nil, + }, + + // Invalid i2p size. + { + []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x02}, + "", + ErrInvalidAddressSize, + }, + + // Valid i2p encoding. + { + []byte{ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x20, 0x10, + 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, + 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, + 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, + 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x22, + 0x22, + }, + string(i2p), + ErrSkippedNetworkID, + }, + + // Invalid cjdns size. + { + []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x02}, + "", + ErrInvalidAddressSize, + }, + + // Valid cjdns encoding. + { + []byte{ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x10, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, + 0x22, + }, + string(cjdns), + ErrSkippedNetworkID, + }, + } + + t.Logf("Running %d tests", len(tests)) + for i, test := range tests { + r := bytes.NewReader(test.buf) + na := &NetAddressV2{} + + err := readNetAddressV2(r, 0, na) + if err != test.expectedError { + t.Errorf("Test #%d had unexpected error %v", i, err) + } else if err != nil { + continue + } + + // Trying to read more should give EOF. + var b [1]byte + if _, err := r.Read(b[:]); err != io.EOF { + t.Errorf("Test #%d did not cleanly finish reading", i) + } + + if na.Addr.Network() != test.expectedNetwork { + t.Errorf("Test #%d had unexpected network %v", i, + na.Addr.Network()) + } + } +} From 201c0836ec9a0ed3523a2eaee87a8370129702a1 Mon Sep 17 00:00:00 2001 From: eugene Date: Thu, 3 Feb 2022 14:02:46 -0500 Subject: [PATCH 2/3] multi: update addrmgr, server to use NetAddressV2 instead of legacy --- addrmgr/addrmanager.go | 182 +++++++++++++++++---------- addrmgr/addrmanager_internal_test.go | 32 +++-- addrmgr/addrmanager_test.go | 175 ++++++++++++++++++-------- addrmgr/internal_test.go | 2 +- addrmgr/knownaddress.go | 8 +- addrmgr/knownaddress_test.go | 18 +-- addrmgr/network.go | 55 +++++--- addrmgr/network_test.go | 12 +- connmgr/seed.go | 6 +- rpcadapters.go | 2 +- rpcserver.go | 4 +- server.go | 80 ++++++++---- 12 files changed, 381 insertions(+), 195 deletions(-) diff --git a/addrmgr/addrmanager.go b/addrmgr/addrmanager.go index 4c737b937e..bdfe9094ce 100644 --- a/addrmgr/addrmanager.go +++ b/addrmgr/addrmanager.go @@ -70,7 +70,7 @@ type serializedAddrManager struct { } type localAddress struct { - na *wire.NetAddress + na *wire.NetAddressV2 score AddressPriority } @@ -163,7 +163,7 @@ const ( // updateAddress is a helper function to either update an address already known // to the address manager, or to add the address if not already known. -func (a *AddrManager) updateAddress(netAddr, srcAddr *wire.NetAddress) { +func (a *AddrManager) updateAddress(netAddr, srcAddr *wire.NetAddressV2) { // Filter out non-routable addresses. Note that non-routable // also includes invalid and local addresses. if !IsRoutable(netAddr) { @@ -296,7 +296,7 @@ func (a *AddrManager) pickTried(bucket int) *list.Element { return oldestElem } -func (a *AddrManager) getNewBucket(netAddr, srcAddr *wire.NetAddress) int { +func (a *AddrManager) getNewBucket(netAddr, srcAddr *wire.NetAddressV2) int { // bitcoind: // doublesha256(key + sourcegroup + int64(doublesha256(key + group + sourcegroup))%bucket_per_source_group) % num_new_buckets @@ -318,7 +318,7 @@ func (a *AddrManager) getNewBucket(netAddr, srcAddr *wire.NetAddress) int { return int(binary.LittleEndian.Uint64(hash2) % newBucketCount) } -func (a *AddrManager) getTriedBucket(netAddr *wire.NetAddress) int { +func (a *AddrManager) getTriedBucket(netAddr *wire.NetAddressV2) int { // bitcoind hashes this as: // doublesha256(key + group + truncate_to_64bits(doublesha256(key)) % buckets_per_group) % num_buckets data1 := []byte{} @@ -550,7 +550,7 @@ func (a *AddrManager) deserializePeers(filePath string) error { // DeserializeNetAddress converts a given address string to a *wire.NetAddress. func (a *AddrManager) DeserializeNetAddress(addr string, - services wire.ServiceFlag) (*wire.NetAddress, error) { + services wire.ServiceFlag) (*wire.NetAddressV2, error) { host, portStr, err := net.SplitHostPort(addr) if err != nil { @@ -599,7 +599,7 @@ func (a *AddrManager) Stop() error { // AddAddresses adds new addresses to the address manager. It enforces a max // number of addresses and silently ignores duplicate addresses. It is // safe for concurrent access. -func (a *AddrManager) AddAddresses(addrs []*wire.NetAddress, srcAddr *wire.NetAddress) { +func (a *AddrManager) AddAddresses(addrs []*wire.NetAddressV2, srcAddr *wire.NetAddressV2) { a.mtx.Lock() defer a.mtx.Unlock() @@ -611,7 +611,7 @@ func (a *AddrManager) AddAddresses(addrs []*wire.NetAddress, srcAddr *wire.NetAd // AddAddress adds a new address to the address manager. It enforces a max // number of addresses and silently ignores duplicate addresses. It is // safe for concurrent access. -func (a *AddrManager) AddAddress(addr, srcAddr *wire.NetAddress) { +func (a *AddrManager) AddAddress(addr, srcAddr *wire.NetAddressV2) { a.mtx.Lock() defer a.mtx.Unlock() @@ -635,7 +635,7 @@ func (a *AddrManager) AddAddressByIP(addrIP string) error { if err != nil { return fmt.Errorf("invalid port %s: %v", portStr, err) } - na := wire.NewNetAddressIPPort(ip, uint16(port), 0) + na := wire.NetAddressV2FromBytes(time.Now(), 0, ip, uint16(port)) a.AddAddress(na, na) // XXX use correct src address return nil } @@ -664,7 +664,7 @@ func (a *AddrManager) NeedMoreAddresses() bool { // AddressCache returns the current address cache. It must be treated as // read-only (but since it is a copy now, this is not as dangerous). -func (a *AddrManager) AddressCache() []*wire.NetAddress { +func (a *AddrManager) AddressCache() []*wire.NetAddressV2 { allAddr := a.getAddresses() numAddresses := len(allAddr) * getAddrPercent / 100 @@ -686,7 +686,7 @@ func (a *AddrManager) AddressCache() []*wire.NetAddress { // getAddresses returns all of the addresses currently found within the // manager's address cache. -func (a *AddrManager) getAddresses() []*wire.NetAddress { +func (a *AddrManager) getAddresses() []*wire.NetAddressV2 { a.mtx.RLock() defer a.mtx.RUnlock() @@ -695,7 +695,7 @@ func (a *AddrManager) getAddresses() []*wire.NetAddress { return nil } - addrs := make([]*wire.NetAddress, 0, addrIndexLen) + addrs := make([]*wire.NetAddressV2, 0, addrIndexLen) for _, v := range a.addrIndex { addrs = append(addrs, v.na) } @@ -722,20 +722,43 @@ func (a *AddrManager) reset() { // HostToNetAddress returns a netaddress given a host address. If the address // is a Tor .onion address this will be taken care of. Else if the host is // not an IP address it will be resolved (via Tor if required). -func (a *AddrManager) HostToNetAddress(host string, port uint16, services wire.ServiceFlag) (*wire.NetAddress, error) { - // Tor address is 16 char base32 + ".onion" - var ip net.IP - if len(host) == 22 && host[16:] == ".onion" { +func (a *AddrManager) HostToNetAddress(host string, port uint16, + services wire.ServiceFlag) (*wire.NetAddressV2, error) { + + var ( + na *wire.NetAddressV2 + ip net.IP + ) + + // Tor v2 address is 16 char base32 + ".onion" + if len(host) == wire.TorV2EncodedSize && host[wire.TorV2EncodedSize-6:] == ".onion" { // go base32 encoding uses capitals (as does the rfc // but Tor and bitcoind tend to user lowercase, so we switch // case here. data, err := base32.StdEncoding.DecodeString( - strings.ToUpper(host[:16])) + strings.ToUpper(host[:wire.TorV2EncodedSize-6])) + if err != nil { + return nil, err + } + + na = wire.NetAddressV2FromBytes( + time.Now(), services, data, port, + ) + } else if len(host) == wire.TorV3EncodedSize && host[wire.TorV3EncodedSize-6:] == ".onion" { + // Tor v3 addresses are 56 base32 characters with the 6 byte + // onion suffix. + data, err := base32.StdEncoding.DecodeString( + strings.ToUpper(host[:wire.TorV3EncodedSize-6]), + ) if err != nil { return nil, err } - prefix := []byte{0xfd, 0x87, 0xd8, 0x7e, 0xeb, 0x43} - ip = net.IP(append(prefix, data...)) + + // The first 32 bytes is the ed25519 public key and is enough + // to reconstruct the .onion address. + na = wire.NetAddressV2FromBytes( + time.Now(), services, data[:wire.TorV3Size], port, + ) } else if ip = net.ParseIP(host); ip == nil { ips, err := a.lookupFunc(host) if err != nil { @@ -745,30 +768,23 @@ func (a *AddrManager) HostToNetAddress(host string, port uint16, services wire.S return nil, fmt.Errorf("no addresses found for %s", host) } ip = ips[0] - } - - return wire.NewNetAddressIPPort(ip, port, services), nil -} -// ipString returns a string for the ip from the provided NetAddress. If the -// ip is in the range used for Tor addresses then it will be transformed into -// the relevant .onion address. -func ipString(na *wire.NetAddress) string { - if IsOnionCatTor(na) { - // We know now that na.IP is long enough. - base32 := base32.StdEncoding.EncodeToString(na.IP[6:]) - return strings.ToLower(base32) + ".onion" + na = wire.NetAddressV2FromBytes(time.Now(), services, ip, port) + } else { + // This is an non-nil IP address that was parsed in the else if + // above. + na = wire.NetAddressV2FromBytes(time.Now(), services, ip, port) } - return na.IP.String() + return na, nil } // NetAddressKey returns a string key in the form of ip:port for IPv4 addresses -// or [ip]:port for IPv6 addresses. -func NetAddressKey(na *wire.NetAddress) string { +// or [ip]:port for IPv6 addresses. It also handles onion v2 and v3 addresses. +func NetAddressKey(na *wire.NetAddressV2) string { port := strconv.FormatUint(uint64(na.Port), 10) - return net.JoinHostPort(ipString(na), port) + return net.JoinHostPort(na.Addr.String(), port) } // GetAddress returns a single address that should be routable. It picks a @@ -842,13 +858,13 @@ func (a *AddrManager) GetAddress() *KnownAddress { } } -func (a *AddrManager) find(addr *wire.NetAddress) *KnownAddress { +func (a *AddrManager) find(addr *wire.NetAddressV2) *KnownAddress { return a.addrIndex[NetAddressKey(addr)] } // Attempt increases the given address' attempt counter and updates // the last attempt time. -func (a *AddrManager) Attempt(addr *wire.NetAddress) { +func (a *AddrManager) Attempt(addr *wire.NetAddressV2) { a.mtx.Lock() defer a.mtx.Unlock() @@ -869,7 +885,7 @@ func (a *AddrManager) Attempt(addr *wire.NetAddress) { // Connected Marks the given address as currently connected and working at the // current time. The address must already be known to AddrManager else it will // be ignored. -func (a *AddrManager) Connected(addr *wire.NetAddress) { +func (a *AddrManager) Connected(addr *wire.NetAddressV2) { a.mtx.Lock() defer a.mtx.Unlock() @@ -894,7 +910,7 @@ func (a *AddrManager) Connected(addr *wire.NetAddress) { // Good marks the given address as good. To be called after a successful // connection and version exchange. If the address is unknown to the address // manager it will be ignored. -func (a *AddrManager) Good(addr *wire.NetAddress) { +func (a *AddrManager) Good(addr *wire.NetAddressV2) { a.mtx.Lock() defer a.mtx.Unlock() @@ -983,7 +999,7 @@ func (a *AddrManager) Good(addr *wire.NetAddress) { } // SetServices sets the services for the giiven address to the provided value. -func (a *AddrManager) SetServices(addr *wire.NetAddress, services wire.ServiceFlag) { +func (a *AddrManager) SetServices(addr *wire.NetAddressV2, services wire.ServiceFlag) { a.mtx.Lock() defer a.mtx.Unlock() @@ -1005,9 +1021,11 @@ func (a *AddrManager) SetServices(addr *wire.NetAddress, services wire.ServiceFl // AddLocalAddress adds na to the list of known local addresses to advertise // with the given priority. -func (a *AddrManager) AddLocalAddress(na *wire.NetAddress, priority AddressPriority) error { +func (a *AddrManager) AddLocalAddress(na *wire.NetAddressV2, priority AddressPriority) error { if !IsRoutable(na) { - return fmt.Errorf("address %s is not routable", na.IP) + return fmt.Errorf( + "address %s is not routable", na.Addr.String(), + ) } a.lamtx.Lock() @@ -1030,7 +1048,7 @@ func (a *AddrManager) AddLocalAddress(na *wire.NetAddress, priority AddressPrior // getReachabilityFrom returns the relative reachability of the provided local // address to the provided remote address. -func getReachabilityFrom(localAddr, remoteAddr *wire.NetAddress) int { +func getReachabilityFrom(localAddr, remoteAddr *wire.NetAddressV2) int { const ( Unreachable = 0 Default = iota @@ -1045,36 +1063,66 @@ func getReachabilityFrom(localAddr, remoteAddr *wire.NetAddress) int { return Unreachable } - if IsOnionCatTor(remoteAddr) { - if IsOnionCatTor(localAddr) { + if remoteAddr.IsTorV3() { + if localAddr.IsTorV3() { return Private } - if IsRoutable(localAddr) && IsIPv4(localAddr) { + lna := localAddr.ToLegacy() + if IsOnionCatTor(lna) { + // Modern v3 clients should not be able to connect to + // deprecated v2 hidden services. + return Unreachable + } + + if IsRoutable(localAddr) && IsIPv4(lna) { return Ipv4 } return Default } - if IsRFC4380(remoteAddr) { + // We can't be sure if the remote party can actually connect to this + // address or not. + if localAddr.IsTorV3() { + return Default + } + + // Convert the V2 addresses into legacy to access the network + // functions. + remoteLna := remoteAddr.ToLegacy() + localLna := localAddr.ToLegacy() + + if IsOnionCatTor(remoteLna) { + if IsOnionCatTor(localLna) { + return Private + } + + if IsRoutable(localAddr) && IsIPv4(localLna) { + return Ipv4 + } + + return Default + } + + if IsRFC4380(remoteLna) { if !IsRoutable(localAddr) { return Default } - if IsRFC4380(localAddr) { + if IsRFC4380(localLna) { return Teredo } - if IsIPv4(localAddr) { + if IsIPv4(localLna) { return Ipv4 } return Ipv6Weak } - if IsIPv4(remoteAddr) { - if IsRoutable(localAddr) && IsIPv4(localAddr) { + if IsIPv4(remoteLna) { + if IsRoutable(localAddr) && IsIPv4(localLna) { return Ipv4 } return Unreachable @@ -1083,7 +1131,7 @@ func getReachabilityFrom(localAddr, remoteAddr *wire.NetAddress) int { /* ipv6 */ var tunnelled bool // Is our v6 is tunnelled? - if IsRFC3964(localAddr) || IsRFC6052(localAddr) || IsRFC6145(localAddr) { + if IsRFC3964(localLna) || IsRFC6052(localLna) || IsRFC6145(localLna) { tunnelled = true } @@ -1091,11 +1139,11 @@ func getReachabilityFrom(localAddr, remoteAddr *wire.NetAddress) int { return Default } - if IsRFC4380(localAddr) { + if IsRFC4380(localLna) { return Teredo } - if IsIPv4(localAddr) { + if IsIPv4(localLna) { return Ipv4 } @@ -1109,13 +1157,13 @@ func getReachabilityFrom(localAddr, remoteAddr *wire.NetAddress) int { // GetBestLocalAddress returns the most appropriate local address to use // for the given remote address. -func (a *AddrManager) GetBestLocalAddress(remoteAddr *wire.NetAddress) *wire.NetAddress { +func (a *AddrManager) GetBestLocalAddress(remoteAddr *wire.NetAddressV2) *wire.NetAddressV2 { a.lamtx.Lock() defer a.lamtx.Unlock() bestreach := 0 var bestscore AddressPriority - var bestAddress *wire.NetAddress + var bestAddress *wire.NetAddressV2 for _, la := range a.localAddresses { reach := getReachabilityFrom(la.na, remoteAddr) if reach > bestreach || @@ -1126,21 +1174,29 @@ func (a *AddrManager) GetBestLocalAddress(remoteAddr *wire.NetAddress) *wire.Net } } if bestAddress != nil { - log.Debugf("Suggesting address %s:%d for %s:%d", bestAddress.IP, - bestAddress.Port, remoteAddr.IP, remoteAddr.Port) + log.Debugf("Suggesting address %s:%d for %s:%d", + bestAddress.Addr.String(), bestAddress.Port, + remoteAddr.Addr.String(), remoteAddr.Port) } else { - log.Debugf("No worthy address for %s:%d", remoteAddr.IP, - remoteAddr.Port) + log.Debugf("No worthy address for %s:%d", + remoteAddr.Addr.String(), remoteAddr.Port) // Send something unroutable if nothing suitable. var ip net.IP - if !IsIPv4(remoteAddr) && !IsOnionCatTor(remoteAddr) { - ip = net.IPv6zero - } else { + if remoteAddr.IsTorV3() { ip = net.IPv4zero + } else { + remoteLna := remoteAddr.ToLegacy() + if !IsIPv4(remoteLna) && !IsOnionCatTor(remoteLna) { + ip = net.IPv6zero + } else { + ip = net.IPv4zero + } } services := wire.SFNodeNetwork | wire.SFNodeWitness | wire.SFNodeBloom - bestAddress = wire.NewNetAddressIPPort(ip, 0, services) + bestAddress = wire.NetAddressV2FromBytes( + time.Now(), services, ip, 0, + ) } return bestAddress diff --git a/addrmgr/addrmanager_internal_test.go b/addrmgr/addrmanager_internal_test.go index 1c19dceb18..1d13f78e6e 100644 --- a/addrmgr/addrmanager_internal_test.go +++ b/addrmgr/addrmanager_internal_test.go @@ -6,12 +6,14 @@ import ( "net" "os" "testing" + "time" "github.com/btcsuite/btcd/wire" ) -// randAddr generates a *wire.NetAddress backed by a random IPv4/IPv6 address. -func randAddr(t *testing.T) *wire.NetAddress { +// randAddr generates a *wire.NetAddressV2 backed by a random IPv4/IPv6 +// address. +func randAddr(t *testing.T) *wire.NetAddressV2 { t.Helper() ipv4 := rand.Intn(2) == 0 @@ -30,22 +32,26 @@ func randAddr(t *testing.T) *wire.NetAddress { ip = b[:] } - return &wire.NetAddress{ - Services: wire.ServiceFlag(rand.Uint64()), - IP: ip, - Port: uint16(rand.Uint32()), - } + services := wire.ServiceFlag(rand.Uint64()) + port := uint16(rand.Uint32()) + + return wire.NetAddressV2FromBytes( + time.Now(), services, ip, port, + ) } // assertAddr ensures that the two addresses match. The timestamp is not // checked as it does not affect uniquely identifying a specific address. -func assertAddr(t *testing.T, got, expected *wire.NetAddress) { +func assertAddr(t *testing.T, got, expected *wire.NetAddressV2) { if got.Services != expected.Services { t.Fatalf("expected address services %v, got %v", expected.Services, got.Services) } - if !got.IP.Equal(expected.IP) { - t.Fatalf("expected address IP %v, got %v", expected.IP, got.IP) + gotAddr := got.Addr.String() + expectedAddr := expected.Addr.String() + if gotAddr != expectedAddr { + t.Fatalf("expected address IP %v, got %v", expectedAddr, + gotAddr) } if got.Port != expected.Port { t.Fatalf("expected address port %d, got %d", expected.Port, @@ -56,7 +62,7 @@ func assertAddr(t *testing.T, got, expected *wire.NetAddress) { // assertAddrs ensures that the manager's address cache matches the given // expected addresses. func assertAddrs(t *testing.T, addrMgr *AddrManager, - expectedAddrs map[string]*wire.NetAddress) { + expectedAddrs map[string]*wire.NetAddressV2) { t.Helper() @@ -96,7 +102,7 @@ func TestAddrManagerSerialization(t *testing.T) { // We'll be adding 5 random addresses to the manager. const numAddrs = 5 - expectedAddrs := make(map[string]*wire.NetAddress, numAddrs) + expectedAddrs := make(map[string]*wire.NetAddressV2, numAddrs) for i := 0; i < numAddrs; i++ { addr := randAddr(t) expectedAddrs[NetAddressKey(addr)] = addr @@ -141,7 +147,7 @@ func TestAddrManagerV1ToV2(t *testing.T) { // each addresses' services will not be stored. const numAddrs = 5 - expectedAddrs := make(map[string]*wire.NetAddress, numAddrs) + expectedAddrs := make(map[string]*wire.NetAddressV2, numAddrs) for i := 0; i < numAddrs; i++ { addr := randAddr(t) expectedAddrs[NetAddressKey(addr)] = addr diff --git a/addrmgr/addrmanager_test.go b/addrmgr/addrmanager_test.go index 676913e21e..4afe5fd601 100644 --- a/addrmgr/addrmanager_test.go +++ b/addrmgr/addrmanager_test.go @@ -19,7 +19,7 @@ import ( // naTest is used to describe a test to be performed against the NetAddressKey // method. type naTest struct { - in wire.NetAddress + in wire.NetAddressV2 want string } @@ -93,8 +93,10 @@ func addNaTests() { func addNaTest(ip string, port uint16, want string) { nip := net.ParseIP(ip) - na := *wire.NewNetAddressIPPort(nip, port, wire.SFNodeNetwork) - test := naTest{na, want} + na := wire.NetAddressV2FromBytes( + time.Now(), wire.SFNodeNetwork, nip, port, + ) + test := naTest{*na, want} naTests = append(naTests, test) } @@ -157,37 +159,49 @@ func TestAddAddressByIP(t *testing.T) { func TestAddLocalAddress(t *testing.T) { var tests = []struct { - address wire.NetAddress + address wire.NetAddressV2 priority addrmgr.AddressPriority valid bool }{ { - wire.NetAddress{IP: net.ParseIP("192.168.0.100")}, + *wire.NetAddressV2FromBytes( + time.Now(), 0, net.ParseIP("192.168.0.100"), 0, + ), addrmgr.InterfacePrio, false, }, { - wire.NetAddress{IP: net.ParseIP("204.124.1.1")}, + *wire.NetAddressV2FromBytes( + time.Now(), 0, net.ParseIP("204.124.1.1"), 0, + ), addrmgr.InterfacePrio, true, }, { - wire.NetAddress{IP: net.ParseIP("204.124.1.1")}, + *wire.NetAddressV2FromBytes( + time.Now(), 0, net.ParseIP("204.124.1.1"), 0, + ), addrmgr.BoundPrio, true, }, { - wire.NetAddress{IP: net.ParseIP("::1")}, + *wire.NetAddressV2FromBytes( + time.Now(), 0, net.ParseIP("::1"), 0, + ), addrmgr.InterfacePrio, false, }, { - wire.NetAddress{IP: net.ParseIP("fe80::1")}, + *wire.NetAddressV2FromBytes( + time.Now(), 0, net.ParseIP("fe80::1"), 0, + ), addrmgr.InterfacePrio, false, }, { - wire.NetAddress{IP: net.ParseIP("2620:100::1")}, + *wire.NetAddressV2FromBytes( + time.Now(), 0, net.ParseIP("2620:100::1"), 0, + ), addrmgr.InterfacePrio, true, }, @@ -197,12 +211,12 @@ func TestAddLocalAddress(t *testing.T) { result := amgr.AddLocalAddress(&test.address, test.priority) if result == nil && !test.valid { t.Errorf("TestAddLocalAddress test #%d failed: %s should have "+ - "been accepted", x, test.address.IP) + "been accepted", x, test.address.Addr.String()) continue } if result != nil && test.valid { t.Errorf("TestAddLocalAddress test #%d failed: %s should not have "+ - "been accepted", x, test.address.IP) + "been accepted", x, test.address.Addr.String()) continue } } @@ -257,7 +271,7 @@ func TestNeedMoreAddresses(t *testing.T) { if !b { t.Errorf("Expected that we need more addresses") } - addrs := make([]*wire.NetAddress, addrsToAdd) + addrs := make([]*wire.NetAddressV2, addrsToAdd) var err error for i := 0; i < addrsToAdd; i++ { @@ -268,7 +282,9 @@ func TestNeedMoreAddresses(t *testing.T) { } } - srcAddr := wire.NewNetAddressIPPort(net.IPv4(173, 144, 173, 111), 8333, 0) + srcAddr := wire.NetAddressV2FromBytes( + time.Now(), 0, net.IPv4(173, 144, 173, 111), 8333, + ) n.AddAddresses(addrs, srcAddr) numAddrs := n.NumAddresses() @@ -285,7 +301,7 @@ func TestNeedMoreAddresses(t *testing.T) { func TestGood(t *testing.T) { n := addrmgr.New("testgood", lookupFunc) addrsToAdd := 64 * 64 - addrs := make([]*wire.NetAddress, addrsToAdd) + addrs := make([]*wire.NetAddressV2, addrsToAdd) var err error for i := 0; i < addrsToAdd; i++ { @@ -296,7 +312,9 @@ func TestGood(t *testing.T) { } } - srcAddr := wire.NewNetAddressIPPort(net.IPv4(173, 144, 173, 111), 8333, 0) + srcAddr := wire.NetAddressV2FromBytes( + time.Now(), 0, net.IPv4(173, 144, 173, 111), 8333, + ) n.AddAddresses(addrs, srcAddr) for _, addr := range addrs { @@ -331,8 +349,8 @@ func TestGetAddress(t *testing.T) { if ka == nil { t.Fatalf("Did not get an address where there is one in the pool") } - if ka.NetAddress().IP.String() != someIP { - t.Errorf("Wrong IP: got %v, want %v", ka.NetAddress().IP.String(), someIP) + if ka.NetAddress().Addr.String() != someIP { + t.Errorf("Wrong IP: got %v, want %v", ka.NetAddress().Addr.String(), someIP) } // Mark this as a good address and get it @@ -341,8 +359,8 @@ func TestGetAddress(t *testing.T) { if ka == nil { t.Fatalf("Did not get an address where there is one in the pool") } - if ka.NetAddress().IP.String() != someIP { - t.Errorf("Wrong IP: got %v, want %v", ka.NetAddress().IP.String(), someIP) + if ka.NetAddress().Addr.String() != someIP { + t.Errorf("Wrong IP: got %v, want %v", ka.NetAddress().Addr.String(), someIP) } numAddrs := n.NumAddresses() @@ -352,43 +370,83 @@ func TestGetAddress(t *testing.T) { } func TestGetBestLocalAddress(t *testing.T) { - localAddrs := []wire.NetAddress{ - {IP: net.ParseIP("192.168.0.100")}, - {IP: net.ParseIP("::1")}, - {IP: net.ParseIP("fe80::1")}, - {IP: net.ParseIP("2001:470::1")}, + localAddrs := []wire.NetAddressV2{ + *wire.NetAddressV2FromBytes( + time.Now(), 0, net.ParseIP("192.168.0.100"), 0, + ), + *wire.NetAddressV2FromBytes( + time.Now(), 0, net.ParseIP("::1"), 0, + ), + *wire.NetAddressV2FromBytes( + time.Now(), 0, net.ParseIP("fe80::1"), 0, + ), + *wire.NetAddressV2FromBytes( + time.Now(), 0, net.ParseIP("2001:470::1"), 0, + ), } var tests = []struct { - remoteAddr wire.NetAddress - want0 wire.NetAddress - want1 wire.NetAddress - want2 wire.NetAddress - want3 wire.NetAddress + remoteAddr wire.NetAddressV2 + want0 wire.NetAddressV2 + want1 wire.NetAddressV2 + want2 wire.NetAddressV2 + want3 wire.NetAddressV2 }{ { // Remote connection from public IPv4 - wire.NetAddress{IP: net.ParseIP("204.124.8.1")}, - wire.NetAddress{IP: net.IPv4zero}, - wire.NetAddress{IP: net.IPv4zero}, - wire.NetAddress{IP: net.ParseIP("204.124.8.100")}, - wire.NetAddress{IP: net.ParseIP("fd87:d87e:eb43:25::1")}, + *wire.NetAddressV2FromBytes( + time.Now(), 0, net.ParseIP("204.124.8.1"), 0, + ), + *wire.NetAddressV2FromBytes( + time.Now(), 0, net.IPv4zero, 0, + ), + *wire.NetAddressV2FromBytes( + time.Now(), 0, net.IPv4zero, 0, + ), + *wire.NetAddressV2FromBytes( + time.Now(), 0, net.ParseIP("204.124.8.100"), 0, + ), + *wire.NetAddressV2FromBytes( + time.Now(), 0, + net.ParseIP("fd87:d87e:eb43:25::1"), 0, + ), }, { // Remote connection from private IPv4 - wire.NetAddress{IP: net.ParseIP("172.16.0.254")}, - wire.NetAddress{IP: net.IPv4zero}, - wire.NetAddress{IP: net.IPv4zero}, - wire.NetAddress{IP: net.IPv4zero}, - wire.NetAddress{IP: net.IPv4zero}, + *wire.NetAddressV2FromBytes( + time.Now(), 0, net.ParseIP("172.16.0.254"), 0, + ), + *wire.NetAddressV2FromBytes( + time.Now(), 0, net.IPv4zero, 0, + ), + *wire.NetAddressV2FromBytes( + time.Now(), 0, net.IPv4zero, 0, + ), + *wire.NetAddressV2FromBytes( + time.Now(), 0, net.IPv4zero, 0, + ), + *wire.NetAddressV2FromBytes( + time.Now(), 0, net.IPv4zero, 0, + ), }, { // Remote connection from public IPv6 - wire.NetAddress{IP: net.ParseIP("2602:100:abcd::102")}, - wire.NetAddress{IP: net.IPv6zero}, - wire.NetAddress{IP: net.ParseIP("2001:470::1")}, - wire.NetAddress{IP: net.ParseIP("2001:470::1")}, - wire.NetAddress{IP: net.ParseIP("2001:470::1")}, + *wire.NetAddressV2FromBytes( + time.Now(), 0, + net.ParseIP("2602:100:abcd::102"), 0, + ), + *wire.NetAddressV2FromBytes( + time.Now(), 0, net.IPv6zero, 0, + ), + *wire.NetAddressV2FromBytes( + time.Now(), 0, net.ParseIP("2001:470::1"), 0, + ), + *wire.NetAddressV2FromBytes( + time.Now(), 0, net.ParseIP("2001:470::1"), 0, + ), + *wire.NetAddressV2FromBytes( + time.Now(), 0, net.ParseIP("2001:470::1"), 0, + ), }, /* XXX { @@ -406,9 +464,12 @@ func TestGetBestLocalAddress(t *testing.T) { // Test against default when there's no address for x, test := range tests { got := amgr.GetBestLocalAddress(&test.remoteAddr) - if !test.want0.IP.Equal(got.IP) { + wantAddr := test.want0.Addr.String() + gotAddr := got.Addr.String() + if wantAddr != gotAddr { + remoteAddr := test.remoteAddr.Addr.String() t.Errorf("TestGetBestLocalAddress test1 #%d failed for remote address %s: want %s got %s", - x, test.remoteAddr.IP, test.want1.IP, got.IP) + x, remoteAddr, wantAddr, gotAddr) continue } } @@ -420,23 +481,31 @@ func TestGetBestLocalAddress(t *testing.T) { // Test against want1 for x, test := range tests { got := amgr.GetBestLocalAddress(&test.remoteAddr) - if !test.want1.IP.Equal(got.IP) { + wantAddr := test.want1.Addr.String() + gotAddr := got.Addr.String() + if wantAddr != gotAddr { + remoteAddr := test.remoteAddr.Addr.String() t.Errorf("TestGetBestLocalAddress test1 #%d failed for remote address %s: want %s got %s", - x, test.remoteAddr.IP, test.want1.IP, got.IP) + x, remoteAddr, wantAddr, gotAddr) continue } } // Add a public IP to the list of local addresses. - localAddr := wire.NetAddress{IP: net.ParseIP("204.124.8.100")} - amgr.AddLocalAddress(&localAddr, addrmgr.InterfacePrio) + localAddr := wire.NetAddressV2FromBytes( + time.Now(), 0, net.ParseIP("204.124.8.100"), 0, + ) + amgr.AddLocalAddress(localAddr, addrmgr.InterfacePrio) // Test against want2 for x, test := range tests { got := amgr.GetBestLocalAddress(&test.remoteAddr) - if !test.want2.IP.Equal(got.IP) { + wantAddr := test.want2.Addr.String() + gotAddr := got.Addr.String() + if wantAddr != gotAddr { + remoteAddr := test.remoteAddr.Addr.String() t.Errorf("TestGetBestLocalAddress test2 #%d failed for remote address %s: want %s got %s", - x, test.remoteAddr.IP, test.want2.IP, got.IP) + x, remoteAddr, wantAddr, gotAddr) continue } } diff --git a/addrmgr/internal_test.go b/addrmgr/internal_test.go index e50e923cf7..ab7644b6ca 100644 --- a/addrmgr/internal_test.go +++ b/addrmgr/internal_test.go @@ -18,7 +18,7 @@ func TstKnownAddressChance(ka *KnownAddress) float64 { return ka.chance() } -func TstNewKnownAddress(na *wire.NetAddress, attempts int, +func TstNewKnownAddress(na *wire.NetAddressV2, attempts int, lastattempt, lastsuccess time.Time, tried bool, refs int) *KnownAddress { return &KnownAddress{na: na, attempts: attempts, lastattempt: lastattempt, lastsuccess: lastsuccess, tried: tried, refs: refs} diff --git a/addrmgr/knownaddress.go b/addrmgr/knownaddress.go index 5a7674f45b..b045365508 100644 --- a/addrmgr/knownaddress.go +++ b/addrmgr/knownaddress.go @@ -15,8 +15,8 @@ import ( // to determine how viable an address is. type KnownAddress struct { mtx sync.RWMutex // na and lastattempt - na *wire.NetAddress - srcAddr *wire.NetAddress + na *wire.NetAddressV2 + srcAddr *wire.NetAddressV2 attempts int lastattempt time.Time lastsuccess time.Time @@ -24,9 +24,9 @@ type KnownAddress struct { refs int // reference count of new buckets } -// NetAddress returns the underlying wire.NetAddress associated with the +// NetAddress returns the underlying wire.NetAddressV2 associated with the // known address. -func (ka *KnownAddress) NetAddress() *wire.NetAddress { +func (ka *KnownAddress) NetAddress() *wire.NetAddressV2 { ka.mtx.RLock() defer ka.mtx.RUnlock() return ka.na diff --git a/addrmgr/knownaddress_test.go b/addrmgr/knownaddress_test.go index a289d5a386..b4a2650140 100644 --- a/addrmgr/knownaddress_test.go +++ b/addrmgr/knownaddress_test.go @@ -21,27 +21,27 @@ func TestChance(t *testing.T) { }{ { //Test normal case - addrmgr.TstNewKnownAddress(&wire.NetAddress{Timestamp: now.Add(-35 * time.Second)}, + addrmgr.TstNewKnownAddress(&wire.NetAddressV2{Timestamp: now.Add(-35 * time.Second)}, 0, time.Now().Add(-30*time.Minute), time.Now(), false, 0), 1.0, }, { //Test case in which lastseen < 0 - addrmgr.TstNewKnownAddress(&wire.NetAddress{Timestamp: now.Add(20 * time.Second)}, + addrmgr.TstNewKnownAddress(&wire.NetAddressV2{Timestamp: now.Add(20 * time.Second)}, 0, time.Now().Add(-30*time.Minute), time.Now(), false, 0), 1.0, }, { //Test case in which lastattempt < 0 - addrmgr.TstNewKnownAddress(&wire.NetAddress{Timestamp: now.Add(-35 * time.Second)}, + addrmgr.TstNewKnownAddress(&wire.NetAddressV2{Timestamp: now.Add(-35 * time.Second)}, 0, time.Now().Add(30*time.Minute), time.Now(), false, 0), 1.0 * .01, }, { //Test case in which lastattempt < ten minutes - addrmgr.TstNewKnownAddress(&wire.NetAddress{Timestamp: now.Add(-35 * time.Second)}, + addrmgr.TstNewKnownAddress(&wire.NetAddressV2{Timestamp: now.Add(-35 * time.Second)}, 0, time.Now().Add(-5*time.Minute), time.Now(), false, 0), 1.0 * .01, }, { //Test case with several failed attempts. - addrmgr.TstNewKnownAddress(&wire.NetAddress{Timestamp: now.Add(-35 * time.Second)}, + addrmgr.TstNewKnownAddress(&wire.NetAddressV2{Timestamp: now.Add(-35 * time.Second)}, 2, time.Now().Add(-30*time.Minute), time.Now(), false, 0), 1 / 1.5 / 1.5, }, @@ -65,10 +65,10 @@ func TestIsBad(t *testing.T) { hoursOld := now.Add(-5 * time.Hour) zeroTime := time.Time{} - futureNa := &wire.NetAddress{Timestamp: future} - minutesOldNa := &wire.NetAddress{Timestamp: minutesOld} - monthOldNa := &wire.NetAddress{Timestamp: monthOld} - currentNa := &wire.NetAddress{Timestamp: secondsOld} + futureNa := &wire.NetAddressV2{Timestamp: future} + minutesOldNa := &wire.NetAddressV2{Timestamp: minutesOld} + monthOldNa := &wire.NetAddressV2{Timestamp: monthOld} + currentNa := &wire.NetAddressV2{Timestamp: secondsOld} //Test addresses that have been tried in the last minute. if addrmgr.TstKnownAddressIsBad(addrmgr.TstNewKnownAddress(futureNa, 3, secondsOld, zeroTime, false, 0)) { diff --git a/addrmgr/network.go b/addrmgr/network.go index 51bfa3ed75..7f30901b21 100644 --- a/addrmgr/network.go +++ b/addrmgr/network.go @@ -222,11 +222,20 @@ func IsValid(na *wire.NetAddress) bool { // IsRoutable returns whether or not the passed address is routable over // the public internet. This is true as long as the address is valid and is not // in any reserved ranges. -func IsRoutable(na *wire.NetAddress) bool { - return IsValid(na) && !(IsRFC1918(na) || IsRFC2544(na) || - IsRFC3927(na) || IsRFC4862(na) || IsRFC3849(na) || - IsRFC4843(na) || IsRFC5737(na) || IsRFC6598(na) || - IsLocal(na) || (IsRFC4193(na) && !IsOnionCatTor(na))) +func IsRoutable(na *wire.NetAddressV2) bool { + if na.IsTorV3() { + // na is a torv3 address, return true. + return true + } + + // Else na can be represented as a legacy NetAddress since i2p and + // cjdns are unsupported. + lna := na.ToLegacy() + return IsValid(lna) && !(IsRFC1918(lna) || IsRFC2544(lna) || + IsRFC3927(lna) || IsRFC4862(lna) || IsRFC3849(lna) || + IsRFC4843(lna) || IsRFC5737(lna) || IsRFC6598(lna) || + IsLocal(lna) || (IsRFC4193(lna) && + !IsOnionCatTor(lna))) } // GroupKey returns a string representing the network group an address is part @@ -234,48 +243,56 @@ func IsRoutable(na *wire.NetAddress) bool { // "local" for a local address, the string "tor:key" where key is the /4 of the // onion address for Tor address, and the string "unroutable" for an unroutable // address. -func GroupKey(na *wire.NetAddress) string { - if IsLocal(na) { +func GroupKey(na *wire.NetAddressV2) string { + if na.IsTorV3() { + // na is a torv3 address. Use the same network group keying as + // for torv2. + return fmt.Sprintf("tor:%d", na.TorV3Key()&((1<<4)-1)) + } + + lna := na.ToLegacy() + + if IsLocal(lna) { return "local" } if !IsRoutable(na) { return "unroutable" } - if IsIPv4(na) { - return na.IP.Mask(net.CIDRMask(16, 32)).String() + if IsIPv4(lna) { + return lna.IP.Mask(net.CIDRMask(16, 32)).String() } - if IsRFC6145(na) || IsRFC6052(na) { + if IsRFC6145(lna) || IsRFC6052(lna) { // last four bytes are the ip address - ip := na.IP[12:16] + ip := lna.IP[12:16] return ip.Mask(net.CIDRMask(16, 32)).String() } - if IsRFC3964(na) { - ip := na.IP[2:6] + if IsRFC3964(lna) { + ip := lna.IP[2:6] return ip.Mask(net.CIDRMask(16, 32)).String() } - if IsRFC4380(na) { + if IsRFC4380(lna) { // teredo tunnels have the last 4 bytes as the v4 address XOR // 0xff. ip := net.IP(make([]byte, 4)) - for i, byte := range na.IP[12:16] { + for i, byte := range lna.IP[12:16] { ip[i] = byte ^ 0xff } return ip.Mask(net.CIDRMask(16, 32)).String() } - if IsOnionCatTor(na) { + if IsOnionCatTor(lna) { // group is keyed off the first 4 bits of the actual onion key. - return fmt.Sprintf("tor:%d", na.IP[6]&((1<<4)-1)) + return fmt.Sprintf("tor:%d", lna.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 - if heNet.Contains(na.IP) { + if heNet.Contains(lna.IP) { bits = 36 } - return na.IP.Mask(net.CIDRMask(bits, 128)).String() + return lna.IP.Mask(net.CIDRMask(bits, 128)).String() } diff --git a/addrmgr/network_test.go b/addrmgr/network_test.go index 8af3369f56..f4bc5d88f7 100644 --- a/addrmgr/network_test.go +++ b/addrmgr/network_test.go @@ -7,6 +7,7 @@ package addrmgr_test import ( "net" "testing" + "time" "github.com/btcsuite/btcd/addrmgr" "github.com/btcsuite/btcd/wire" @@ -136,7 +137,10 @@ func TestIPTypes(t *testing.T) { t.Errorf("IsValid %s\n got: %v want: %v", test.in.IP, rv, test.valid) } - if rv := addrmgr.IsRoutable(&test.in); rv != test.routable { + currentNa := wire.NetAddressV2FromBytes( + time.Now(), test.in.Services, test.in.IP, test.in.Port, + ) + if rv := addrmgr.IsRoutable(currentNa); rv != test.routable { t.Errorf("IsRoutable %s\n got: %v want: %v", test.in.IP, rv, test.routable) } } @@ -192,8 +196,10 @@ func TestGroupKey(t *testing.T) { for i, test := range tests { nip := net.ParseIP(test.ip) - na := *wire.NewNetAddressIPPort(nip, 8333, wire.SFNodeNetwork) - if key := addrmgr.GroupKey(&na); key != test.expected { + na := wire.NetAddressV2FromBytes( + time.Now(), wire.SFNodeNetwork, nip, 8333, + ) + if key := addrmgr.GroupKey(na); key != test.expected { t.Errorf("TestGroupKey #%d (%s): unexpected group key "+ "- got '%s', want '%s'", i, test.name, key, test.expected) diff --git a/connmgr/seed.go b/connmgr/seed.go index 063b546ab8..4c26160d8f 100644 --- a/connmgr/seed.go +++ b/connmgr/seed.go @@ -24,7 +24,7 @@ const ( // OnSeed is the signature of the callback function which is invoked when DNS // seeding is succesfull. -type OnSeed func(addrs []*wire.NetAddress) +type OnSeed func(addrs []*wire.NetAddressV2) // LookupFunc is the signature of the DNS lookup function. type LookupFunc func(string) ([]net.IP, error) @@ -56,11 +56,11 @@ func SeedFromDNS(chainParams *chaincfg.Params, reqServices wire.ServiceFlag, if numPeers == 0 { return } - addresses := make([]*wire.NetAddress, len(seedpeers)) + addresses := make([]*wire.NetAddressV2, len(seedpeers)) // if this errors then we have *real* problems intPort, _ := strconv.Atoi(chainParams.DefaultPort) for i, peer := range seedpeers { - addresses[i] = wire.NewNetAddressTimestamp( + addresses[i] = wire.NetAddressV2FromBytes( // bitcoind seeds with addresses from // a time randomly selected between 3 // and 7 days ago. diff --git a/rpcadapters.go b/rpcadapters.go index 02d33710ac..487574a81c 100644 --- a/rpcadapters.go +++ b/rpcadapters.go @@ -228,7 +228,7 @@ func (cm *rpcConnManager) RelayTransactions(txns []*mempool.TxDesc) { // // This function is safe for concurrent access and is part of the // rpcserverConnManager interface implementation. -func (cm *rpcConnManager) NodeAddresses() []*wire.NetAddress { +func (cm *rpcConnManager) NodeAddresses() []*wire.NetAddressV2 { return cm.server.addrManager.AddressCache() } diff --git a/rpcserver.go b/rpcserver.go index b8012b2b01..4bbeb29ae9 100644 --- a/rpcserver.go +++ b/rpcserver.go @@ -2518,7 +2518,7 @@ func handleGetNodeAddresses(s *rpcServer, cmd interface{}, closeChan <-chan stru address := &btcjson.GetNodeAddressesResult{ Time: node.Timestamp.Unix(), Services: uint64(node.Services), - Address: node.IP.String(), + Address: node.Addr.String(), Port: node.Port, } addresses = append(addresses, address) @@ -4524,7 +4524,7 @@ type rpcserverConnManager interface { // NodeAddresses returns an array consisting node addresses which can // potentially be used to find new nodes in the network. - NodeAddresses() []*wire.NetAddress + NodeAddresses() []*wire.NetAddressV2 } // rpcserverSyncManager represents a sync manager for use with the RPC server. diff --git a/server.go b/server.go index 45bfb17ee4..2b5cc0ff42 100644 --- a/server.go +++ b/server.go @@ -25,6 +25,8 @@ import ( "github.com/btcsuite/btcd/addrmgr" "github.com/btcsuite/btcd/blockchain" "github.com/btcsuite/btcd/blockchain/indexers" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/btcutil/bloom" "github.com/btcsuite/btcd/chaincfg" "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/connmgr" @@ -36,8 +38,7 @@ import ( "github.com/btcsuite/btcd/peer" "github.com/btcsuite/btcd/txscript" "github.com/btcsuite/btcd/wire" - "github.com/btcsuite/btcd/btcutil" - "github.com/btcsuite/btcd/btcutil/bloom" + "github.com/decred/dcrd/lru" ) const ( @@ -274,7 +275,7 @@ type serverPeer struct { isWhitelisted bool filter *bloom.Filter addressesMtx sync.RWMutex - knownAddresses map[string]struct{} + knownAddresses lru.Cache banScore connmgr.DynamicBanScore quit chan struct{} // The following chans are used to sync blockmanager and server. @@ -289,7 +290,7 @@ func newServerPeer(s *server, isPersistent bool) *serverPeer { server: s, persistent: isPersistent, filter: bloom.LoadFilter(nil), - knownAddresses: make(map[string]struct{}), + knownAddresses: lru.NewCache(5000), quit: make(chan struct{}), txProcessed: make(chan struct{}, 1), blockProcessed: make(chan struct{}, 1), @@ -305,18 +306,18 @@ func (sp *serverPeer) newestBlock() (*chainhash.Hash, int32, error) { // addKnownAddresses adds the given addresses to the set of known addresses to // the peer to prevent sending duplicate addresses. -func (sp *serverPeer) addKnownAddresses(addresses []*wire.NetAddress) { +func (sp *serverPeer) addKnownAddresses(addresses []*wire.NetAddressV2) { sp.addressesMtx.Lock() for _, na := range addresses { - sp.knownAddresses[addrmgr.NetAddressKey(na)] = struct{}{} + sp.knownAddresses.Add(addrmgr.NetAddressKey(na)) } sp.addressesMtx.Unlock() } // addressKnown true if the given address is already known to the peer. -func (sp *serverPeer) addressKnown(na *wire.NetAddress) bool { +func (sp *serverPeer) addressKnown(na *wire.NetAddressV2) bool { sp.addressesMtx.RLock() - _, exists := sp.knownAddresses[addrmgr.NetAddressKey(na)] + exists := sp.knownAddresses.Contains(addrmgr.NetAddressKey(na)) sp.addressesMtx.RUnlock() return exists } @@ -340,23 +341,45 @@ func (sp *serverPeer) relayTxDisabled() bool { return isDisabled } -// pushAddrMsg sends an addr message to the connected peer using the provided -// addresses. -func (sp *serverPeer) pushAddrMsg(addresses []*wire.NetAddress) { - // Filter addresses already known to the peer. +// pushAddrMsg sends a legacy addr message to the connected peer using the +// provided addresses. +func (sp *serverPeer) pushAddrMsg(addresses []*wire.NetAddressV2) { addrs := make([]*wire.NetAddress, 0, len(addresses)) for _, addr := range addresses { - if !sp.addressKnown(addr) { - addrs = append(addrs, addr) + // Filter addresses already known to the peer. + if sp.addressKnown(addr) { + continue + } + + // Must skip the V3 addresses for legacy ADDR messages. + if addr.IsTorV3() { + continue } + + // Convert the NetAddressV2 to a legacy address. + addrs = append(addrs, addr.ToLegacy()) } + known, err := sp.PushAddrMsg(addrs) if err != nil { - peerLog.Errorf("Can't push address message to %s: %v", sp.Peer, err) + peerLog.Errorf( + "Can't push address message to %s: %v", sp.Peer, err, + ) sp.Disconnect() return } - sp.addKnownAddresses(known) + + // Convert all of the known addresses to NetAddressV2 to add them to + // the set of known addresses. + knownAddrs := make([]*wire.NetAddressV2, 0, len(known)) + for _, knownAddr := range known { + currentKna := wire.NetAddressV2FromBytes( + knownAddr.Timestamp, knownAddr.Services, + knownAddr.IP, knownAddr.Port, + ) + knownAddrs = append(knownAddrs, currentKna) + } + sp.addKnownAddresses(knownAddrs) } // addBanScore increases the persistent and decaying ban score fields by the @@ -1272,6 +1295,7 @@ func (sp *serverPeer) OnAddr(_ *peer.Peer, msg *wire.MsgAddr) { return } + addrs := make([]*wire.NetAddressV2, 0, len(msg.AddrList)) for _, na := range msg.AddrList { // Don't add more address if we're disconnecting. if !sp.Connected() { @@ -1286,8 +1310,14 @@ func (sp *serverPeer) OnAddr(_ *peer.Peer, msg *wire.MsgAddr) { na.Timestamp = now.Add(-1 * time.Hour * 24 * 5) } - // Add address to known addresses for this peer. - sp.addKnownAddresses([]*wire.NetAddress{na}) + // Add address to known addresses for this peer. This is + // converted to NetAddressV2 since that's what the address + // manager uses. + currentNa := wire.NetAddressV2FromBytes( + na.Timestamp, na.Services, na.IP, na.Port, + ) + addrs = append(addrs, currentNa) + sp.addKnownAddresses([]*wire.NetAddressV2{currentNa}) } // Add addresses to server address manager. The address manager handles @@ -1295,7 +1325,7 @@ func (sp *serverPeer) OnAddr(_ *peer.Peer, msg *wire.MsgAddr) { // addresses, and last seen updates. // XXX bitcoind gives a 2 hour time penalty here, do we want to do the // same? - sp.server.addrManager.AddAddresses(msg.AddrList, sp.NA()) + sp.server.addrManager.AddAddresses(addrs, sp.NA()) } // OnRead is invoked when a peer receives a message and it is used to update @@ -1700,7 +1730,7 @@ func (s *server) handleAddPeerMsg(state *peerState, sp *serverPeer) bool { lna := s.addrManager.GetBestLocalAddress(sp.NA()) if addrmgr.IsRoutable(lna) { // Filter addresses the peer already knows about. - addresses := []*wire.NetAddress{lna} + addresses := []*wire.NetAddressV2{lna} sp.pushAddrMsg(addresses) } } @@ -2152,7 +2182,7 @@ func (s *server) peerHandler() { if !cfg.DisableDNSSeed { // Add peers discovered through DNS to the address manager. connmgr.SeedFromDNS(activeNetParams.Params, defaultRequiredServices, - btcdLookup, func(addrs []*wire.NetAddress) { + btcdLookup, func(addrs []*wire.NetAddressV2) { // Bitcoind uses a lookup of the dns seeder here. This // is rather strange since the values looked up by the // DNS seed lookups will vary quite a lot. @@ -2543,8 +2573,8 @@ out: srvrLog.Warnf("UPnP can't get external address: %v", err) continue out } - na := wire.NewNetAddressIPPort(externalip, uint16(listenPort), - s.services) + na := wire.NetAddressV2FromBytes(time.Now(), s.services, + externalip, uint16(listenPort)) err = s.addrManager.AddLocalAddress(na, addrmgr.UpnpPrio) if err != nil { // XXX DeletePortMapping? @@ -3117,7 +3147,9 @@ func addLocalAddress(addrMgr *addrmgr.AddrManager, addr string, services wire.Se continue } - netAddr := wire.NewNetAddressIPPort(ifaceIP, uint16(port), services) + netAddr := wire.NetAddressV2FromBytes( + time.Now(), services, ifaceIP, uint16(port), + ) addrMgr.AddLocalAddress(netAddr, addrmgr.BoundPrio) } } else { From cb6f21b598e90c8b1ea55e569b838797e9b37efe Mon Sep 17 00:00:00 2001 From: eugene Date: Thu, 3 Feb 2022 14:04:43 -0500 Subject: [PATCH 3/3] peer+wire: add addrv2 message, protocol negotiation --- peer/peer.go | 231 ++++++++++++++++++++++++++------ peer/peer_test.go | 290 ++++++++++++++++++++++++++++++++++++----- server.go | 68 ++++++++++ wire/message.go | 18 ++- wire/message_test.go | 4 +- wire/msgaddrv2.go | 102 +++++++++++++++ wire/msgaddrv2_test.go | 73 +++++++++++ wire/protocol.go | 9 +- 8 files changed, 714 insertions(+), 81 deletions(-) create mode 100644 wire/msgaddrv2.go create mode 100644 wire/msgaddrv2_test.go diff --git a/peer/peer.go b/peer/peer.go index a7b925802f..6d34c5f822 100644 --- a/peer/peer.go +++ b/peer/peer.go @@ -29,7 +29,7 @@ import ( const ( // MaxProtocolVersion is the max protocol version the peer supports. - MaxProtocolVersion = wire.FeeFilterVersion + MaxProtocolVersion = wire.AddrV2Version // DefaultTrickleInterval is the min time between attempts to send an // inv message to a peer. @@ -102,6 +102,9 @@ type MessageListeners struct { // OnAddr is invoked when a peer receives an addr bitcoin message. OnAddr func(p *Peer, msg *wire.MsgAddr) + // OnAddrV2 is invoked when a peer receives an addrv2 bitcoin message. + OnAddrV2 func(p *Peer, msg *wire.MsgAddrV2) + // OnPing is invoked when a peer receives a ping bitcoin message. OnPing func(p *Peer, msg *wire.MsgPing) @@ -197,6 +200,9 @@ type MessageListeners struct { // message. OnSendHeaders func(p *Peer, msg *wire.MsgSendHeaders) + // OnSendAddrV2 is invoked when a peer receives a sendaddrv2 message. + OnSendAddrV2 func(p *Peer, msg *wire.MsgSendAddrV2) + // OnRead is invoked when a peer receives a bitcoin message. It // consists of the number of bytes read, the message, and whether or not // an error in the read occurred. Typically, callers will opt to use @@ -399,7 +405,7 @@ type AddrFunc func(remoteAddr *wire.NetAddress) *wire.NetAddress // HostToNetAddrFunc is a func which takes a host, port, services and returns // the netaddress. type HostToNetAddrFunc func(host string, port uint16, - services wire.ServiceFlag) (*wire.NetAddress, error) + services wire.ServiceFlag) (*wire.NetAddressV2, error) // NOTE: The overall data flow of a peer is split into 3 goroutines. Inbound // messages are read via the inHandler goroutine and generally dispatched to @@ -445,7 +451,7 @@ type Peer struct { inbound bool flagsMtx sync.Mutex // protects the peer flags below - na *wire.NetAddress + na *wire.NetAddressV2 id int32 userAgent string services wire.ServiceFlag @@ -455,6 +461,7 @@ type Peer struct { sendHeadersPreferred bool // peer sent a sendheaders message verAckReceived bool witnessEnabled bool + sendAddrV2 bool wireEncoding wire.MessageEncoding @@ -585,7 +592,7 @@ func (p *Peer) ID() int32 { // NA returns the peer network address. // // This function is safe for concurrent access. -func (p *Peer) NA() *wire.NetAddress { +func (p *Peer) NA() *wire.NetAddressV2 { p.flagsMtx.Lock() na := p.na p.flagsMtx.Unlock() @@ -820,6 +827,16 @@ func (p *Peer) IsWitnessEnabled() bool { return witnessEnabled } +// WantsAddrV2 returns if the peer supports addrv2 messages instead of the +// legacy addr messages. +func (p *Peer) WantsAddrV2() bool { + p.flagsMtx.Lock() + wantsAddrV2 := p.sendAddrV2 + p.flagsMtx.Unlock() + + return wantsAddrV2 +} + // PushAddrMsg sends an addr message to the connected peer using the provided // addresses. This function is useful over manually sending the message via // QueueMessage since it automatically limits the addresses to the maximum @@ -856,6 +873,38 @@ func (p *Peer) PushAddrMsg(addresses []*wire.NetAddress) ([]*wire.NetAddress, er return msg.AddrList, nil } +// PushAddrV2Msg is used to push an addrv2 message to the remote peer. +// +// This function is safe for concurrent access. +func (p *Peer) PushAddrV2Msg(addrs []*wire.NetAddressV2) ( + []*wire.NetAddressV2, error) { + + count := len(addrs) + + // Nothing to send. + if count == 0 { + return nil, nil + } + + m := wire.NewMsgAddrV2() + m.AddrList = make([]*wire.NetAddressV2, count) + copy(m.AddrList, addrs) + + // Randomize the addresses sent if there are more than the maximum. + if count > wire.MaxV2AddrPerMsg { + rand.Shuffle(count, func(i, j int) { + m.AddrList[i] = m.AddrList[j] + m.AddrList[j] = m.AddrList[i] + }) + + // Truncate it to the maximum size. + m.AddrList = m.AddrList[:wire.MaxV2AddrPerMsg] + } + + p.QueueMessage(m, nil) + return m.AddrList, nil +} + // PushGetBlocksMsg sends a getblocks message for the provided block locator // and stop hash. It will ignore back-to-back duplicate requests. // @@ -1363,6 +1412,19 @@ out: continue } + // Since the protocol version is 70016 but we don't + // implement compact blocks, we have to ignore unknown + // messages after the version-verack handshake. This + // matches bitcoind's behavior and is necessary since + // compact blocks negotiation occurs after the + // handshake. + if err == wire.ErrUnknownMessage { + log.Debugf("Received unknown message from %s:"+ + " %v", p, err) + idleTimer.Reset(idleTimeout) + continue + } + // Only log the error and send reject message if the // local peer is not forcibly disconnecting and the // remote peer has not disconnected. @@ -1404,6 +1466,11 @@ out: ) break out + case *wire.MsgSendAddrV2: + // Disconnect if peer sends this after the handshake is + // completed. + break out + case *wire.MsgGetAddr: if p.cfg.Listeners.OnGetAddr != nil { p.cfg.Listeners.OnGetAddr(p, msg) @@ -1414,6 +1481,11 @@ out: p.cfg.Listeners.OnAddr(p, msg) } + case *wire.MsgAddrV2: + if p.cfg.Listeners.OnAddrV2 != nil { + p.cfg.Listeners.OnAddrV2(p, msg) + } + case *wire.MsgPing: p.handlePingMsg(msg) if p.cfg.Listeners.OnPing != nil { @@ -1986,29 +2058,8 @@ func (p *Peer) readRemoteVersionMsg() error { return nil } -// readRemoteVerAckMsg waits for the next message to arrive from the remote -// peer. If this message is not a verack message, then an error is returned. -// This method is to be used as part of the version negotiation upon a new -// connection. -func (p *Peer) readRemoteVerAckMsg() error { - // Read the next message from the wire. - remoteMsg, _, err := p.readMessage(wire.LatestEncoding) - if err != nil { - return err - } - - // It should be a verack message, otherwise send a reject message to the - // peer explaining why. - msg, ok := remoteMsg.(*wire.MsgVerAck) - if !ok { - reason := "a verack message must follow version" - rejectMsg := wire.NewMsgReject( - msg.Command(), wire.RejectMalformed, reason, - ) - _ = p.writeMessage(rejectMsg, wire.LatestEncoding) - return errors.New(reason) - } - +// processRemoteVerAckMsg takes the verack from the remote peer and handles it. +func (p *Peer) processRemoteVerAckMsg(msg *wire.MsgVerAck) { p.flagsMtx.Lock() p.verAckReceived = true p.flagsMtx.Unlock() @@ -2016,8 +2067,6 @@ func (p *Peer) readRemoteVerAckMsg() error { if p.cfg.Listeners.OnVerAck != nil { p.cfg.Listeners.OnVerAck(p, msg) } - - return nil } // localVersionMsg creates a version message that can be used to send to the @@ -2032,7 +2081,15 @@ func (p *Peer) localVersionMsg() (*wire.MsgVersion, error) { } } - theirNA := p.na + theirNA := p.na.ToLegacy() + + // If p.na is a torv3 hidden service address, we'll need to send over + // an empty NetAddress for their address. + if p.na.IsTorV3() { + theirNA = wire.NewNetAddressIPPort( + net.IP([]byte{0, 0, 0, 0}), p.na.Port, p.na.Services, + ) + } // If we are behind a proxy and the connection comes from the proxy then // we return an unroutable address as their address. This is to prevent @@ -2040,7 +2097,7 @@ func (p *Peer) localVersionMsg() (*wire.MsgVersion, error) { if p.cfg.Proxy != "" { proxyaddress, _, err := net.SplitHostPort(p.cfg.Proxy) // invalid proxy means poorly configured, be on the safe side. - if err != nil || p.na.IP.String() == proxyaddress { + if err != nil || p.na.Addr.String() == proxyaddress { theirNA = wire.NewNetAddressIPPort(net.IP([]byte{0, 0, 0, 0}), 0, theirNA.Services) } @@ -2092,14 +2149,71 @@ func (p *Peer) writeLocalVersionMsg() error { return p.writeMessage(localVerMsg, wire.LatestEncoding) } +// writeSendAddrV2Msg writes our sendaddrv2 message to the remote peer if the +// peer supports protocol version 70016 and above. +func (p *Peer) writeSendAddrV2Msg(pver uint32) error { + if pver < wire.AddrV2Version { + return nil + } + + sendAddrMsg := wire.NewMsgSendAddrV2() + return p.writeMessage(sendAddrMsg, wire.LatestEncoding) +} + +// waitToFinishNegotiation waits until desired negotiation messages are +// received, recording the remote peer's preference for sendaddrv2 as an +// example. The list of negotiated features can be expanded in the future. If a +// verack is received, negotiation stops and the connection is live. +func (p *Peer) waitToFinishNegotiation(pver uint32) error { + // There are several possible messages that can be received here. We + // could immediately receive verack and be done with the handshake. We + // could receive sendaddrv2 and still have to wait for verack. Or we + // can receive unknown messages before and after sendaddrv2 and still + // have to wait for verack. + for { + remoteMsg, _, err := p.readMessage(wire.LatestEncoding) + if err == wire.ErrUnknownMessage { + continue + } else if err != nil { + return err + } + + switch m := remoteMsg.(type) { + case *wire.MsgSendAddrV2: + if pver >= wire.AddrV2Version { + p.flagsMtx.Lock() + p.sendAddrV2 = true + p.flagsMtx.Unlock() + + if p.cfg.Listeners.OnSendAddrV2 != nil { + p.cfg.Listeners.OnSendAddrV2(p, m) + } + } + case *wire.MsgVerAck: + // Receiving a verack means we are done with the + // handshake. + p.processRemoteVerAckMsg(m) + return nil + default: + // This is triggered if the peer sends, for example, a + // GETDATA message during this negotiation. + return wire.ErrInvalidHandshake + } + } +} + // negotiateInboundProtocol performs the negotiation protocol for an inbound // peer. The events should occur in the following order, otherwise an error is // returned: // // 1. Remote peer sends their version. // 2. We send our version. -// 3. We send our verack. -// 4. Remote peer sends their verack. +// 3. We send sendaddrv2 if their version is >= 70016. +// 4. We send our verack. +// 5. Wait until sendaddrv2 or verack is received. Unknown messages are +// skipped as it could be wtxidrelay or a different message in the future +// that btcd does not implement but bitcoind does. +// 6. If remote peer sent sendaddrv2 above, wait until receipt of verack. func (p *Peer) negotiateInboundProtocol() error { if err := p.readRemoteVersionMsg(); err != nil { return err @@ -2109,12 +2223,22 @@ func (p *Peer) negotiateInboundProtocol() error { return err } + var protoVersion uint32 + p.flagsMtx.Lock() + protoVersion = p.protocolVersion + p.flagsMtx.Unlock() + + if err := p.writeSendAddrV2Msg(protoVersion); err != nil { + return err + } + err := p.writeMessage(wire.NewMsgVerAck(), wire.LatestEncoding) if err != nil { return err } - return p.readRemoteVerAckMsg() + // Finish the negotiation by waiting for negotiable messages or verack. + return p.waitToFinishNegotiation(protoVersion) } // negotiateOutboundProtocol performs the negotiation protocol for an outbound @@ -2123,8 +2247,11 @@ func (p *Peer) negotiateInboundProtocol() error { // // 1. We send our version. // 2. Remote peer sends their version. -// 3. Remote peer sends their verack. +// 3. We send sendaddrv2 if their version is >= 70016. // 4. We send our verack. +// 5. We wait to receive sendaddrv2 or verack, skipping unknown messages as +// in the inbound case. +// 6. If sendaddrv2 was received, wait for receipt of verack. func (p *Peer) negotiateOutboundProtocol() error { if err := p.writeLocalVersionMsg(); err != nil { return err @@ -2134,11 +2261,22 @@ func (p *Peer) negotiateOutboundProtocol() error { return err } - if err := p.readRemoteVerAckMsg(); err != nil { + var protoVersion uint32 + p.flagsMtx.Lock() + protoVersion = p.protocolVersion + p.flagsMtx.Unlock() + + if err := p.writeSendAddrV2Msg(protoVersion); err != nil { + return err + } + + err := p.writeMessage(wire.NewMsgVerAck(), wire.LatestEncoding) + if err != nil { return err } - return p.writeMessage(wire.NewMsgVerAck(), wire.LatestEncoding) + // Finish the negotiation by waiting for negotiable messages or verack. + return p.waitToFinishNegotiation(protoVersion) } // start begins processing input and output messages. @@ -2201,7 +2339,12 @@ func (p *Peer) AssociateConnection(conn net.Conn) { p.Disconnect() return } - p.na = na + + // Convert the NetAddress created above into NetAddressV2. + currentNa := wire.NetAddressV2FromBytes( + na.Timestamp, na.Services, na.IP, na.Port, + ) + p.na = currentNa } go func() { @@ -2267,7 +2410,10 @@ func NewInboundPeer(cfg *Config) *Peer { return newPeerBase(cfg, true) } -// NewOutboundPeer returns a new outbound bitcoin peer. +// NewOutboundPeer returns a new outbound bitcoin peer. If the Config argument +// does not set HostToNetAddress, connecting to anything other than an ipv4 or +// ipv6 address will fail and may cause a nil-pointer-dereference. This +// includes hostnames and onion services. func NewOutboundPeer(cfg *Config, addr string) (*Peer, error) { p := newPeerBase(cfg, false) p.addr = addr @@ -2289,7 +2435,12 @@ func NewOutboundPeer(cfg *Config, addr string) (*Peer, error) { } p.na = na } else { - p.na = wire.NewNetAddressIPPort(net.ParseIP(host), uint16(port), 0) + // If host is an onion hidden service or a hostname, it is + // likely that a nil-pointer-dereference will occur. The caller + // should set HostToNetAddress if connecting to these. + p.na = wire.NetAddressV2FromBytes( + time.Now(), 0, net.ParseIP(host), uint16(port), + ) } return p, nil diff --git a/peer/peer_test.go b/peer/peer_test.go index dd7f36aa3a..9df90c233d 100644 --- a/peer/peer_test.go +++ b/peer/peer_test.go @@ -289,18 +289,16 @@ func TestPeerConnection(t *testing.T) { { "basic handshake", func() (*peer.Peer, *peer.Peer, error) { - inConn, outConn := pipe( - &conn{raddr: "10.0.0.1:8333"}, - &conn{raddr: "10.0.0.2:8333"}, - ) inPeer := peer.NewInboundPeer(peer1Cfg) - inPeer.AssociateConnection(inConn) - outPeer, err := peer.NewOutboundPeer(peer2Cfg, "10.0.0.2:8333") if err != nil { return nil, nil, err } - outPeer.AssociateConnection(outConn) + + err = setupPeerConnection(inPeer, outPeer) + if err != nil { + return nil, nil, err + } for i := 0; i < 4; i++ { select { @@ -315,18 +313,16 @@ func TestPeerConnection(t *testing.T) { { "socks proxy", func() (*peer.Peer, *peer.Peer, error) { - inConn, outConn := pipe( - &conn{raddr: "10.0.0.1:8333", proxy: true}, - &conn{raddr: "10.0.0.2:8333"}, - ) inPeer := peer.NewInboundPeer(peer1Cfg) - inPeer.AssociateConnection(inConn) - outPeer, err := peer.NewOutboundPeer(peer2Cfg, "10.0.0.2:8333") if err != nil { return nil, nil, err } - outPeer.AssociateConnection(outConn) + + err = setupPeerConnection(inPeer, outPeer) + if err != nil { + return nil, nil, err + } for i := 0; i < 4; i++ { select { @@ -359,7 +355,7 @@ func TestPeerConnection(t *testing.T) { // TestPeerListeners tests that the peer listeners are called as expected. func TestPeerListeners(t *testing.T) { verack := make(chan struct{}, 1) - ok := make(chan wire.Message, 20) + ok := make(chan wire.Message, 22) peerCfg := &peer.Config{ Listeners: peer.MessageListeners{ OnGetAddr: func(p *peer.Peer, msg *wire.MsgGetAddr) { @@ -447,6 +443,12 @@ func TestPeerListeners(t *testing.T) { OnSendHeaders: func(p *peer.Peer, msg *wire.MsgSendHeaders) { ok <- msg }, + OnSendAddrV2: func(p *peer.Peer, msg *wire.MsgSendAddrV2) { + ok <- msg + }, + OnAddrV2: func(p *peer.Peer, msg *wire.MsgAddrV2) { + ok <- msg + }, }, UserAgentName: "peer", UserAgentVersion: "1.0", @@ -456,12 +458,7 @@ func TestPeerListeners(t *testing.T) { TrickleInterval: time.Second * 10, AllowSelfConns: true, } - inConn, outConn := pipe( - &conn{raddr: "10.0.0.1:8333"}, - &conn{raddr: "10.0.0.2:8333"}, - ) inPeer := peer.NewInboundPeer(peerCfg) - inPeer.AssociateConnection(inConn) peerCfg.Listeners = peer.MessageListeners{ OnVerAck: func(p *peer.Peer, msg *wire.MsgVerAck) { @@ -473,7 +470,12 @@ func TestPeerListeners(t *testing.T) { t.Errorf("NewOutboundPeer: unexpected err %v\n", err) return } - outPeer.AssociateConnection(outConn) + + err = setupPeerConnection(inPeer, outPeer) + if err != nil { + t.Errorf("setupPeerConnection: failed: %v\n", err) + return + } for i := 0; i < 2; i++ { select { @@ -597,6 +599,14 @@ func TestPeerListeners(t *testing.T) { "OnSendHeaders", wire.NewMsgSendHeaders(), }, + { + "OnSendAddrV2", + wire.NewMsgSendAddrV2(), + }, + { + "OnAddrV2", + wire.NewMsgAddrV2(), + }, } t.Logf("Running %d tests", len(tests)) for _, test := range tests { @@ -881,17 +891,17 @@ func TestDuplicateVersionMsg(t *testing.T) { Services: 0, AllowSelfConns: true, } - inConn, outConn := pipe( - &conn{laddr: "10.0.0.1:9108", raddr: "10.0.0.2:9108"}, - &conn{laddr: "10.0.0.2:9108", raddr: "10.0.0.1:9108"}, - ) - outPeer, err := peer.NewOutboundPeer(peerCfg, inConn.laddr) + outPeer, err := peer.NewOutboundPeer(peerCfg, "10.0.0.2:8333") if err != nil { t.Fatalf("NewOutboundPeer: unexpected err: %v\n", err) } - outPeer.AssociateConnection(outConn) inPeer := peer.NewInboundPeer(peerCfg) - inPeer.AssociateConnection(inConn) + + err = setupPeerConnection(inPeer, outPeer) + if err != nil { + t.Fatalf("setupPeerConnection failed to connect: %v\n", err) + } + // Wait for the veracks from the initial protocol version negotiation. for i := 0; i < 2; i++ { select { @@ -947,17 +957,16 @@ func TestUpdateLastBlockHeight(t *testing.T) { remotePeerCfg.NewestBlock = func() (*chainhash.Hash, int32, error) { return &chainhash.Hash{}, remotePeerHeight, nil } - inConn, outConn := pipe( - &conn{laddr: "10.0.0.1:9108", raddr: "10.0.0.2:9108"}, - &conn{laddr: "10.0.0.2:9108", raddr: "10.0.0.1:9108"}, - ) - localPeer, err := peer.NewOutboundPeer(&peerCfg, inConn.laddr) + localPeer, err := peer.NewOutboundPeer(&peerCfg, "10.0.0.2:8333") if err != nil { t.Fatalf("NewOutboundPeer: unexpected err: %v\n", err) } - localPeer.AssociateConnection(outConn) inPeer := peer.NewInboundPeer(&remotePeerCfg) - inPeer.AssociateConnection(inConn) + + err = setupPeerConnection(inPeer, localPeer) + if err != nil { + t.Fatalf("setupPeerConnection failed to connect: %v\n", err) + } // Wait for the veracks from the initial protocol version negotiation. for i := 0; i < 2; i++ { @@ -989,3 +998,214 @@ func TestUpdateLastBlockHeight(t *testing.T) { remotePeerHeight+1) } } + +// setupPeerConnection initiates a tcp connection between two peers. +func setupPeerConnection(in, out *peer.Peer) error { + // listenFunc is a function closure that listens for a tcp connection. + // The tcp connection will be the one the inbound peer uses. This will + // be run as a goroutine. + listenFunc := func(l *net.TCPListener, errChan chan error, + listenChan chan struct{}) { + + listenChan <- struct{}{} + + conn, err := l.Accept() + if err != nil { + errChan <- err + return + } + + in.AssociateConnection(conn) + errChan <- nil + } + + // dialFunc is a function closure that initiates the tcp connection. + // The tcp connection will be the one the outbound peer uses. + dialFunc := func(addr *net.TCPAddr) error { + conn, err := net.Dial("tcp", addr.String()) + if err != nil { + return err + } + + out.AssociateConnection(conn) + return nil + } + + listenAddr := "localhost:0" + + addr, err := net.ResolveTCPAddr("tcp", listenAddr) + if err != nil { + return err + } + + l, err := net.ListenTCP("tcp", addr) + if err != nil { + return err + } + + errChan := make(chan error, 1) + listenChan := make(chan struct{}, 1) + + go listenFunc(l, errChan, listenChan) + <-listenChan + + if err := dialFunc(l.Addr().(*net.TCPAddr)); err != nil { + return err + } + + select { + case err = <-errChan: + return err + case <-time.After(time.Second * 2): + return errors.New("failed to create connection") + } +} + +// TestSendAddrV2Handshake tests that the version-verack handshake with the +// addition of the sendaddrv2 message works as expected. +func TestSendAddrV2Handshake(t *testing.T) { + verack := make(chan struct{}, 2) + sendaddr := make(chan struct{}, 2) + peer1Cfg := &peer.Config{ + Listeners: peer.MessageListeners{ + OnVerAck: func(p *peer.Peer, msg *wire.MsgVerAck) { + verack <- struct{}{} + }, + OnSendAddrV2: func(p *peer.Peer, + msg *wire.MsgSendAddrV2) { + + sendaddr <- struct{}{} + }, + }, + AllowSelfConns: true, + ChainParams: &chaincfg.MainNetParams, + } + + peer2Cfg := &peer.Config{ + Listeners: peer1Cfg.Listeners, + AllowSelfConns: true, + ChainParams: &chaincfg.MainNetParams, + } + + verackErr := errors.New("verack timeout") + + tests := []struct { + name string + expectsV2 bool + setup func() (*peer.Peer, *peer.Peer, error) + }{ + { + "successful sendaddrv2 handshake", + true, + func() (*peer.Peer, *peer.Peer, error) { + inPeer := peer.NewInboundPeer(peer1Cfg) + outPeer, err := peer.NewOutboundPeer( + peer2Cfg, "10.0.0.2:8333", + ) + if err != nil { + return nil, nil, err + } + + err = setupPeerConnection(inPeer, outPeer) + if err != nil { + return nil, nil, err + } + + for i := 0; i < 4; i++ { + select { + case <-sendaddr: + case <-verack: + case <-time.After(time.Second * 2): + return nil, nil, verackErr + } + } + + return inPeer, outPeer, nil + }, + }, + { + "handshake with legacy inbound peer", + false, + func() (*peer.Peer, *peer.Peer, error) { + legacyVersion := wire.AddrV2Version - 1 + peer1Cfg.ProtocolVersion = legacyVersion + inPeer := peer.NewInboundPeer(peer1Cfg) + outPeer, err := peer.NewOutboundPeer( + peer2Cfg, "10.0.0.2:8333", + ) + if err != nil { + return nil, nil, err + } + + err = setupPeerConnection(inPeer, outPeer) + if err != nil { + return nil, nil, err + } + + for i := 0; i < 2; i++ { + select { + case <-verack: + case <-time.After(time.Second * 2): + return nil, nil, verackErr + } + } + + return inPeer, outPeer, nil + }, + }, + { + "handshake with legacy outbound peer", + false, + func() (*peer.Peer, *peer.Peer, error) { + inPeer := peer.NewInboundPeer(peer1Cfg) + legacyVersion := wire.AddrV2Version - 1 + peer2Cfg.ProtocolVersion = legacyVersion + outPeer, err := peer.NewOutboundPeer( + peer2Cfg, "10.0.0.2:8333", + ) + if err != nil { + return nil, nil, err + } + + err = setupPeerConnection(inPeer, outPeer) + if err != nil { + return nil, nil, err + } + + for i := 0; i < 2; i++ { + select { + case <-verack: + case <-time.After(time.Second * 2): + return nil, nil, verackErr + } + } + + return inPeer, outPeer, nil + }, + }, + } + + t.Logf("Running %d tests", len(tests)) + for i, test := range tests { + inPeer, outPeer, err := test.setup() + if err != nil { + t.Fatalf("TestSendAddrV2Handshake setup #%d: "+ + "unexpected err: %v", i, err) + } + + if inPeer.WantsAddrV2() != test.expectsV2 { + t.Fatalf("TestSendAddrV2Handshake #%d expected "+ + "wantsAddrV2 to be %v instead was %v", i, + test.expectsV2, inPeer.WantsAddrV2()) + } else if outPeer.WantsAddrV2() != test.expectsV2 { + t.Fatalf("TestSendAddrV2Handshake #%d expected "+ + "wantsAddrV2 to be %v instead was %v", i, + test.expectsV2, outPeer.WantsAddrV2()) + } + + inPeer.Disconnect() + outPeer.Disconnect() + inPeer.WaitForDisconnect() + outPeer.WaitForDisconnect() + } +} diff --git a/server.go b/server.go index 2b5cc0ff42..5cef434292 100644 --- a/server.go +++ b/server.go @@ -344,6 +344,34 @@ func (sp *serverPeer) relayTxDisabled() bool { // pushAddrMsg sends a legacy addr message to the connected peer using the // provided addresses. func (sp *serverPeer) pushAddrMsg(addresses []*wire.NetAddressV2) { + if sp.WantsAddrV2() { + // If the peer supports addrv2, we'll be pushing an addrv2 + // message instead. The logic is otherwise identical to the + // addr case below. + addrs := make([]*wire.NetAddressV2, 0, len(addresses)) + for _, addr := range addresses { + // Filter addresses already known to the peer. + if sp.addressKnown(addr) { + continue + } + + addrs = append(addrs, addr) + } + + known, err := sp.PushAddrV2Msg(addrs) + if err != nil { + peerLog.Errorf("Can't push addrv2 message to %s: %v", + sp.Peer, err) + sp.Disconnect() + return + } + + // Add the final set of addresses sent to the set the peer + // knows of. + sp.addKnownAddresses(known) + return + } + addrs := make([]*wire.NetAddress, 0, len(addresses)) for _, addr := range addresses { // Filter addresses already known to the peer. @@ -1328,6 +1356,45 @@ func (sp *serverPeer) OnAddr(_ *peer.Peer, msg *wire.MsgAddr) { sp.server.addrManager.AddAddresses(addrs, sp.NA()) } +// OnAddrV2 is invoked when a peer receives an addrv2 bitcoin message and is +// used to notify the server about advertised addresses. +func (sp *serverPeer) OnAddrV2(_ *peer.Peer, msg *wire.MsgAddrV2) { + // Ignore if simnet for the same reasons as the regular addr message. + if cfg.SimNet { + return + } + + // An empty AddrV2 message is invalid. + if len(msg.AddrList) == 0 { + peerLog.Errorf("Command [%s] from %s does not contain any "+ + "addresses", msg.Command(), sp.Peer) + sp.Disconnect() + return + } + + for _, na := range msg.AddrList { + // Don't add more to the set of known addresses if we're + // disconnecting. + if !sp.Connected() { + return + } + + // Set the timestamp to 5 days ago if the timestamp received is + // more than 10 minutes in the future so this address is one of + // the first to be removed. + now := time.Now() + if na.Timestamp.After(now.Add(time.Minute * 10)) { + na.Timestamp = now.Add(-1 * time.Hour * 24 * 5) + } + + // Add to the set of known addresses. + sp.addKnownAddresses([]*wire.NetAddressV2{na}) + } + + // Add the addresses to the addrmanager. + sp.server.addrManager.AddAddresses(msg.AddrList, sp.NA()) +} + // OnRead is invoked when a peer receives a message and it is used to update // the bytes received by the server. func (sp *serverPeer) OnRead(_ *peer.Peer, bytesRead int, msg wire.Message, err error) { @@ -2074,6 +2141,7 @@ func newPeerConfig(sp *serverPeer) *peer.Config { OnFilterLoad: sp.OnFilterLoad, OnGetAddr: sp.OnGetAddr, OnAddr: sp.OnAddr, + OnAddrV2: sp.OnAddrV2, OnRead: sp.OnRead, OnWrite: sp.OnWrite, OnNotFound: sp.OnNotFound, diff --git a/wire/message.go b/wire/message.go index 6d3147a81d..1f412fa6fa 100644 --- a/wire/message.go +++ b/wire/message.go @@ -32,6 +32,7 @@ const ( CmdVerAck = "verack" CmdGetAddr = "getaddr" CmdAddr = "addr" + CmdAddrV2 = "addrv2" CmdGetBlocks = "getblocks" CmdInv = "inv" CmdGetData = "getdata" @@ -78,6 +79,13 @@ const ( // protocol. var LatestEncoding = WitnessEncoding +// ErrUnknownMessage is the error returned when decoding an unknown message. +var ErrUnknownMessage = fmt.Errorf("received unknown message") + +// ErrInvalidHandshake is the error returned when a peer sends us a known +// message that does not belong in the version-verack handshake. +var ErrInvalidHandshake = fmt.Errorf("invalid message during handshake") + // Message is an interface that describes a bitcoin message. A type that // implements Message has complete control over the representation of its data // and may therefore contain additional or fewer fields than those which @@ -109,6 +117,9 @@ func makeEmptyMessage(command string) (Message, error) { case CmdAddr: msg = &MsgAddr{} + case CmdAddrV2: + msg = &MsgAddrV2{} + case CmdGetBlocks: msg = &MsgGetBlocks{} @@ -185,7 +196,7 @@ func makeEmptyMessage(command string) (Message, error) { msg = &MsgCFCheckpt{} default: - return nil, fmt.Errorf("unhandled command [%s]", command) + return nil, ErrUnknownMessage } return msg, nil } @@ -378,9 +389,10 @@ func ReadMessageWithEncodingN(r io.Reader, pver uint32, btcnet BitcoinNet, // Create struct of appropriate message type based on the command. msg, err := makeEmptyMessage(command) if err != nil { + // makeEmptyMessage can only return ErrUnknownMessage and it is + // important that we bubble it up to the caller. discardInput(r, hdr.length) - return totalBytes, nil, nil, messageError("ReadMessage", - err.Error()) + return totalBytes, nil, nil, err } // Check for maximum length based on the message type as a malicious client diff --git a/wire/message_test.go b/wire/message_test.go index 3a422e66ba..7ba2e0639f 100644 --- a/wire/message_test.go +++ b/wire/message_test.go @@ -295,7 +295,7 @@ func TestReadMessageWireErrors(t *testing.T) { pver, btcnet, len(unsupportedCommandBytes), - &MessageError{}, + ErrUnknownMessage, 24, }, @@ -345,7 +345,7 @@ func TestReadMessageWireErrors(t *testing.T) { pver, btcnet, len(discardBytes), - &MessageError{}, + ErrUnknownMessage, 24, }, } diff --git a/wire/msgaddrv2.go b/wire/msgaddrv2.go new file mode 100644 index 0000000000..4db4a1334a --- /dev/null +++ b/wire/msgaddrv2.go @@ -0,0 +1,102 @@ +package wire + +import ( + "fmt" + "io" +) + +// MaxV2AddrPerMsg is the maximum number of version 2 addresses that will exist +// in a single addrv2 message (MsgAddrV2). +const MaxV2AddrPerMsg = 1000 + +// MsgAddrV2 implements the Message interface and represents a bitcoin addrv2 +// message that can support longer-length addresses like torv3, cjdns, and i2p. +// It is used to gossip addresses on the network. Each message is limited to +// MaxV2AddrPerMsg addresses. This is the same limit as MsgAddr. +type MsgAddrV2 struct { + AddrList []*NetAddressV2 +} + +// BtcDecode decodes r using the bitcoin protocol into a MsgAddrV2. +func (m *MsgAddrV2) BtcDecode(r io.Reader, pver uint32, + enc MessageEncoding) error { + + count, err := ReadVarInt(r, pver) + if err != nil { + return err + } + + // Limit to max addresses per message. + if count > MaxV2AddrPerMsg { + str := fmt.Sprintf("too many addresses for message [count %v,"+ + " max %v]", count, MaxV2AddrPerMsg) + return messageError("MsgAddrV2.BtcDecode", str) + } + + addrList := make([]NetAddressV2, count) + m.AddrList = make([]*NetAddressV2, 0, count) + for i := uint64(0); i < count; i++ { + na := &addrList[i] + err := readNetAddressV2(r, pver, na) + switch err { + case ErrSkippedNetworkID: + // This may be a network ID we don't know of, but is + // still valid. We can safely skip those. + continue + case ErrInvalidAddressSize: + // The encoding used by the peer does not follow + // BIP-155 and we should stop processing this message. + return err + } + + m.AddrList = append(m.AddrList, na) + } + + return nil +} + +// BtcEncode encodes the MsgAddrV2 into a writer w. +func (m *MsgAddrV2) BtcEncode(w io.Writer, pver uint32, + enc MessageEncoding) error { + + count := len(m.AddrList) + if count > MaxV2AddrPerMsg { + str := fmt.Sprintf("too many addresses for message [count %v,"+ + " max %v]", count, MaxV2AddrPerMsg) + return messageError("MsgAddrV2.BtcEncode", str) + } + + err := WriteVarInt(w, pver, uint64(count)) + if err != nil { + return err + } + + for _, na := range m.AddrList { + err = writeNetAddressV2(w, pver, na) + if err != nil { + return err + } + } + + return nil +} + +// Command returns the protocol command string for MsgAddrV2. +func (m *MsgAddrV2) Command() string { + return CmdAddrV2 +} + +// MaxPayloadLength returns the maximum length payload possible for MsgAddrV2. +func (m *MsgAddrV2) MaxPayloadLength(pver uint32) uint32 { + // The varint that can store the maximum number of addresses is 3 bytes + // long. The maximum payload is then 3 + 1000 * maxNetAddressV2Payload. + return 3 + (MaxV2AddrPerMsg * maxNetAddressV2Payload()) +} + +// NewMsgAddrV2 returns a new bitcoin addrv2 message that conforms to the +// Message interface. +func NewMsgAddrV2() *MsgAddrV2 { + return &MsgAddrV2{ + AddrList: make([]*NetAddressV2, 0, MaxV2AddrPerMsg), + } +} diff --git a/wire/msgaddrv2_test.go b/wire/msgaddrv2_test.go new file mode 100644 index 0000000000..213d699c96 --- /dev/null +++ b/wire/msgaddrv2_test.go @@ -0,0 +1,73 @@ +package wire + +import ( + "bytes" + "io" + "testing" +) + +// TestAddrV2Decode checks that decoding an addrv2 message off the wire behaves +// as expected. This means ignoring certain addresses, and failing in certain +// failure scenarios. +func TestAddrV2Decode(t *testing.T) { + tests := []struct { + buf []byte + expectedError bool + expectedAddrs int + }{ + // Exceeding max addresses. + { + []byte{0xfd, 0xff, 0xff}, + true, + 0, + }, + + // Invalid address size. + { + []byte{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x05}, + true, + 0, + }, + + // One valid address and one skipped address + { + []byte{ + 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x04, + 0x7f, 0x00, 0x00, 0x01, 0x22, 0x22, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x02, 0x10, 0xfd, 0x87, 0xd8, + 0x7e, 0xeb, 0x43, 0xff, 0xfe, 0xcc, 0x39, 0xa8, + 0x73, 0x69, 0x15, 0xff, 0xff, 0x22, 0x22, + }, + false, + 1, + }, + } + + t.Logf("Running %d tests", len(tests)) + for i, test := range tests { + r := bytes.NewReader(test.buf) + m := &MsgAddrV2{} + + err := m.BtcDecode(r, 0, LatestEncoding) + if test.expectedError { + if err == nil { + t.Errorf("Test #%d expected error", i) + } + + continue + } else if err != nil { + t.Errorf("Test #%d unexpected error %v", i, err) + } + + // Trying to read more should give EOF. + var b [1]byte + if _, err := r.Read(b[:]); err != io.EOF { + t.Errorf("Test #%d did not cleanly finish reading", i) + } + + if len(m.AddrList) != test.expectedAddrs { + t.Errorf("Test #%d expected %d addrs, instead of %d", + i, test.expectedAddrs, len(m.AddrList)) + } + } +} diff --git a/wire/protocol.go b/wire/protocol.go index 8cc9838a55..3b414ec3f1 100644 --- a/wire/protocol.go +++ b/wire/protocol.go @@ -13,7 +13,7 @@ import ( // XXX pedro: we will probably need to bump this. const ( // ProtocolVersion is the latest protocol version this package supports. - ProtocolVersion uint32 = 70013 + ProtocolVersion uint32 = 70016 // MultipleAddressVersion is the protocol version which added multiple // addresses per message (pver >= MultipleAddressVersion). @@ -51,6 +51,13 @@ const ( // FeeFilterVersion is the protocol version which added a new // feefilter message. FeeFilterVersion uint32 = 70013 + + // AddrV2Version is the protocol version which added two new messages. + // sendaddrv2 is sent during the version-verack handshake and signals + // support for sending and receiving the addrv2 message. In the future, + // new messages that occur during the version-verack handshake will not + // come with a protocol version bump. + AddrV2Version uint32 = 70016 ) // ServiceFlag identifies services supported by a bitcoin peer.