From 650a935a68f073013323491ea4e7143c4327f5d1 Mon Sep 17 00:00:00 2001 From: Sef Boukenken Date: Sat, 3 Apr 2021 23:26:40 -0400 Subject: [PATCH 1/9] addrmgr: Track network address types. This commit adds support for determining and tracking the type of a network address when constructed. --- addrmgr/addrmanager.go | 95 ++++++++++++------ addrmgr/error.go | 8 ++ addrmgr/knownaddress.go | 4 +- addrmgr/netaddress.go | 106 ++++++++++++++++++-- addrmgr/netaddress_test.go | 200 +++++++++++++++++++++++++++++-------- addrmgr/network.go | 23 +---- 6 files changed, 334 insertions(+), 102 deletions(-) diff --git a/addrmgr/addrmanager.go b/addrmgr/addrmanager.go index 49909b863d..3c44dec717 100644 --- a/addrmgr/addrmanager.go +++ b/addrmgr/addrmanager.go @@ -753,6 +753,38 @@ func (a *AddrManager) reset() { } } +// ParseHost deconstructs a given host to its respective []byte representation +// and also returns the network address type. If an error occurs while decoding +// an onion address, the error is returned. If the host cannot be decoded then +// an unknown address type is returned without error. +func ParseHost(host string) (NetAddressType, []byte, error) { + if strings.HasSuffix(host, ".onion") { + // Check if this is a TorV2 address. + if len(host) == 22 { + data, err := base32.StdEncoding.DecodeString( + strings.ToUpper(host[:16])) + if err != nil { + return UnknownAddressType, nil, err + } + prefix := []byte{0xfd, 0x87, 0xd8, 0x7e, 0xeb, 0x43} + addrBytes := append(prefix, data...) + return TORv2Address, addrBytes, nil + } + } + + if ip := net.ParseIP(host); ip != nil { + if isIPv4(ip) { + return IPv4Address, ip.To4(), nil + } + if isOnionCatTor(ip) { + return TORv2Address, ip, nil + } + return IPv6Address, ip, nil + } + + return UnknownAddressType, nil, nil +} + // HostToNetAddress parses and returns a network address given a hostname in a // supported format (IPv4, IPv6, TORv2). If the hostname cannot be immediately // converted from a known address format, it will be resolved using the lookup @@ -761,31 +793,24 @@ func (a *AddrManager) reset() { // // This function is safe for concurrent access. func (a *AddrManager) HostToNetAddress(host string, port uint16, services wire.ServiceFlag) (*NetAddress, error) { - // Tor address is 16 char base32 + ".onion" - var ip net.IP - if len(host) == 22 && host[16:] == ".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])) - if err != nil { - return nil, err - } - prefix := []byte{0xfd, 0x87, 0xd8, 0x7e, 0xeb, 0x43} - ip = net.IP(append(prefix, data...)) - } else if ip = net.ParseIP(host); ip == nil { - ips, err := a.lookupFunc(host) - if err != nil { - return nil, err - } - if len(ips) == 0 { - return nil, fmt.Errorf("no addresses found for %s", host) - } - ip = ips[0] + networkID, addrBytes, err := ParseHost(host) + if err != nil { + return nil, err } - - return NewNetAddressIPPort(ip, port, services), nil + if networkID != UnknownAddressType { + // Since the host has been successfully decoded, there is no need to + // perform a DNS lookup. + now := time.Unix(time.Now().Unix(), 0) + return NewNetAddressByType(networkID, addrBytes, port, now, services) + } + ips, err := a.lookupFunc(host) + if err != nil { + return nil, err + } + if len(ips) == 0 { + return nil, fmt.Errorf("no addresses found for host %s", host) + } + return NewNetAddressIPPort(ips[0], port, services), nil } // GetAddress returns a single address that should be routable. It picks a @@ -1129,12 +1154,12 @@ func getReachabilityFrom(localAddr, remoteAddr *NetAddress) NetAddressReach { return Unreachable } - if isOnionCatTor(remoteAddr.IP) { - if isOnionCatTor(localAddr.IP) { + if remoteAddr.Type == TORv2Address { + if localAddr.Type == TORv2Address { return Private } - if localAddr.IsRoutable() && isIPv4(localAddr.IP) { + if localAddr.IsRoutable() && localAddr.Type == IPv4Address { return Ipv4 } @@ -1150,15 +1175,15 @@ func getReachabilityFrom(localAddr, remoteAddr *NetAddress) NetAddressReach { return Teredo } - if isIPv4(localAddr.IP) { + if localAddr.Type == IPv4Address { return Ipv4 } return Ipv6Weak } - if isIPv4(remoteAddr.IP) { - if localAddr.IsRoutable() && isIPv4(localAddr.IP) { + if remoteAddr.Type == IPv4Address { + if localAddr.IsRoutable() && localAddr.Type == IPv4Address { return Ipv4 } return Unreachable @@ -1179,7 +1204,7 @@ func getReachabilityFrom(localAddr, remoteAddr *NetAddress) NetAddressReach { return Teredo } - if isIPv4(localAddr.IP) { + if localAddr.Type == IPv4Address { return Ipv4 } @@ -1219,7 +1244,7 @@ func (a *AddrManager) GetBestLocalAddress(remoteAddr *NetAddress) *NetAddress { // Send something unroutable if nothing suitable. var ip net.IP - if !isIPv4(remoteAddr.IP) && !isOnionCatTor(remoteAddr.IP) { + if remoteAddr.Type != IPv4Address && remoteAddr.Type != TORv2Address { ip = net.IPv6zero } else { ip = net.IPv4zero @@ -1236,8 +1261,12 @@ func (a *AddrManager) GetBestLocalAddress(remoteAddr *NetAddress) *NetAddress { // // This function is safe for concurrent access. func (a *AddrManager) ValidatePeerNa(localAddr, remoteAddr *NetAddress) (bool, NetAddressReach) { - net := addressType(localAddr.IP) + net := localAddr.Type reach := getReachabilityFrom(localAddr, remoteAddr) + if isLocal(localAddr.IP) { + return false, reach + } + valid := (net == IPv4Address && reach == Ipv4) || (net == IPv6Address && (reach == Ipv6Weak || reach == Ipv6Strong || reach == Teredo)) return valid, reach diff --git a/addrmgr/error.go b/addrmgr/error.go index 3aa0ef7536..aa3c47fcaa 100644 --- a/addrmgr/error.go +++ b/addrmgr/error.go @@ -14,6 +14,14 @@ const ( // ErrAddressNotFound indicates that an operation in the address manager // failed due to an address lookup failure. ErrAddressNotFound = ErrorKind("ErrAddressNotFound") + + // ErrUnknownAddressType indicates that the network type could not be + // determined from a network address' bytes. + ErrUnknownAddressType = ErrorKind("ErrUnknownAddressType") + + // ErrMismatchedAddressType indicates that the network type could not be + // determined from a network address' bytes. + ErrMismatchedAddressType = ErrorKind("ErrMismatchedAddressType") ) // Error satisfies the error interface and prints human-readable errors. diff --git a/addrmgr/knownaddress.go b/addrmgr/knownaddress.go index 5243385127..793742113a 100644 --- a/addrmgr/knownaddress.go +++ b/addrmgr/knownaddress.go @@ -41,8 +41,8 @@ type KnownAddress struct { refs int } -// NetAddress returns the underlying wire.NetAddress associated with the -// known address. +// NetAddress returns the underlying address manager network address associated +// with the known address. func (ka *KnownAddress) NetAddress() *NetAddress { ka.mtx.Lock() defer ka.mtx.Unlock() diff --git a/addrmgr/netaddress.go b/addrmgr/netaddress.go index c2d2ba4779..95f70306ac 100644 --- a/addrmgr/netaddress.go +++ b/addrmgr/netaddress.go @@ -6,6 +6,7 @@ package addrmgr import ( "encoding/base32" + "fmt" "net" "strconv" "strings" @@ -16,6 +17,9 @@ import ( // NetAddress defines information about a peer on the network. type NetAddress struct { + // Type represents the type of network that the network address belongs to. + Type NetAddressType + // IP address of the peer. It is defined as a byte array to support various // address types that are not standard to the net module and therefore not // entirely appropriate to store as a net.IP. @@ -43,8 +47,8 @@ func (netAddr *NetAddress) IsRoutable() bool { // port. func (netAddr *NetAddress) ipString() string { netIP := netAddr.IP - if isOnionCatTor(netIP) { - // We know now that na.IP is long enough. + switch netAddr.Type { + case TORv2Address: base32 := base32.StdEncoding.EncodeToString(netIP[6:]) return strings.ToLower(base32) + ".onion" } @@ -78,8 +82,79 @@ func (netAddr *NetAddress) AddService(service wire.ServiceFlag) { netAddr.Services |= service } -// newAddressFromString creates a new address manager network address from the -// provided string. The address is expected to be provided in the format +// canonicalizeIP converts the provided address' bytes into a standard structure +// based on the type of the network address, if applicable. +func canonicalizeIP(addrType NetAddressType, addrBytes []byte) []byte { + if addrBytes == nil { + return []byte{} + } + len := len(addrBytes) + switch { + case len == 16 && addrType == IPv4Address: + return net.IP(addrBytes).To4() + case len == 10 && addrType == TORv2Address: + prefix := []byte{0xfd, 0x87, 0xd8, 0x7e, 0xeb, 0x43} + return append(prefix, addrBytes...) + case addrType == IPv6Address: + return net.IP(addrBytes).To16() + } + return addrBytes +} + +// deriveNetAddressType attempts to determine the network address type from +// the address' raw bytes. If the type cannot be determined, an error is +// returned. +func deriveNetAddressType(addrBytes []byte) (NetAddressType, error) { + len := len(addrBytes) + switch { + case isIPv4(addrBytes): + return IPv4Address, nil + case len == 16 && isOnionCatTor(addrBytes): + return TORv2Address, nil + case len == 16: + return IPv6Address, nil + } + strErr := fmt.Sprintf("unable to determine address type from raw network "+ + "address bytes: %v", addrBytes) + return UnknownAddressType, makeError(ErrUnknownAddressType, strErr) +} + +// assertNetAddressTypeValid returns an error if the suggested address type does +// not appear to match the provided address. +func assertNetAddressTypeValid(netAddressType NetAddressType, addrBytes []byte) error { + derivedAddressType, err := deriveNetAddressType(addrBytes) + if err != nil { + return err + } + if netAddressType != derivedAddressType { + str := fmt.Sprintf("derived address type does not match expected value"+ + " (got %v, expected %v, address bytes %v).", derivedAddressType, + netAddressType, addrBytes) + return makeError(ErrMismatchedAddressType, str) + } + return nil +} + +// NewNetAddressByType creates a new network address using the provided +// parameters. If the provided network id does not appear to match the address, +// an error is returned. +func NewNetAddressByType(netAddressType NetAddressType, addrBytes []byte, port uint16, timestamp time.Time, services wire.ServiceFlag) (*NetAddress, error) { + canonicalizedIP := canonicalizeIP(netAddressType, addrBytes) + err := assertNetAddressTypeValid(netAddressType, canonicalizedIP) + if err != nil { + return nil, err + } + return &NetAddress{ + Type: netAddressType, + IP: canonicalizedIP, + Port: port, + Services: services, + Timestamp: timestamp, + }, nil +} + +// newAddressFromString creates a new address manager network address from +// the provided string. The address is expected to be provided in the format // host:port. func (a *AddrManager) newAddressFromString(addr string) (*NetAddress, error) { host, portStr, err := net.SplitHostPort(addr) @@ -91,15 +166,30 @@ func (a *AddrManager) newAddressFromString(addr string) (*NetAddress, error) { return nil, err } - return a.HostToNetAddress(host, uint16(port), wire.SFNodeNetwork) + networkID, addrBytes, err := ParseHost(host) + if err != nil { + return nil, err + } + if networkID == UnknownAddressType { + str := fmt.Sprintf("failed to deserialize address %s", addr) + return nil, makeError(ErrUnknownAddressType, str) + } + timestamp := time.Unix(time.Now().Unix(), 0) + return NewNetAddressByType(networkID, addrBytes, uint16(port), timestamp, + wire.SFNodeNetwork) } -// NewNetAddressIPPort creates a new address manager network address given an ip, -// port, and the supported service flags for the address. +// NewNetAddressIPPort creates a new address manager network address given an +// ip, port, and the supported service flags for the address. The provided ip +// MUST be an IPv4, IPv6, or TORv2 address since this method does not perform +// error checking on the derived network address type. func NewNetAddressIPPort(ip net.IP, port uint16, services wire.ServiceFlag) *NetAddress { + netAddressType, _ := deriveNetAddressType(ip) timestamp := time.Unix(time.Now().Unix(), 0) + canonicalizedIP := canonicalizeIP(netAddressType, ip) return &NetAddress{ - IP: ip, + Type: netAddressType, + IP: canonicalizedIP, Port: port, Services: services, Timestamp: timestamp, diff --git a/addrmgr/netaddress_test.go b/addrmgr/netaddress_test.go index 769196e99b..a3c0649b4c 100644 --- a/addrmgr/netaddress_test.go +++ b/addrmgr/netaddress_test.go @@ -9,81 +9,203 @@ import ( "reflect" "testing" + "time" + "github.com/decred/dcrd/wire" ) +// TestNewNetAddressByType verifies that the TestNewNetAddressByType constructor +// converts a network address with expected field values. +func TestNewNetAddressByType(t *testing.T) { + const port = 8345 + const services = wire.SFNodeNetwork + timestamp := time.Unix(time.Now().Unix(), 0) + + tests := []struct { + name string + addrType NetAddressType + addrBytes []byte + want *NetAddress + }{ + { + name: "4 byte ipv4 address stored as 4 byte ip", + addrType: IPv4Address, + addrBytes: net.ParseIP("127.0.0.1").To4(), + want: &NetAddress{ + IP: []byte{0x7f, 0x00, 0x00, 0x01}, + Port: port, + Services: services, + Timestamp: timestamp, + Type: IPv4Address, + }, + }, + { + name: "16 byte ipv4 address stored as 4 byte ip", + addrType: IPv4Address, + addrBytes: net.ParseIP("127.0.0.1").To16(), + want: &NetAddress{ + IP: []byte{0x7f, 0x00, 0x00, 0x01}, + Port: port, + Services: services, + Timestamp: timestamp, + Type: IPv4Address, + }, + }, + { + name: "16 byte ipv6 address stored in 16 bytes", + addrType: IPv6Address, + addrBytes: net.ParseIP("::1"), + want: &NetAddress{ + IP: []byte{ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01}, + Port: port, + Services: services, + Timestamp: timestamp, + Type: IPv6Address, + }, + }, + { + name: "16 byte torv2 address stored in 16 bytes", + addrType: TORv2Address, + addrBytes: net.ParseIP("fd87:d87e:eb43::"), + want: &NetAddress{ + IP: []byte{ + 0xfd, 0x87, 0xd8, 0x7e, 0xeb, 0x43, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + }, + Port: port, + Services: services, + Timestamp: timestamp, + Type: TORv2Address, + }, + }, + { + name: "10 byte torv2 public key stored in 16 bytes with prefix", + addrType: TORv2Address, + addrBytes: []byte{ + 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, + }, + want: &NetAddress{ + IP: []byte{ + 0xfd, 0x87, 0xd8, 0x7e, 0xeb, 0x43, 0x01, 0x02, + 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, + }, + Port: port, + Services: services, + Timestamp: timestamp, + Type: TORv2Address, + }, + }, + } + + for _, test := range tests { + addr, err := NewNetAddressByType(test.addrType, test.addrBytes, port, + timestamp, services) + if err != nil { + t.Fatalf("%q: unexpected error - %v", test.name, err) + } + if !reflect.DeepEqual(addr, test.want) { + t.Errorf("%q: mismatched entries\ngot %+v\nwant %+v", test.name, + addr, test.want) + } + } +} + // TestKey verifies that Key converts a network address to an expected string // value. func TestKey(t *testing.T) { tests := []struct { - ip string + host string port uint16 want string }{ // IPv4 // Localhost - {ip: "127.0.0.1", port: 8333, want: "127.0.0.1:8333"}, - {ip: "127.0.0.1", port: 8334, want: "127.0.0.1:8334"}, + {host: "127.0.0.1", port: 8333, want: "127.0.0.1:8333"}, + {host: "127.0.0.1", port: 8334, want: "127.0.0.1:8334"}, // Class A - {ip: "1.0.0.1", port: 8333, want: "1.0.0.1:8333"}, - {ip: "2.2.2.2", port: 8334, want: "2.2.2.2:8334"}, - {ip: "27.253.252.251", port: 8335, want: "27.253.252.251:8335"}, - {ip: "123.3.2.1", port: 8336, want: "123.3.2.1:8336"}, + {host: "1.0.0.1", port: 8333, want: "1.0.0.1:8333"}, + {host: "2.2.2.2", port: 8334, want: "2.2.2.2:8334"}, + {host: "27.253.252.251", port: 8335, want: "27.253.252.251:8335"}, + {host: "123.3.2.1", port: 8336, want: "123.3.2.1:8336"}, // Private Class A - {ip: "10.0.0.1", port: 8333, want: "10.0.0.1:8333"}, - {ip: "10.1.1.1", port: 8334, want: "10.1.1.1:8334"}, - {ip: "10.2.2.2", port: 8335, want: "10.2.2.2:8335"}, - {ip: "10.10.10.10", port: 8336, want: "10.10.10.10:8336"}, + {host: "10.0.0.1", port: 8333, want: "10.0.0.1:8333"}, + {host: "10.1.1.1", port: 8334, want: "10.1.1.1:8334"}, + {host: "10.2.2.2", port: 8335, want: "10.2.2.2:8335"}, + {host: "10.10.10.10", port: 8336, want: "10.10.10.10:8336"}, // Class B - {ip: "128.0.0.1", port: 8333, want: "128.0.0.1:8333"}, - {ip: "129.1.1.1", port: 8334, want: "129.1.1.1:8334"}, - {ip: "180.2.2.2", port: 8335, want: "180.2.2.2:8335"}, - {ip: "191.10.10.10", port: 8336, want: "191.10.10.10:8336"}, + {host: "128.0.0.1", port: 8333, want: "128.0.0.1:8333"}, + {host: "129.1.1.1", port: 8334, want: "129.1.1.1:8334"}, + {host: "180.2.2.2", port: 8335, want: "180.2.2.2:8335"}, + {host: "191.10.10.10", port: 8336, want: "191.10.10.10:8336"}, // Private Class B - {ip: "172.16.0.1", port: 8333, want: "172.16.0.1:8333"}, - {ip: "172.16.1.1", port: 8334, want: "172.16.1.1:8334"}, - {ip: "172.16.2.2", port: 8335, want: "172.16.2.2:8335"}, - {ip: "172.16.172.172", port: 8336, want: "172.16.172.172:8336"}, + {host: "172.16.0.1", port: 8333, want: "172.16.0.1:8333"}, + {host: "172.16.1.1", port: 8334, want: "172.16.1.1:8334"}, + {host: "172.16.2.2", port: 8335, want: "172.16.2.2:8335"}, + {host: "172.16.172.172", port: 8336, want: "172.16.172.172:8336"}, // Class C - {ip: "193.0.0.1", port: 8333, want: "193.0.0.1:8333"}, - {ip: "200.1.1.1", port: 8334, want: "200.1.1.1:8334"}, - {ip: "205.2.2.2", port: 8335, want: "205.2.2.2:8335"}, - {ip: "223.10.10.10", port: 8336, want: "223.10.10.10:8336"}, + {host: "193.0.0.1", port: 8333, want: "193.0.0.1:8333"}, + {host: "200.1.1.1", port: 8334, want: "200.1.1.1:8334"}, + {host: "205.2.2.2", port: 8335, want: "205.2.2.2:8335"}, + {host: "223.10.10.10", port: 8336, want: "223.10.10.10:8336"}, // Private Class C - {ip: "192.168.0.1", port: 8333, want: "192.168.0.1:8333"}, - {ip: "192.168.1.1", port: 8334, want: "192.168.1.1:8334"}, - {ip: "192.168.2.2", port: 8335, want: "192.168.2.2:8335"}, - {ip: "192.168.192.192", port: 8336, want: "192.168.192.192:8336"}, + {host: "192.168.0.1", port: 8333, want: "192.168.0.1:8333"}, + {host: "192.168.1.1", port: 8334, want: "192.168.1.1:8334"}, + {host: "192.168.2.2", port: 8335, want: "192.168.2.2:8335"}, + {host: "192.168.192.192", port: 8336, want: "192.168.192.192:8336"}, // IPv6 // Localhost - {ip: "::1", port: 8333, want: "[::1]:8333"}, - {ip: "fe80::1", port: 8334, want: "[fe80::1]:8334"}, + {host: "::1", port: 8333, want: "[::1]:8333"}, + {host: "fe80::1", port: 8334, want: "[fe80::1]:8334"}, // Link-local - {ip: "fe80::1:1", port: 8333, want: "[fe80::1:1]:8333"}, - {ip: "fe91::2:2", port: 8334, want: "[fe91::2:2]:8334"}, - {ip: "fea2::3:3", port: 8335, want: "[fea2::3:3]:8335"}, - {ip: "feb3::4:4", port: 8336, want: "[feb3::4:4]:8336"}, + {host: "fe80::1:1", port: 8333, want: "[fe80::1:1]:8333"}, + {host: "fe91::2:2", port: 8334, want: "[fe91::2:2]:8334"}, + {host: "fea2::3:3", port: 8335, want: "[fea2::3:3]:8335"}, + {host: "feb3::4:4", port: 8336, want: "[feb3::4:4]:8336"}, // Site-local - {ip: "fec0::1:1", port: 8333, want: "[fec0::1:1]:8333"}, - {ip: "fed1::2:2", port: 8334, want: "[fed1::2:2]:8334"}, - {ip: "fee2::3:3", port: 8335, want: "[fee2::3:3]:8335"}, - {ip: "fef3::4:4", port: 8336, want: "[fef3::4:4]:8336"}, + {host: "fec0::1:1", port: 8333, want: "[fec0::1:1]:8333"}, + {host: "fed1::2:2", port: 8334, want: "[fed1::2:2]:8334"}, + {host: "fee2::3:3", port: 8335, want: "[fee2::3:3]:8335"}, + {host: "fef3::4:4", port: 8336, want: "[fef3::4:4]:8336"}, // TORv2 - {ip: "fd87:d87e:eb43::", port: 8333, want: "aaaaaaaaaaaaaaaa.onion:8333"}, + { + host: "fd87:d87e:eb43::", + port: 8333, + want: "aaaaaaaaaaaaaaaa.onion:8333", + }, + { + host: "aaaaaaaaaaaaaaaa.onion", + port: 8334, + want: "aaaaaaaaaaaaaaaa.onion:8334", + }, } + timeNow := time.Now() for _, test := range tests { - netAddr := NewNetAddressIPPort(net.ParseIP(test.ip), test.port, wire.SFNodeNetwork) + host := test.host + addrType, addrBytes, err := ParseHost(host) + if err != nil { + t.Fatalf("failed to decode host %s: %v", host, err) + } + + netAddr, err := NewNetAddressByType(addrType, addrBytes, test.port, + timeNow, wire.SFNodeNetwork) + if err != nil { + t.Fatalf("failed to construct network address from host %q: %v", + host, err) + } + key := netAddr.Key() if key != test.want { t.Errorf("unexpected network address key -- got %s, want %s", diff --git a/addrmgr/network.go b/addrmgr/network.go index 8d5720da7d..6321058e25 100644 --- a/addrmgr/network.go +++ b/addrmgr/network.go @@ -121,29 +121,12 @@ func isOnionCatTor(netIP net.IP) bool { type NetAddressType uint8 const ( - LocalAddress NetAddressType = iota + UnknownAddressType NetAddressType = iota IPv4Address IPv6Address TORv2Address ) -// addressType returns the network address type of the provided network address. -func addressType(netIP net.IP) NetAddressType { - switch { - case isLocal(netIP): - return LocalAddress - - case isIPv4(netIP): - return IPv4Address - - case isOnionCatTor(netIP): - return TORv2Address - - default: - return IPv6Address - } -} - // isRFC1918 returns whether or not the passed address is part of the IPv4 // private network address space as defined by RFC1918 (10.0.0.0/8, // 172.16.0.0/12, or 192.168.0.0/16). @@ -269,7 +252,7 @@ func (na *NetAddress) GroupKey() string { if !IsRoutable(netIP) { return "unroutable" } - if isIPv4(netIP) { + if na.Type == IPv4Address { return netIP.Mask(net.CIDRMask(16, 32)).String() } if isRFC6145(netIP) || isRFC6052(netIP) { @@ -291,7 +274,7 @@ func (na *NetAddress) GroupKey() string { } return newIP.Mask(net.CIDRMask(16, 32)).String() } - if isOnionCatTor(netIP) { + if na.Type == TORv2Address { // group is keyed off the first 4 bits of the actual onion key. return fmt.Sprintf("tor:%d", netIP[6]&((1<<4)-1)) } From 77e2118851d9f2d875fb4561351fe2d6888e1040 Mon Sep 17 00:00:00 2001 From: Sef Boukenken Date: Sun, 4 Apr 2021 01:32:44 -0400 Subject: [PATCH 2/9] addrmgr: Add network address type filter. This commit introduces a network address type filter that allows a caller to indicate the types of addresses that can be returned from the address manager. This allows finer control of what types of addresses may be broadcast to a particular peer. The filters are decided by the protocol version of the peer in a way that allows the address manager's internal implementation to remain agnostic of the protocol versioning logic. --- addrmgr/addrmanager.go | 15 ++++++++++++--- addrmgr/addrmanager_test.go | 14 ++++++++++---- addrmgr/network.go | 6 ++++++ server.go | 35 ++++++++++++++++++++++++++++++----- 4 files changed, 58 insertions(+), 12 deletions(-) diff --git a/addrmgr/addrmanager.go b/addrmgr/addrmanager.go index 3c44dec717..a36abe6404 100644 --- a/addrmgr/addrmanager.go +++ b/addrmgr/addrmanager.go @@ -684,10 +684,11 @@ func (a *AddrManager) NeedMoreAddresses() bool { return a.numAddresses() < needAddressThreshold } -// AddressCache returns a randomized subset of all known addresses. +// AddressCache returns a randomized subset of all addresses known to the +// address manager with a network address type matching the provided type flags. // // This function is safe for concurrent access. -func (a *AddrManager) AddressCache() []*NetAddress { +func (a *AddrManager) AddressCache(supportedNetAddressType NetAddressTypeFilter) []*NetAddress { a.mtx.Lock() defer a.mtx.Unlock() @@ -700,6 +701,10 @@ func (a *AddrManager) AddressCache() []*NetAddress { allAddr := make([]*NetAddress, 0, addrLen) // Iteration order is undefined here, but we randomize it anyway. for _, v := range a.addrIndex { + // Skip address types not requested by the caller. + if !supportedNetAddressType(v.na.Type) { + continue + } // Skip low quality addresses. if v.isBad() { continue @@ -1220,7 +1225,7 @@ func getReachabilityFrom(localAddr, remoteAddr *NetAddress) NetAddressReach { // for the given remote address. // // This function is safe for concurrent access. -func (a *AddrManager) GetBestLocalAddress(remoteAddr *NetAddress) *NetAddress { +func (a *AddrManager) GetBestLocalAddress(remoteAddr *NetAddress, supportedNetAddressType NetAddressTypeFilter) *NetAddress { a.lamtx.Lock() defer a.lamtx.Unlock() @@ -1228,6 +1233,10 @@ func (a *AddrManager) GetBestLocalAddress(remoteAddr *NetAddress) *NetAddress { var bestscore AddressPriority var bestAddress *NetAddress for _, la := range a.localAddresses { + // Only return addresses matching the type flags provided by the caller. + if !supportedNetAddressType(la.na.Type) { + continue + } reach := getReachabilityFrom(la.na, remoteAddr) if reach > bestreach || (reach == bestreach && la.score > bestscore) { diff --git a/addrmgr/addrmanager_test.go b/addrmgr/addrmanager_test.go index 5f01929f65..46a4d8fc37 100644 --- a/addrmgr/addrmanager_test.go +++ b/addrmgr/addrmanager_test.go @@ -21,6 +21,12 @@ import ( // Put some IP in here for convenience. Points to google. var someIP = "173.194.115.66" +// defaultNetAddressTypeFilter defines a filter that instructs address manager +// operations that accept it to return network addresses of any type. +func defaultNetAddressTypeFilter(netAddressType NetAddressType) bool { + return true +} + func lookupFunc(host string) ([]net.IP, error) { return nil, errors.New("not implemented") } @@ -341,7 +347,7 @@ func TestGood(t *testing.T) { addrsToAdd) } - numCache := len(n.AddressCache()) + numCache := len(n.AddressCache(defaultNetAddressTypeFilter)) if numCache >= numAddrs/4 { t.Fatalf("Number of addresses in cache: got %d, want %d", numCache, numAddrs/4) @@ -536,7 +542,7 @@ func TestGetBestLocalAddress(t *testing.T) { // Test against default when there's no address for x, test := range tests { - got := amgr.GetBestLocalAddress(test.remoteAddr) + got := amgr.GetBestLocalAddress(test.remoteAddr, defaultNetAddressTypeFilter) if !reflect.DeepEqual(test.want0.IP, got.IP) { t.Errorf("TestGetBestLocalAddress test1 #%d failed for remote address %s: want %s got %s", x, test.remoteAddr.IP, test.want1.IP, got.IP) @@ -550,7 +556,7 @@ func TestGetBestLocalAddress(t *testing.T) { // Test against want1 for x, test := range tests { - got := amgr.GetBestLocalAddress(test.remoteAddr) + got := amgr.GetBestLocalAddress(test.remoteAddr, defaultNetAddressTypeFilter) if !reflect.DeepEqual(test.want1.IP, got.IP) { t.Errorf("TestGetBestLocalAddress test1 #%d failed for remote address %s: want %s got %s", x, test.remoteAddr.IP, test.want1.IP, got.IP) @@ -564,7 +570,7 @@ func TestGetBestLocalAddress(t *testing.T) { // Test against want2 for x, test := range tests { - got := amgr.GetBestLocalAddress(test.remoteAddr) + got := amgr.GetBestLocalAddress(test.remoteAddr, defaultNetAddressTypeFilter) if !reflect.DeepEqual(test.want2.IP, got.IP) { t.Errorf("TestGetBestLocalAddress test2 #%d failed for remote address %s: want %s got %s", x, test.remoteAddr.IP, test.want2.IP, got.IP) diff --git a/addrmgr/network.go b/addrmgr/network.go index 6321058e25..4ff9ab7bd2 100644 --- a/addrmgr/network.go +++ b/addrmgr/network.go @@ -127,6 +127,12 @@ const ( TORv2Address ) +// NetAddressTypeFilter represents a function that returns whether a particular +// network address type matches a filter. Internally, it is used to ensure that +// only addresses that pass the filter's constraints are returned from the +// address manager. +type NetAddressTypeFilter func(NetAddressType) bool + // isRFC1918 returns whether or not the passed address is part of the IPv4 // private network address space as defined by RFC1918 (10.0.0.0/8, // 172.16.0.0/12, or 192.168.0.0/16). diff --git a/server.go b/server.go index 15f59fc964..2445d26af5 100644 --- a/server.go +++ b/server.go @@ -444,9 +444,8 @@ func (ps *peerState) ResolveLocalAddress(netType addrmgr.NetAddressType, addrMgr // Add a local address if the network address is a probable external // endpoint of the listener. - lNa := wire.NewNetAddressIPPort(listenerIP, uint16(port), services) lNet := addrmgr.IPv4Address - if lNa.IP.To4() == nil { + if listenerIP.To4() == nil { lNet = addrmgr.IPv6Address } @@ -705,6 +704,25 @@ func hasServices(advertised, desired wire.ServiceFlag) bool { return advertised&desired == desired } +// isSupportedNetAddressTypeV1 returns whether the provided address manager +// network address type is supported by the addr wire message. +func isSupportedNetAddressTypeV1(netAddressType addrmgr.NetAddressType) bool { + switch netAddressType { + case addrmgr.IPv4Address: + case addrmgr.IPv6Address: + case addrmgr.TORv2Address: + return true + } + return false +} + +// getNetAddressTypeFilter returns a function that determines whether a +// specific address manager network address type is supported by the +// provided protocol version. +func getNetAddressTypeFilter(pver uint32) addrmgr.NetAddressTypeFilter { + return isSupportedNetAddressTypeV1 +} + // OnVersion is invoked when a peer receives a version wire message and is used // to negotiate the protocol version details as well as kick start the // communications. @@ -720,6 +738,7 @@ func (sp *serverPeer) OnVersion(_ *peer.Peer, msg *wire.MsgVersion) { // it is updated regardless in the case a new minimum protocol version is // enforced and the remote node has not upgraded yet. isInbound := sp.Inbound() + msgProtocolVersion := uint32(msg.ProtocolVersion) remoteAddr := wireToAddrmgrNetAddress(sp.NA()) addrManager := sp.server.addrManager if !cfg.SimNet && !cfg.RegNet && !isInbound { @@ -730,7 +749,7 @@ func (sp *serverPeer) OnVersion(_ *peer.Peer, msg *wire.MsgVersion) { } // Reject peers that have a protocol version that is too old. - if msg.ProtocolVersion < int32(wire.SendHeadersVersion) { + if msgProtocolVersion < wire.SendHeadersVersion { srvrLog.Debugf("Rejecting peer %s with protocol version %d prior to "+ "the required version %d", sp.Peer, msg.ProtocolVersion, wire.SendHeadersVersion) @@ -760,7 +779,8 @@ func (sp *serverPeer) OnVersion(_ *peer.Peer, msg *wire.MsgVersion) { // known tip. if !cfg.DisableListen && sp.server.syncManager.IsCurrent() { // Get address that best matches. - lna := addrManager.GetBestLocalAddress(remoteAddr) + naTypeFilter := getNetAddressTypeFilter(msgProtocolVersion) + lna := addrManager.GetBestLocalAddress(remoteAddr, naTypeFilter) if lna.IsRoutable() { // Filter addresses the peer already knows about. addresses := []*addrmgr.NetAddress{lna} @@ -1387,7 +1407,9 @@ func (sp *serverPeer) OnGetAddr(p *peer.Peer, msg *wire.MsgGetAddr) { sp.addrsSent = true // Get the current known addresses from the address manager. - addrCache := sp.server.addrManager.AddressCache() + pver := sp.ProtocolVersion() + naTypeFilter := getNetAddressTypeFilter(pver) + addrCache := sp.server.addrManager.AddressCache(naTypeFilter) // Push the addresses. sp.pushAddrMsg(addrCache) @@ -3781,6 +3803,9 @@ func newServer(ctx context.Context, listenAddrs []string, db database.DB, if !cfg.SimNet && !cfg.RegNet && len(cfg.ConnectPeers) == 0 { newAddressFunc = func() (net.Addr, error) { for tries := 0; tries < 100; tries++ { + // Note that this does not filter by address type. Unsupported + // network address types should be pruned from the address + // manager's internal storage prior to calling this function. addr := s.addrManager.GetAddress() if addr == nil { break From 2d13ac73608255ee9d7118decacbab85806ff067 Mon Sep 17 00:00:00 2001 From: Sef Boukenken Date: Sun, 4 Apr 2021 14:19:47 -0400 Subject: [PATCH 3/9] peer: Partially decouple peer from wire. This prevents sending addresses in a version message that cannot be converted to a wire network address. --- peer/go.mod | 8 +++++ peer/peer.go | 99 +++++++++++++++++++++++++++++----------------------- server.go | 42 +++++++++++----------- 3 files changed, 83 insertions(+), 66 deletions(-) diff --git a/peer/go.mod b/peer/go.mod index ee5213718d..d5f52544a0 100644 --- a/peer/go.mod +++ b/peer/go.mod @@ -4,6 +4,7 @@ go 1.11 require ( github.com/davecgh/go-spew v1.1.1 + github.com/decred/dcrd/addrmgr/v2 v2.0.0 // indirect github.com/decred/dcrd/chaincfg/chainhash v1.0.3 github.com/decred/dcrd/lru v1.1.0 github.com/decred/dcrd/txscript/v4 v4.0.0 @@ -11,3 +12,10 @@ require ( github.com/decred/go-socks v1.1.0 github.com/decred/slog v1.2.0 ) + +replace ( + github.com/decred/dcrd/addrmgr/v2 => ../addrmgr + github.com/decred/dcrd/dcrec/secp256k1/v4 => ../dcrec/secp256k1 + github.com/decred/dcrd/dcrutil/v4 => ../dcrutil + github.com/decred/dcrd/txscript/v4 => ../txscript +) diff --git a/peer/peer.go b/peer/peer.go index 3562f02f7f..846bab5b91 100644 --- a/peer/peer.go +++ b/peer/peer.go @@ -18,10 +18,10 @@ import ( "time" "github.com/davecgh/go-spew/spew" + "github.com/decred/dcrd/addrmgr/v2" "github.com/decred/dcrd/chaincfg/chainhash" "github.com/decred/dcrd/lru" "github.com/decred/dcrd/wire" - "github.com/decred/go-socks/socks" "github.com/decred/slog" ) @@ -81,6 +81,9 @@ var ( // connection detecting and disconnect logic since they intentionally // do so for testing purposes. allowSelfConns bool + + // zeroIPv4 is an unroutable IPv4 address consisting of all zeros. + zeroIPv4 = net.IP([]byte{0, 0, 0, 0}) ) // MessageListeners defines callback function pointers to invoke with message @@ -284,40 +287,30 @@ func minUint32(a, b uint32) uint32 { // newNetAddress attempts to extract the IP address and port from the passed // net.Addr interface and create a NetAddress structure using that information. -func newNetAddress(addr net.Addr, services wire.ServiceFlag) (*wire.NetAddress, error) { - // addr will be a net.TCPAddr when not using a proxy. - if tcpAddr, ok := addr.(*net.TCPAddr); ok { - ip := tcpAddr.IP - port := uint16(tcpAddr.Port) - na := wire.NewNetAddressIPPort(ip, port, services) - return na, nil - } - - // addr will be a socks.ProxiedAddr when using a proxy. - if proxiedAddr, ok := addr.(*socks.ProxiedAddr); ok { - ip := net.ParseIP(proxiedAddr.Host) - if ip == nil { - ip = net.ParseIP("0.0.0.0") - } - port := uint16(proxiedAddr.Port) - na := wire.NewNetAddressIPPort(ip, port, services) - return na, nil +func newNetAddress(addr net.Addr, services wire.ServiceFlag) (*addrmgr.NetAddress, error) { + host, portStr, err := net.SplitHostPort(addr.String()) + if err != nil { + return nil, err } - // For the most part, addr should be one of the two above cases, but - // to be safe, fall back to trying to parse the information from the - // address string as a last resort. - host, portStr, err := net.SplitHostPort(addr.String()) + addrType, addrBytes, err := addrmgr.ParseHost(host) if err != nil { return nil, err } - ip := net.ParseIP(host) + + if addrType == addrmgr.UnknownAddressType { + addrType = addrmgr.IPv4Address + addrBytes = zeroIPv4 + } + port, err := strconv.ParseUint(portStr, 10, 16) if err != nil { return nil, err } - na := wire.NewNetAddressIPPort(ip, uint16(port), services) - return na, nil + + timestamp := time.Unix(time.Now().Unix(), 0) + return addrmgr.NewNetAddressByType(addrType, addrBytes, uint16(port), + timestamp, services) } // outMsg is used to house a message to be sent along with a channel to signal @@ -385,7 +378,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) (*addrmgr.NetAddress, 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 @@ -431,7 +424,7 @@ type Peer struct { inbound bool flagsMtx sync.Mutex // protects the peer flags below - na *wire.NetAddress + na *addrmgr.NetAddress id int32 userAgent string services wire.ServiceFlag @@ -564,7 +557,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() *addrmgr.NetAddress { p.flagsMtx.Lock() if p.na == nil { p.flagsMtx.Unlock() @@ -1820,6 +1813,37 @@ func (p *Peer) readRemoteVersionMsg() error { return nil } +// addrmgrToWireNetAddress converts an IPv4, IPv6, or TORv2 address manager +// network address to a wire network address. The host name must not match the +// configured proxy host name. +// +// If the address cannot be converted, an IPv4 network address consisting of all +// zeroes is returned. +func addrmgrToWireNetAddress(addr *addrmgr.NetAddress, proxyAddr string) *wire.NetAddress { + if addr.Type != addrmgr.IPv4Address && + addr.Type != addrmgr.IPv6Address && + addr.Type != addrmgr.TORv2Address { + return wire.NewNetAddressIPPort(zeroIPv4, 0, addr.Services) + } + + // If we are behind a proxy and the connection comes from the proxy then + // return an unroutable address as their address. This is to prevent + // leaking the tor proxy address. + if proxyAddr != "" { + proxyHost, _, err := net.SplitHostPort(proxyAddr) + if err != nil { + return wire.NewNetAddressIPPort(zeroIPv4, 0, addr.Services) + } + peerAddress, _, err := net.SplitHostPort(addr.Key()) + if err != nil || peerAddress == proxyHost { + return wire.NewNetAddressIPPort(zeroIPv4, 0, addr.Services) + } + } + + return wire.NewNetAddressTimestamp(addr.Timestamp, addr.Services, addr.IP, + addr.Port) +} + // localVersionMsg creates a version message that can be used to send to the // remote peer. func (p *Peer) localVersionMsg() (*wire.MsgVersion, error) { @@ -1832,20 +1856,6 @@ func (p *Peer) localVersionMsg() (*wire.MsgVersion, error) { } } - theirNA := p.NA() - - // 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 - // leaking the tor proxy address. - 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 { - theirNA = wire.NewNetAddressIPPort(net.IP([]byte{0, 0, 0, 0}), 0, - theirNA.Services) - } - } - // Create a wire.NetAddress with only the services set to use as the // "addrme" in the version message. // @@ -1869,6 +1879,7 @@ func (p *Peer) localVersionMsg() (*wire.MsgVersion, error) { sentNonces.Add(nonce) // Version message. + theirNA := addrmgrToWireNetAddress(p.na, p.cfg.Proxy) msg := wire.NewMsgVersion(ourNA, theirNA, nonce, int32(blockNum)) msg.AddUserAgent(p.cfg.UserAgentName, p.cfg.UserAgentVersion, p.cfg.UserAgentComments...) @@ -2077,7 +2088,7 @@ func NewOutboundPeer(cfg *Config, addr string) (*Peer, error) { } p.na = na } else { - p.na = wire.NewNetAddressIPPort(net.ParseIP(host), uint16(port), 0) + p.na = addrmgr.NewNetAddressIPPort(net.ParseIP(host), uint16(port), 0) } return p, nil diff --git a/server.go b/server.go index 2445d26af5..bb5ad14dd1 100644 --- a/server.go +++ b/server.go @@ -6,6 +6,7 @@ package main import ( + "bytes" "context" "crypto/elliptic" "crypto/rand" @@ -313,20 +314,20 @@ type peerState struct { } // ConnectionsWithIP returns the number of connections with the given IP. -func (ps *peerState) ConnectionsWithIP(ip net.IP) int { +func (ps *peerState) ConnectionsWithIP(ip []byte) int { var total int for _, p := range ps.inboundPeers { - if ip.Equal(p.NA().IP) { + if bytes.Equal(ip, p.NA().IP) { total++ } } for _, p := range ps.outboundPeers { - if ip.Equal(p.NA().IP) { + if bytes.Equal(ip, p.NA().IP) { total++ } } for _, p := range ps.persistentPeers { - if ip.Equal(p.NA().IP) { + if bytes.Equal(ip, p.NA().IP) { total++ } } @@ -739,7 +740,7 @@ func (sp *serverPeer) OnVersion(_ *peer.Peer, msg *wire.MsgVersion) { // enforced and the remote node has not upgraded yet. isInbound := sp.Inbound() msgProtocolVersion := uint32(msg.ProtocolVersion) - remoteAddr := wireToAddrmgrNetAddress(sp.NA()) + remoteAddr := sp.NA() addrManager := sp.server.addrManager if !cfg.SimNet && !cfg.RegNet && !isInbound { err := addrManager.SetServices(remoteAddr, msg.Services) @@ -1458,7 +1459,7 @@ func (sp *serverPeer) OnAddr(p *peer.Peer, msg *wire.MsgAddr) { // Add addresses to server address manager. The address manager handles // the details of things such as preventing duplicate addresses, max // addresses, and last seen updates. - remoteAddr := wireToAddrmgrNetAddress(p.NA()) + remoteAddr := p.NA() sp.server.addrManager.AddAddresses(addrList, remoteAddr) } @@ -1711,7 +1712,10 @@ func (s *server) handleAddPeerMsg(state *peerState, sp *serverPeer) bool { // Limit max number of connections from a single IP. However, allow // whitelisted inbound peers and localhost connections regardless. isInboundWhitelisted := sp.isWhitelisted && sp.Inbound() - peerIP := sp.NA().IP + + // Since an address manager IP is just a byte array, cast it to access + // convenience methods on net.IP. + peerIP := net.IP(sp.NA().IP) if cfg.MaxSameIP > 0 && !isInboundWhitelisted && !peerIP.IsLoopback() && state.ConnectionsWithIP(peerIP)+1 > cfg.MaxSameIP { srvrLog.Infof("Max connections with %s reached [%d] - "+ @@ -1753,7 +1757,7 @@ func (s *server) handleAddPeerMsg(state *peerState, sp *serverPeer) bool { } } } else { - remoteAddr := wireToAddrmgrNetAddress(sp.NA()) + remoteAddr := sp.NA() state.outboundGroups[remoteAddr.GroupKey()]++ if sp.persistent { state.persistentPeers[sp.ID()] = sp @@ -1840,7 +1844,7 @@ func (s *server) handleDonePeerMsg(state *peerState, sp *serverPeer) { } if _, ok := list[sp.ID()]; ok { if !sp.Inbound() && sp.VersionKnown() { - remoteAddr := wireToAddrmgrNetAddress(sp.NA()) + remoteAddr := sp.NA() state.outboundGroups[remoteAddr.GroupKey()]-- } if !sp.Inbound() && sp.connReq != nil { @@ -1858,7 +1862,7 @@ func (s *server) handleDonePeerMsg(state *peerState, sp *serverPeer) { // Update the address' last seen time if the peer has acknowledged // our version and has sent us its version as well. if sp.VerAckReceived() && sp.VersionKnown() && sp.NA() != nil { - remoteAddr := wireToAddrmgrNetAddress(sp.NA()) + remoteAddr := sp.NA() err := s.addrManager.Connected(remoteAddr) if err != nil { srvrLog.Debugf("Marking address as connected failed: %v", err) @@ -2072,7 +2076,7 @@ func (s *server) handleQuery(state *peerState, querymsg interface{}) { found := disconnectPeer(state.persistentPeers, msg.cmp, func(sp *serverPeer) { // Keep group counts ok since we remove from // the list now. - remoteAddr := wireToAddrmgrNetAddress(sp.NA()) + remoteAddr := sp.NA() state.outboundGroups[remoteAddr.GroupKey()]-- peerLog.Debugf("Removing persistent peer %s (reqid %d)", remoteAddr, @@ -2128,7 +2132,7 @@ func (s *server) handleQuery(state *peerState, querymsg interface{}) { found = disconnectPeer(state.outboundPeers, msg.cmp, func(sp *serverPeer) { // Keep group counts ok since we remove from // the list now. - remoteAddr := wireToAddrmgrNetAddress(sp.NA()) + remoteAddr := sp.NA() state.outboundGroups[remoteAddr.GroupKey()]-- }) if found { @@ -2137,7 +2141,7 @@ func (s *server) handleQuery(state *peerState, querymsg interface{}) { // peers are found. for found { found = disconnectPeer(state.outboundPeers, msg.cmp, func(sp *serverPeer) { - remoteAddr := wireToAddrmgrNetAddress(sp.NA()) + remoteAddr := sp.NA() state.outboundGroups[remoteAddr.GroupKey()]-- }) } @@ -2206,14 +2210,8 @@ func newPeerConfig(sp *serverPeer) *peer.Config { OnWrite: sp.OnWrite, OnNotFound: sp.OnNotFound, }, - NewestBlock: sp.newestBlock, - HostToNetAddress: func(host string, port uint16, services wire.ServiceFlag) (*wire.NetAddress, error) { - address, err := sp.server.addrManager.HostToNetAddress(host, port, services) - if err != nil { - return nil, err - } - return addrmgrToWireNetAddress(address), nil - }, + NewestBlock: sp.newestBlock, + HostToNetAddress: sp.server.addrManager.HostToNetAddress, Proxy: cfg.Proxy, UserAgentName: userAgentName, UserAgentVersion: userAgentVersion, @@ -2257,7 +2255,7 @@ func (s *server) outboundPeerConnected(c *connmgr.ConnReq, conn net.Conn) { sp.AssociateConnection(conn) go s.peerDoneHandler(sp) - remoteAddr := wireToAddrmgrNetAddress(sp.NA()) + remoteAddr := sp.NA() err = s.addrManager.Attempt(remoteAddr) if err != nil { srvrLog.Debugf("Marking address as attempted failed: %v", err) From a32aa052b99bbb2335ff6369091b29d8e6e33e4f Mon Sep 17 00:00:00 2001 From: Sef Boukenken Date: Fri, 12 Mar 2021 04:46:14 -0500 Subject: [PATCH 4/9] addrmgr: Remove DNS lookups from address manager. This removes the need to perform DNS lookups in the address manager. --- addrmgr/addrmanager.go | 37 +----------- addrmgr/addrmanager_test.go | 116 +++++------------------------------- config.go | 4 +- rpcadaptors.go | 2 +- server.go | 51 ++++++++++++---- server_test.go | 107 +++++++++++++++++++++++++++++++++ 6 files changed, 164 insertions(+), 153 deletions(-) create mode 100644 server_test.go diff --git a/addrmgr/addrmanager.go b/addrmgr/addrmanager.go index a36abe6404..353bc52e22 100644 --- a/addrmgr/addrmanager.go +++ b/addrmgr/addrmanager.go @@ -39,11 +39,6 @@ type AddrManager struct { // is saved to and loaded from. peersFile string - // lookupFunc is a function provided to the address manager that is used to - // perform DNS lookups for a given hostname. - // The provided function MUST be safe for concurrent access. - lookupFunc func(string) ([]net.IP, error) - // rand is the address manager's internal PRNG. It is used to both randomly // retrieve addresses from the address manager's internal new and tried // buckets in addition to deciding whether an unknown address is accepted @@ -790,34 +785,6 @@ func ParseHost(host string) (NetAddressType, []byte, error) { return UnknownAddressType, nil, nil } -// HostToNetAddress parses and returns a network address given a hostname in a -// supported format (IPv4, IPv6, TORv2). If the hostname cannot be immediately -// converted from a known address format, it will be resolved using the lookup -// function provided to the address manager. If it cannot be resolved, an error -// is returned. -// -// This function is safe for concurrent access. -func (a *AddrManager) HostToNetAddress(host string, port uint16, services wire.ServiceFlag) (*NetAddress, error) { - networkID, addrBytes, err := ParseHost(host) - if err != nil { - return nil, err - } - if networkID != UnknownAddressType { - // Since the host has been successfully decoded, there is no need to - // perform a DNS lookup. - now := time.Unix(time.Now().Unix(), 0) - return NewNetAddressByType(networkID, addrBytes, port, now, services) - } - ips, err := a.lookupFunc(host) - if err != nil { - return nil, err - } - if len(ips) == 0 { - return nil, fmt.Errorf("no addresses found for host %s", host) - } - return NewNetAddressIPPort(ips[0], port, services), nil -} - // GetAddress returns a single address that should be routable. It picks a // random one from the possible addresses with preference given to ones that // have not been used recently and should not pick 'close' addresses @@ -1283,11 +1250,9 @@ func (a *AddrManager) ValidatePeerNa(localAddr, remoteAddr *NetAddress) (bool, N // New constructs a new address manager instance. // Use Start to begin processing asynchronous address updates. -// The address manager uses lookupFunc for necessary DNS lookups. -func New(dataDir string, lookupFunc func(string) ([]net.IP, error)) *AddrManager { +func New(dataDir string) *AddrManager { am := AddrManager{ peersFile: filepath.Join(dataDir, peersFilename), - lookupFunc: lookupFunc, rand: rand.New(rand.NewSource(time.Now().UnixNano())), quit: make(chan struct{}), localAddresses: make(map[string]*localAddress), diff --git a/addrmgr/addrmanager_test.go b/addrmgr/addrmanager_test.go index 46a4d8fc37..0fe3b150e5 100644 --- a/addrmgr/addrmanager_test.go +++ b/addrmgr/addrmanager_test.go @@ -6,7 +6,6 @@ package addrmgr import ( - "errors" "fmt" "net" "os" @@ -27,10 +26,6 @@ func defaultNetAddressTypeFilter(netAddressType NetAddressType) bool { return true } -func lookupFunc(host string) ([]net.IP, error) { - return nil, errors.New("not implemented") -} - // addAddressByIP is a convenience function that adds an address to the // address manager given a valid string representation of an ip address and // a port. @@ -55,7 +50,7 @@ func TestStartStop(t *testing.T) { t.Fatalf("peers file exists though it should not: %s", peersFile) } - amgr := New(dir, nil) + amgr := New(dir) amgr.Start() // Add single network address to the address manager. @@ -73,7 +68,7 @@ func TestStartStop(t *testing.T) { } // Start a new address manager, which initializes it from the peers file. - amgr = New(dir, nil) + amgr = New(dir) amgr.Start() knownAddress := amgr.GetAddress() @@ -96,7 +91,7 @@ func TestStartStop(t *testing.T) { } func TestAddOrUpdateAddress(t *testing.T) { - amgr := New("testaddaddressupdate", nil) + amgr := New("testaddaddressupdate") amgr.Start() if ka := amgr.GetAddress(); ka != nil { t.Fatal("address manager should contain no addresses") @@ -195,7 +190,7 @@ func TestAddLocalAddress(t *testing.T) { const testPort = 8333 const testServices = wire.SFNodeNetwork - amgr := New("testaddlocaladdress", nil) + amgr := New("testaddlocaladdress") validLocalAddresses := make(map[string]struct{}) for _, test := range tests { netAddr := NewNetAddressIPPort(test.ip, testPort, testServices) @@ -236,7 +231,7 @@ func TestAddLocalAddress(t *testing.T) { } func TestAttempt(t *testing.T) { - n := New("testattempt", lookupFunc) + n := New("testattempt") // Add a new address and get it. n.addAddressByIP(someIP, 8333) @@ -266,7 +261,7 @@ func TestAttempt(t *testing.T) { } func TestConnected(t *testing.T) { - n := New("testconnected", lookupFunc) + n := New("testconnected") // Add a new address and get it n.addAddressByIP(someIP, 8333) @@ -296,7 +291,7 @@ func TestConnected(t *testing.T) { } func TestNeedMoreAddresses(t *testing.T) { - n := New("testneedmoreaddresses", lookupFunc) + n := New("testneedmoreaddresses") addrsToAdd := needAddressThreshold b := n.NeedMoreAddresses() if !b { @@ -325,7 +320,7 @@ func TestNeedMoreAddresses(t *testing.T) { } func TestGood(t *testing.T) { - n := New("testgood", lookupFunc) + n := New("testgood") addrsToAdd := 64 * 64 addrs := make([]*NetAddress, addrsToAdd) @@ -358,7 +353,7 @@ func TestGood(t *testing.T) { // the new bucket, and when marked good it should move to the tried bucket. // If the tried bucket is full then it should make room for the newly tried // address by moving the old one back to the new bucket. - n = New("testgood_tried_overflow", lookupFunc) + n = New("testgood_tried_overflow") n.triedBucketSize = 1 n.getNewBucket = func(netAddr, srcAddr *NetAddress) int { return 0 @@ -443,7 +438,7 @@ func TestGood(t *testing.T) { } func TestGetAddress(t *testing.T) { - n := New("testgetaddress", lookupFunc) + n := New("testgetaddress") // Get an address from an empty set (should error) if rv := n.GetAddress(); rv != nil { @@ -538,7 +533,7 @@ func TestGetBestLocalAddress(t *testing.T) { newAddressFromIP(net.ParseIP("2001:470::1")), }} - amgr := New("testgetbestlocaladdress", nil) + amgr := New("testgetbestlocaladdress") // Test against default when there's no address for x, test := range tests { @@ -609,7 +604,7 @@ func TestCorruptPeersFile(t *testing.T) { if err := fp.Close(); err != nil { t.Fatalf("Could not write empty peers file: %s", peersFile) } - amgr := New(dir, nil) + amgr := New(dir) amgr.Start() amgr.Stop() if _, err := os.Stat(peersFile); err != nil { @@ -758,7 +753,7 @@ func TestValidatePeerNa(t *testing.T) { reach: Ipv6Weak, }} - addressManager := New("testValidatePeerNa", nil) + addressManager := New("testValidatePeerNa") for _, test := range tests { localIP := net.ParseIP(test.localAddress) remoteIP := net.ParseIP(test.remoteAddress) @@ -778,94 +773,11 @@ func TestValidatePeerNa(t *testing.T) { } } -// TestHostToNetAddress ensures that HostToNetAddress behaves as expected -// given valid and invalid host name arguments. -func TestHostToNetAddress(t *testing.T) { - // Define a hostname that will cause a lookup to be performed using the - // lookupFunc provided to the address manager instance for each test. - const hostnameForLookup = "hostname.test" - const services = wire.SFNodeNetwork - - tests := []struct { - name string - host string - port uint16 - lookupFunc func(host string) ([]net.IP, error) - wantErr bool - want *NetAddress - }{{ - name: "valid onion address", - host: "a5ccbdkubbr2jlcp.onion", - port: 8333, - lookupFunc: nil, - wantErr: false, - want: NewNetAddressIPPort( - net.ParseIP("fd87:d87e:eb43:744:208d:5408:63a4:ac4f"), 8333, - services), - }, { - name: "invalid onion address", - host: "0000000000000000.onion", - port: 8333, - lookupFunc: nil, - wantErr: true, - want: nil, - }, { - name: "unresolvable host name", - host: hostnameForLookup, - port: 8333, - lookupFunc: func(host string) ([]net.IP, error) { - return nil, fmt.Errorf("unresolvable host %v", host) - }, - wantErr: true, - want: nil, - }, { - name: "not resolved host name", - host: hostnameForLookup, - port: 8333, - lookupFunc: func(host string) ([]net.IP, error) { - return nil, nil - }, - wantErr: true, - want: nil, - }, { - name: "resolved host name", - host: hostnameForLookup, - port: 8333, - lookupFunc: func(host string) ([]net.IP, error) { - return []net.IP{net.ParseIP("127.0.0.1")}, nil - }, - wantErr: false, - want: NewNetAddressIPPort(net.ParseIP("127.0.0.1"), 8333, - services), - }, { - name: "valid ip address", - host: "12.1.2.3", - port: 8333, - lookupFunc: nil, - wantErr: false, - want: NewNetAddressIPPort(net.ParseIP("12.1.2.3"), 8333, - services), - }} - - for _, test := range tests { - addrManager := New("testHostToNetAddress", test.lookupFunc) - result, err := addrManager.HostToNetAddress(test.host, test.port, - services) - if test.wantErr == true && err == nil { - t.Errorf("%q: expected error but one was not returned", test.name) - } - if !reflect.DeepEqual(result, test.want) { - t.Errorf("%q: unexpected result - got %v, want %v", test.name, - result, test.want) - } - } -} - // TestSetServices ensures that a known address' services are updated as // expected and that the services field is not mutated when new services are // added. func TestSetServices(t *testing.T) { - addressManager := New("testSetServices", nil) + addressManager := New("testSetServices") const services = wire.SFNodeNetwork // Attempt to set services for an address not known to the address manager. diff --git a/config.go b/config.go index 7d41fc506e..a401cf2c8b 100644 --- a/config.go +++ b/config.go @@ -1393,14 +1393,14 @@ func dcrdDial(ctx context.Context, network, addr string) (net.Conn, error) { return cfg.dial(ctx, network, addr) } -// dcrdLookup returns the correct DNS lookup function to use depending on the +// dcrdLookup invokes the correct DNS lookup function to use depending on the // passed host and configuration options. For example, .onion addresses will be // resolved using the onion specific proxy if one was specified, but will // otherwise treat the normal proxy as tor unless --noonion was specified in // which case the lookup will fail. Meanwhile, normal IP addresses will be // resolved using tor if a proxy was specified unless --noonion was also // specified in which case the normal system DNS resolver will be used. -func dcrdLookup(host string) ([]net.IP, error) { +func (cfg *config) dcrdLookup(host string) ([]net.IP, error) { if strings.HasSuffix(host, ".onion") { return cfg.onionlookup(host) } diff --git a/rpcadaptors.go b/rpcadaptors.go index b95aed5ab5..fd9ea93e01 100644 --- a/rpcadaptors.go +++ b/rpcadaptors.go @@ -303,7 +303,7 @@ func (cm *rpcConnManager) AddedNodeInfo() []rpcserver.Peer { // This function is safe for concurrent access and is part of the // rpcserver.ConnManager interface implementation. func (*rpcConnManager) Lookup(host string) ([]net.IP, error) { - return dcrdLookup(host) + return cfg.dcrdLookup(host) } // rpcSyncMgr provides an adaptor for use with the RPC server and implements the diff --git a/server.go b/server.go index bb5ad14dd1..7c30657aea 100644 --- a/server.go +++ b/server.go @@ -360,6 +360,35 @@ func (ps *peerState) forAllPeers(closure func(sp *serverPeer)) { ps.forAllOutboundPeers(closure) } +// hostToNetAddress parses and returns an address manager network address given +// a hostname in a supported format (IPv4, IPv6, TORv2). If the hostname +// cannot be immediately converted from a known address format, it will be +// resolved using a DNS lookup function. If it cannot be resolved, an error is +// returned. +func (cfg *config) hostToNetAddress(host string, port uint16, services wire.ServiceFlag) (*addrmgr.NetAddress, error) { + networkID, addrBytes, err := addrmgr.ParseHost(host) + if err != nil { + return nil, err + } + if networkID != addrmgr.UnknownAddressType { + // Since the host has been successfully decoded, there is no need to + // perform a DNS lookup. + now := time.Unix(time.Now().Unix(), 0) + return addrmgr.NewNetAddressByType(networkID, addrBytes, port, + now, services) + } + + ips, err := cfg.dcrdLookup(host) + if err != nil { + return nil, err + } + if len(ips) == 0 { + return nil, fmt.Errorf("no addresses found for %s", host) + } + na := addrmgr.NewNetAddressIPPort(ips[0], port, services) + return na, nil +} + // ResolveLocalAddress picks the best suggested network address from available // options, per the network interface key provided. The best suggestion, if // found, is added as a local address. @@ -382,7 +411,7 @@ func (ps *peerState) ResolveLocalAddress(netType addrmgr.NetAddressType, addrMgr } addLocalAddress := func(bestSuggestion string, port uint16, services wire.ServiceFlag) { - na, err := addrMgr.HostToNetAddress(bestSuggestion, port, services) + na, err := cfg.hostToNetAddress(bestSuggestion, port, services) if err != nil { amgrLog.Errorf("unable to generate network address using host %v: "+ "%v", bestSuggestion, err) @@ -2211,7 +2240,7 @@ func newPeerConfig(sp *serverPeer) *peer.Config { OnNotFound: sp.OnNotFound, }, NewestBlock: sp.newestBlock, - HostToNetAddress: sp.server.addrManager.HostToNetAddress, + HostToNetAddress: cfg.hostToNetAddress, Proxy: cfg.Proxy, UserAgentName: userAgentName, UserAgentVersion: userAgentVersion, @@ -3099,11 +3128,10 @@ func (s *server) querySeeders(ctx context.Context) { // seeded addresses. In the incredibly rare event that the lookup // fails after it just succeeded, fall back to using the first // returned address as the source. - srcAddr := wireToAddrmgrNetAddress(addrs[0]) - srcIPs, err := dcrdLookup(seeder) - if err == nil && len(srcIPs) > 0 { - const httpsPort = 443 - srcAddr = addrmgr.NewNetAddressIPPort(srcIPs[0], httpsPort, 0) + const httpsPort = 443 + srcAddr, err := cfg.hostToNetAddress(seeder, httpsPort, 0) + if err != nil { + srcAddr = wireToAddrmgrNetAddress(addrs[0]) } addresses := wireToAddrmgrNetAddresses(addrs) s.addrManager.AddAddresses(addresses, srcAddr) @@ -3443,8 +3471,7 @@ func setupRPCListeners() ([]net.Listener, error) { func newServer(ctx context.Context, listenAddrs []string, db database.DB, utxoDb *leveldb.DB, chainParams *chaincfg.Params, dataDir string) (*server, error) { - - amgr := addrmgr.New(cfg.DataDir, dcrdLookup) + amgr := addrmgr.New(cfg.DataDir) services := defaultServices var listeners []net.Listener @@ -4001,7 +4028,7 @@ func initListeners(ctx context.Context, params *chaincfg.Params, amgr *addrmgr.A eport = uint16(port) } - na, err := amgr.HostToNetAddress(host, eport, services) + na, err := cfg.hostToNetAddress(host, eport, services) if err != nil { srvrLog.Warnf("Not adding %s as externalip: %v", sip, err) continue @@ -4047,7 +4074,7 @@ func addrStringToNetAddr(addr string) (net.Addr, error) { // Attempt to look up an IP address associated with the parsed host. // The dcrdLookup function will transparently handle performing the // lookup over Tor if necessary. - ips, err := dcrdLookup(host) + ips, err := cfg.dcrdLookup(host) if err != nil { return nil, err } @@ -4102,7 +4129,7 @@ func addLocalAddress(addrMgr *addrmgr.AddrManager, addr string, services wire.Se addrMgr.AddLocalAddress(netAddr, addrmgr.BoundPrio) } } else { - netAddr, err := addrMgr.HostToNetAddress(host, uint16(port), services) + netAddr, err := cfg.hostToNetAddress(host, uint16(port), services) if err != nil { return err } diff --git a/server_test.go b/server_test.go new file mode 100644 index 0000000000..f480f858b0 --- /dev/null +++ b/server_test.go @@ -0,0 +1,107 @@ +// Copyright (c) 2021 The Decred developers +// Use of this source code is governed by an ISC +// license that can be found in the LICENSE file. + +package main + +import ( + "fmt" + "net" + "reflect" + "testing" + + "github.com/decred/dcrd/addrmgr/v2" + "github.com/decred/dcrd/wire" +) + +// TestHostToNetAddress ensures that HostToNetAddress behaves as expected +// given valid and invalid host name arguments. +func TestHostToNetAddress(t *testing.T) { + // Define a hostname that will cause a lookup to be performed using the + // lookupFunc provided to the address manager instance for each test. + const hostnameForLookup = "hostname.test" + const services = wire.SFNodeNetwork + + tests := []struct { + name string + host string + port uint16 + lookupFunc func(host string) ([]net.IP, error) + wantErr bool + want *addrmgr.NetAddress + }{ + { + name: "valid onion address", + host: "a5ccbdkubbr2jlcp.onion", + port: 8333, + lookupFunc: nil, + wantErr: false, + want: addrmgr.NewNetAddressIPPort( + net.ParseIP("fd87:d87e:eb43:744:208d:5408:63a4:ac4f"), 8333, + services), + }, + { + name: "invalid onion address", + host: "0000000000000000.onion", + port: 8333, + lookupFunc: nil, + wantErr: true, + want: nil, + }, + { + name: "unresolvable host name", + host: hostnameForLookup, + port: 8333, + lookupFunc: func(host string) ([]net.IP, error) { + return nil, fmt.Errorf("unresolvable host %v", host) + }, + wantErr: true, + want: nil, + }, + { + name: "not resolved host name", + host: hostnameForLookup, + port: 8333, + lookupFunc: func(host string) ([]net.IP, error) { + return nil, nil + }, + wantErr: true, + want: nil, + }, + { + name: "resolved host name", + host: hostnameForLookup, + port: 8333, + lookupFunc: func(host string) ([]net.IP, error) { + return []net.IP{net.ParseIP("127.0.0.1")}, nil + }, + wantErr: false, + want: addrmgr.NewNetAddressIPPort(net.ParseIP("127.0.0.1"), 8333, + services), + }, + { + name: "valid ip address", + host: "12.1.2.3", + port: 8333, + lookupFunc: nil, + wantErr: false, + want: addrmgr.NewNetAddressIPPort(net.ParseIP("12.1.2.3"), 8333, + services), + }, + } + + for _, test := range tests { + cfg := &config{ + lookup: test.lookupFunc, + onionlookup: test.lookupFunc, + } + result, err := cfg.hostToNetAddress(test.host, test.port, services) + if test.wantErr == true && err == nil { + t.Errorf("%q: expected error but one was not returned", test.name) + } + if !reflect.DeepEqual(result, test.want) { + t.Errorf("%q: unexpected result -- got %v, want %v", test.name, + result, test.want) + } + } +} From ddf3d7debca575df3580709db6ded7a52982c62e Mon Sep 17 00:00:00 2001 From: Sef Boukenken Date: Sun, 4 Apr 2021 02:16:11 -0400 Subject: [PATCH 5/9] addrmgr: Add TORv3 network address type. This change adds support for TORv3 addresses to the address manager. It also allows connecting to a TORv3 hostname with the cli --connect flag when starting dcrd. It does not support relaying TORv3 addresses over the peer to peer network. --- addrmgr/addrmanager.go | 20 ++++++++- addrmgr/go.mod | 1 + addrmgr/go.sum | 8 ++++ addrmgr/netaddress.go | 15 +++++-- addrmgr/netaddress_test.go | 7 ++++ addrmgr/network.go | 50 +++++++++++++++++++++-- addrmgr/network_test.go | 83 +++++++++++++++++++++----------------- go.mod | 2 +- go.sum | 10 +++-- peer/go.sum | 8 ++++ server.go | 35 ++++++++++++---- 11 files changed, 184 insertions(+), 55 deletions(-) diff --git a/addrmgr/addrmanager.go b/addrmgr/addrmanager.go index 353bc52e22..11ac813317 100644 --- a/addrmgr/addrmanager.go +++ b/addrmgr/addrmanager.go @@ -770,6 +770,17 @@ func ParseHost(host string) (NetAddressType, []byte, error) { addrBytes := append(prefix, data...) return TORv2Address, addrBytes, nil } + // Check if this is a valid TORv3 address. + if len(host) == 62 { + torAddressBytes, err := base32.StdEncoding.DecodeString( + strings.ToUpper(host[:56])) + if err != nil { + return UnknownAddressType, nil, err + } + if pubkey, valid := isTORv3(torAddressBytes); valid { + return TORv3Address, pubkey, nil + } + } } if ip := net.ParseIP(host); ip != nil { @@ -1126,8 +1137,13 @@ func getReachabilityFrom(localAddr, remoteAddr *NetAddress) NetAddressReach { return Unreachable } - if remoteAddr.Type == TORv2Address { - if localAddr.Type == TORv2Address { + isRemoteAddrTOR := remoteAddr.Type == TORv2Address || + remoteAddr.Type == TORv3Address + isLocalAddrTOR := localAddr.Type == TORv2Address || + localAddr.Type == TORv3Address + + if isRemoteAddrTOR { + if isLocalAddrTOR { return Private } diff --git a/addrmgr/go.mod b/addrmgr/go.mod index fe49d140e5..93d66de654 100644 --- a/addrmgr/go.mod +++ b/addrmgr/go.mod @@ -6,4 +6,5 @@ require ( github.com/decred/dcrd/chaincfg/chainhash v1.0.3 github.com/decred/dcrd/wire v1.5.0 github.com/decred/slog v1.2.0 + golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2 ) diff --git a/addrmgr/go.sum b/addrmgr/go.sum index 57c5b2d5d4..414fc9bd1d 100644 --- a/addrmgr/go.sum +++ b/addrmgr/go.sum @@ -9,3 +9,11 @@ github.com/decred/dcrd/wire v1.5.0 h1:3SgcEzSjqAMQvOugP0a8iX7yQSpiVT1yNi9bc4iOXV github.com/decred/dcrd/wire v1.5.0/go.mod h1:fzAjVqw32LkbAZIt5mnrvBR751GTa3e0rRQdOIhPY3w= github.com/decred/slog v1.2.0 h1:soHAxV52B54Di3WtKLfPum9OFfWqwtf/ygf9njdfnPM= github.com/decred/slog v1.2.0/go.mod h1:kVXlGnt6DHy2fV5OjSeuvCJ0OmlmTF6LFpEPMu/fOY0= +golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2 h1:It14KIkyBFYkHkwZ7k45minvA9aorojkyjGk9KJ5B/w= +golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68 h1:nxC68pudNYkKU6jWhgrqdreuFiOQWj1Fs7T3VrH4Pjw= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= diff --git a/addrmgr/netaddress.go b/addrmgr/netaddress.go index 95f70306ac..2af6a98528 100644 --- a/addrmgr/netaddress.go +++ b/addrmgr/netaddress.go @@ -51,6 +51,13 @@ func (netAddr *NetAddress) ipString() string { case TORv2Address: base32 := base32.StdEncoding.EncodeToString(netIP[6:]) return strings.ToLower(base32) + ".onion" + case TORv3Address: + addrBytes := netIP + checksum := calcTORv3Checksum(addrBytes) + addrBytes = append(addrBytes, checksum[:]...) + addrBytes = append(addrBytes, torV3VersionByte) + base32 := base32.StdEncoding.EncodeToString(addrBytes) + return strings.ToLower(base32) + ".onion" } return net.IP(netIP).String() } @@ -104,7 +111,7 @@ func canonicalizeIP(addrType NetAddressType, addrBytes []byte) []byte { // deriveNetAddressType attempts to determine the network address type from // the address' raw bytes. If the type cannot be determined, an error is // returned. -func deriveNetAddressType(addrBytes []byte) (NetAddressType, error) { +func deriveNetAddressType(claimedType NetAddressType, addrBytes []byte) (NetAddressType, error) { len := len(addrBytes) switch { case isIPv4(addrBytes): @@ -113,6 +120,8 @@ func deriveNetAddressType(addrBytes []byte) (NetAddressType, error) { return TORv2Address, nil case len == 16: return IPv6Address, nil + case len == 32 && claimedType == TORv3Address: + return TORv3Address, nil } strErr := fmt.Sprintf("unable to determine address type from raw network "+ "address bytes: %v", addrBytes) @@ -122,7 +131,7 @@ func deriveNetAddressType(addrBytes []byte) (NetAddressType, error) { // assertNetAddressTypeValid returns an error if the suggested address type does // not appear to match the provided address. func assertNetAddressTypeValid(netAddressType NetAddressType, addrBytes []byte) error { - derivedAddressType, err := deriveNetAddressType(addrBytes) + derivedAddressType, err := deriveNetAddressType(netAddressType, addrBytes) if err != nil { return err } @@ -184,7 +193,7 @@ func (a *AddrManager) newAddressFromString(addr string) (*NetAddress, error) { // MUST be an IPv4, IPv6, or TORv2 address since this method does not perform // error checking on the derived network address type. func NewNetAddressIPPort(ip net.IP, port uint16, services wire.ServiceFlag) *NetAddress { - netAddressType, _ := deriveNetAddressType(ip) + netAddressType, _ := deriveNetAddressType(UnknownAddressType, ip) timestamp := time.Unix(time.Now().Unix(), 0) canonicalizedIP := canonicalizeIP(netAddressType, ip) return &NetAddress{ diff --git a/addrmgr/netaddress_test.go b/addrmgr/netaddress_test.go index a3c0649b4c..a53a7eba2b 100644 --- a/addrmgr/netaddress_test.go +++ b/addrmgr/netaddress_test.go @@ -189,6 +189,13 @@ func TestKey(t *testing.T) { port: 8334, want: "aaaaaaaaaaaaaaaa.onion:8334", }, + + // TORv3 + { + host: "xa4r2iadxm55fbnqgwwi5mymqdcofiu3w6rpbtqn7b2dyn7mgwj64jyd.onion", + port: 8333, + want: "xa4r2iadxm55fbnqgwwi5mymqdcofiu3w6rpbtqn7b2dyn7mgwj64jyd.onion:8333", + }, } timeNow := time.Now() diff --git a/addrmgr/network.go b/addrmgr/network.go index 4ff9ab7bd2..e2c895f5eb 100644 --- a/addrmgr/network.go +++ b/addrmgr/network.go @@ -6,10 +6,17 @@ package addrmgr import ( + "bytes" "fmt" "net" + + "golang.org/x/crypto/sha3" ) +// torV3VersionByte represents the version byte used when encoding and decoding +// a torv3 host name. +const torV3VersionByte = byte(3) + var ( // rfc1918Nets specifies the IPv4 private address blocks as defined by // by RFC1918 (10.0.0.0/8, 172.16.0.0/12, and 192.168.0.0/16). @@ -125,6 +132,7 @@ const ( IPv4Address IPv6Address TORv2Address + TORv3Address ) // NetAddressTypeFilter represents a function that returns whether a particular @@ -224,6 +232,39 @@ func isRFC6598(netIP net.IP) bool { return rfc6598Net.Contains(netIP) } +// calcTORv3Checksum returns the checksum bytes given a 32 byte +// TORv3 public key. +func calcTORv3Checksum(publicKey []byte) []byte { + checkSumInput := []byte(".onion checksum") + checkSumInput = append(checkSumInput, publicKey...) + checkSumInput = append(checkSumInput, torV3VersionByte) + digest := sha3.Sum256(checkSumInput) + return digest[:2] +} + +// isTORv3 returns whether or not the passed address is a valid TORv3 address +// with the checksum and version bytes. If it is valid, it also returns the +// public key of the tor v3 address. +func isTORv3(addressBytes []byte) ([]byte, bool) { + if len(addressBytes) != 35 { + return nil, false + } + + version := addressBytes[34] + if version != torV3VersionByte { + return nil, false + } + + publicKey := addressBytes[:32] + computedChecksum := calcTORv3Checksum(publicKey) + checksum := addressBytes[32:34] + if !bytes.Equal(computedChecksum, checksum) { + return nil, false + } + + return publicKey, true +} + // isValid returns whether or not the passed address is valid. The address is // considered invalid under the following circumstances: // IPv4: It is either a zero or all bits set address. @@ -266,7 +307,6 @@ func (na *NetAddress) GroupKey() string { newIP := netIP[12:16] return newIP.Mask(net.CIDRMask(16, 32)).String() } - if isRFC3964(netIP) { newIP := netIP[2:6] return newIP.Mask(net.CIDRMask(16, 32)).String() @@ -281,8 +321,12 @@ func (na *NetAddress) GroupKey() string { return newIP.Mask(net.CIDRMask(16, 32)).String() } if na.Type == TORv2Address { - // group is keyed off the first 4 bits of the actual onion key. - return fmt.Sprintf("tor:%d", netIP[6]&((1<<4)-1)) + // Group is keyed off the first 4 bits of the actual onion key. + return fmt.Sprintf("torv2:%d", netIP[6]&((1<<4)-1)) + } + if na.Type == TORv3Address { + // Group is keyed off the first 4 bits of the public key. + return fmt.Sprintf("torv3:%d", netIP[0]&((1<<4)-1)) } // OK, so now we know ourselves to be a IPv6 address. diff --git a/addrmgr/network_test.go b/addrmgr/network_test.go index 07453ccd1a..2c14bbf824 100644 --- a/addrmgr/network_test.go +++ b/addrmgr/network_test.go @@ -10,6 +10,7 @@ import ( "testing" "github.com/decred/dcrd/wire" + "time" ) // TestIPTypes ensures the various functions which determine the type of an IP @@ -146,55 +147,65 @@ func TestIPTypes(t *testing.T) { func TestGroupKey(t *testing.T) { tests := []struct { name string - ip string + host string expected string }{ // Local addresses. - {name: "ipv4 localhost", ip: "127.0.0.1", expected: "local"}, - {name: "ipv6 localhost", ip: "::1", expected: "local"}, - {name: "ipv4 zero", ip: "0.0.0.0", expected: "local"}, - {name: "ipv4 first octet zero", ip: "0.1.2.3", expected: "local"}, + {name: "ipv4 localhost", host: "127.0.0.1", expected: "local"}, + {name: "ipv6 localhost", host: "::1", expected: "local"}, + {name: "ipv4 zero", host: "0.0.0.0", expected: "local"}, + {name: "ipv4 first octet zero", host: "0.1.2.3", expected: "local"}, // Unroutable addresses. - {name: "ipv4 invalid bcast", ip: "255.255.255.255", expected: "unroutable"}, - {name: "ipv4 rfc1918 10/8", ip: "10.1.2.3", expected: "unroutable"}, - {name: "ipv4 rfc1918 172.16/12", ip: "172.16.1.2", expected: "unroutable"}, - {name: "ipv4 rfc1918 192.168/16", ip: "192.168.1.2", expected: "unroutable"}, - {name: "ipv6 rfc3849 2001:db8::/32", ip: "2001:db8::1234", expected: "unroutable"}, - {name: "ipv4 rfc3927 169.254/16", ip: "169.254.1.2", expected: "unroutable"}, - {name: "ipv6 rfc4193 fc00::/7", ip: "fc00::1234", expected: "unroutable"}, - {name: "ipv6 rfc4843 2001:10::/28", ip: "2001:10::1234", expected: "unroutable"}, - {name: "ipv6 rfc4862 fe80::/64", ip: "fe80::1234", expected: "unroutable"}, + {name: "ipv4 invalid bcast", host: "255.255.255.255", expected: "unroutable"}, + {name: "ipv4 rfc1918 10/8", host: "10.1.2.3", expected: "unroutable"}, + {name: "ipv4 rfc1918 172.16/12", host: "172.16.1.2", expected: "unroutable"}, + {name: "ipv4 rfc1918 192.168/16", host: "192.168.1.2", expected: "unroutable"}, + {name: "ipv6 rfc3849 2001:db8::/32", host: "2001:db8::1234", expected: "unroutable"}, + {name: "ipv4 rfc3927 169.254/16", host: "169.254.1.2", expected: "unroutable"}, + {name: "ipv6 rfc4193 fc00::/7", host: "fc00::1234", expected: "unroutable"}, + {name: "ipv6 rfc4843 2001:10::/28", host: "2001:10::1234", expected: "unroutable"}, + {name: "ipv6 rfc4862 fe80::/64", host: "fe80::1234", expected: "unroutable"}, // IPv4 normal. - {name: "ipv4 normal class a", ip: "12.1.2.3", expected: "12.1.0.0"}, - {name: "ipv4 normal class b", ip: "173.1.2.3", expected: "173.1.0.0"}, - {name: "ipv4 normal class c", ip: "196.1.2.3", expected: "196.1.0.0"}, + {name: "ipv4 normal class a", host: "12.1.2.3", expected: "12.1.0.0"}, + {name: "ipv4 normal class b", host: "173.1.2.3", expected: "173.1.0.0"}, + {name: "ipv4 normal class c", host: "196.1.2.3", expected: "196.1.0.0"}, // IPv6/IPv4 translations. - {name: "ipv6 rfc3964 with ipv4 encap", ip: "2002:0c01:0203::", expected: "12.1.0.0"}, - {name: "ipv6 rfc4380 toredo ipv4", ip: "2001:0:1234::f3fe:fdfc", expected: "12.1.0.0"}, - {name: "ipv6 rfc6052 well-known prefix with ipv4", ip: "64:ff9b::0c01:0203", expected: "12.1.0.0"}, - {name: "ipv6 rfc6145 translated ipv4", ip: "::ffff:0:0c01:0203", expected: "12.1.0.0"}, - - // Tor. - {name: "ipv6 tor onioncat", ip: "fd87:d87e:eb43:1234::5678", expected: "tor:2"}, - {name: "ipv6 tor onioncat 2", ip: "fd87:d87e:eb43:1245::6789", expected: "tor:2"}, - {name: "ipv6 tor onioncat 3", ip: "fd87:d87e:eb43:1345::6789", expected: "tor:3"}, + {name: "ipv6 rfc3964 with ipv4 encap", host: "2002:0c01:0203::", expected: "12.1.0.0"}, + {name: "ipv6 rfc4380 toredo ipv4", host: "2001:0:1234::f3fe:fdfc", expected: "12.1.0.0"}, + {name: "ipv6 rfc6052 well-known prefix with ipv4", host: "64:ff9b::0c01:0203", expected: "12.1.0.0"}, + {name: "ipv6 rfc6145 translated ipv4", host: "::ffff:0:0c01:0203", expected: "12.1.0.0"}, + + // TORv2 + {name: "ipv6 tor onioncat", host: "fd87:d87e:eb43:1234::5678", expected: "torv2:2"}, + {name: "ipv6 tor onioncat 2", host: "fd87:d87e:eb43:1245::6789", expected: "torv2:2"}, + {name: "ipv6 tor onioncat 3", host: "fd87:d87e:eb43:1345::6789", expected: "torv2:3"}, + + // TORv3 + { + name: "torv3", + host: "xa4r2iadxm55fbnqgwwi5mymqdcofiu3w6rpbtqn7b2dyn7mgwj64jyd.onion", + expected: "torv3:8", + }, // IPv6 normal. - {name: "ipv6 normal", ip: "2602:100::1", expected: "2602:100::"}, - {name: "ipv6 normal 2", ip: "2602:0100::1234", expected: "2602:100::"}, - {name: "ipv6 hurricane electric", ip: "2001:470:1f10:a1::2", expected: "2001:470:1000::"}, - {name: "ipv6 hurricane electric 2", ip: "2001:0470:1f10:a1::2", expected: "2001:470:1000::"}, + {name: "ipv6 normal", host: "2602:100::1", expected: "2602:100::"}, + {name: "ipv6 normal 2", host: "2602:0100::1234", expected: "2602:100::"}, + {name: "ipv6 hurricane electric", host: "2001:470:1f10:a1::2", expected: "2001:470:1000::"}, + {name: "ipv6 hurricane electric 2", host: "2001:0470:1f10:a1::2", expected: "2001:470:1000::"}, } - for i, test := range tests { - nip := net.ParseIP(test.ip) - na := NewNetAddressIPPort(nip, 8333, wire.SFNodeNetwork) - if key := na.GroupKey(); key != test.expected { - t.Errorf("TestGroupKey #%d (%s): unexpected group key "+ - "- got '%s', want '%s'", i, test.name, key, test.expected) + ts := time.Now() + for _, test := range tests { + addrType, addrBytes, _ := ParseHost(test.host) + na, _ := NewNetAddressByType(addrType, addrBytes, 8333, ts, + wire.SFNodeNetwork) + actualKey := na.GroupKey() + if actualKey != test.expected { + t.Errorf("%q: unexpected group key - got %s, want %s", + test.name, actualKey, test.expected) } } } diff --git a/go.mod b/go.mod index c15dbf2061..82f9247754 100644 --- a/go.mod +++ b/go.mod @@ -37,7 +37,7 @@ require ( github.com/jrick/bitset v1.0.0 github.com/jrick/logrotate v1.0.0 github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 - golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a + golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2 golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e ) diff --git a/go.sum b/go.sum index a1b2d72400..793e3c4a1d 100644 --- a/go.sum +++ b/go.sum @@ -50,13 +50,14 @@ github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 h1:epCh84lMvA70 github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7/go.mod h1:q4W45IWZaF22tdD+VEXcAWRA037jwmWEB5VWYORlTpc= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a h1:vclmkQCjlDX5OydZ9wv8rBCcS0QyQY66Mpf/7BZbInM= -golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2 h1:It14KIkyBFYkHkwZ7k45minvA9aorojkyjGk9KJ5B/w= +golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc h1:zK/HqS5bZxDptfPJNq8v7vJfXtkU7r9TLIoSr1bXaP4= golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110 h1:qWPm9rbaAMKs8Bq/9LRpbMqxWRVUAQwMI9fVrssnTfw= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -67,9 +68,12 @@ golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e h1:fLOSk5Q00efkSvAm+4xcoXD+RRmLmmulPn5I3Y9F2EM= golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 h1:v+OssWQX+hTHEmOBgwxdZxK4zHq3yOs8F9J7mk0PY8E= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k= diff --git a/peer/go.sum b/peer/go.sum index 1d99529b01..b030764d0f 100644 --- a/peer/go.sum +++ b/peer/go.sum @@ -31,3 +31,11 @@ github.com/decred/go-socks v1.1.0 h1:dnENcc0KIqQo3HSXdgboXAHgqsCIutkqq6ntQjYtm2U github.com/decred/go-socks v1.1.0/go.mod h1:sDhHqkZH0X4JjSa02oYOGhcGHYp12FsY1jQ/meV8md0= github.com/decred/slog v1.2.0 h1:soHAxV52B54Di3WtKLfPum9OFfWqwtf/ygf9njdfnPM= github.com/decred/slog v1.2.0/go.mod h1:kVXlGnt6DHy2fV5OjSeuvCJ0OmlmTF6LFpEPMu/fOY0= +golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2 h1:It14KIkyBFYkHkwZ7k45minvA9aorojkyjGk9KJ5B/w= +golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68 h1:nxC68pudNYkKU6jWhgrqdreuFiOQWj1Fs7T3VrH4Pjw= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= diff --git a/server.go b/server.go index 7c30657aea..0bb02dbf45 100644 --- a/server.go +++ b/server.go @@ -3826,6 +3826,7 @@ func newServer(ctx context.Context, listenAddrs []string, db database.DB, // network. var newAddressFunc func() (net.Addr, error) if !cfg.SimNet && !cfg.RegNet && len(cfg.ConnectPeers) == 0 { + isTorDisabled := cfg.OnionProxy == "" newAddressFunc = func() (net.Addr, error) { for tries := 0; tries < 100; tries++ { // Note that this does not filter by address type. Unsupported @@ -3836,13 +3837,20 @@ func newServer(ctx context.Context, listenAddrs []string, db database.DB, break } + netAddr := addr.NetAddress() + + // Discard TORv3 addresses if not configured to connect to an + // onion proxy. + if isTorDisabled && netAddr.Type == addrmgr.TORv3Address { + continue + } + // Address will not be invalid, local or unroutable // because addrmanager rejects those on addition. // Just check that we don't already have an address // in the same group so that we are not connecting // to the same network segment at the expense of // others. - netAddr := addr.NetAddress() if s.OutboundGroupCount(netAddr.GroupKey()) != 0 { continue } @@ -4064,12 +4072,30 @@ func initListeners(ctx context.Context, params *chaincfg.Params, amgr *addrmgr.A // addrStringToNetAddr takes an address in the form of 'host:port' and returns // a net.Addr which maps to the original address with any host names resolved -// to IP addresses. +// to IP addresses, if applicable for the respective address type. func addrStringToNetAddr(addr string) (net.Addr, error) { host, strPort, err := net.SplitHostPort(addr) if err != nil { return nil, err } + port, err := strconv.Atoi(strPort) + if err != nil { + return nil, err + } + + // Determine the network that the address belongs to and return early if + // a DNS lookup should not be performed for the address. + networkID, _, err := addrmgr.ParseHost(host) + if err != nil { + return nil, err + } + switch networkID { + case addrmgr.TORv3Address: + return &simpleAddr{ + net: "tcp", + addr: addr, + }, nil + } // Attempt to look up an IP address associated with the parsed host. // The dcrdLookup function will transparently handle performing the @@ -4082,11 +4108,6 @@ func addrStringToNetAddr(addr string) (net.Addr, error) { return nil, fmt.Errorf("no addresses found for %s", host) } - port, err := strconv.Atoi(strPort) - if err != nil { - return nil, err - } - return &net.TCPAddr{ IP: ips[0], Port: port, From 27215a034ccf23ebd545f6ecd7f3c54344553166 Mon Sep 17 00:00:00 2001 From: Sef Boukenken Date: Tue, 25 Jan 2022 01:10:44 -0500 Subject: [PATCH 6/9] multi: Remove TORv2 network address type. This change removes support for the TORv2 network address persistence and propogation across the peer to peer network. --- addrmgr/addrmanager.go | 32 +++------ addrmgr/addrmanager_test.go | 60 +++++++++++----- addrmgr/netaddress.go | 17 ++--- addrmgr/netaddress_test.go | 136 ++++++++++++++---------------------- addrmgr/network.go | 36 ++-------- addrmgr/network_test.go | 8 +-- peer/peer.go | 7 +- server.go | 8 +-- server_test.go | 125 ++++++++++++++++++--------------- 9 files changed, 190 insertions(+), 239 deletions(-) diff --git a/addrmgr/addrmanager.go b/addrmgr/addrmanager.go index 11ac813317..791c88851e 100644 --- a/addrmgr/addrmanager.go +++ b/addrmgr/addrmanager.go @@ -546,13 +546,15 @@ func (a *AddrManager) deserializePeers(filePath string) error { for _, v := range sam.Addresses { netAddr, err := a.newAddressFromString(v.Addr) if err != nil { - return fmt.Errorf("failed to deserialize netaddress "+ - "%s: %v", v.Addr, err) + log.Warnf("skipping unrecognized network address %s: %v", + v.Addr, err) + continue } srcAddr, err := a.newAddressFromString(v.Src) if err != nil { - return fmt.Errorf("failed to deserialize netaddress "+ - "%s: %v", v.Src, err) + log.Warnf("skipping unrecognized network address %s: %v", + v.Src, err) + continue } ka := &KnownAddress{ @@ -759,17 +761,6 @@ func (a *AddrManager) reset() { // an unknown address type is returned without error. func ParseHost(host string) (NetAddressType, []byte, error) { if strings.HasSuffix(host, ".onion") { - // Check if this is a TorV2 address. - if len(host) == 22 { - data, err := base32.StdEncoding.DecodeString( - strings.ToUpper(host[:16])) - if err != nil { - return UnknownAddressType, nil, err - } - prefix := []byte{0xfd, 0x87, 0xd8, 0x7e, 0xeb, 0x43} - addrBytes := append(prefix, data...) - return TORv2Address, addrBytes, nil - } // Check if this is a valid TORv3 address. if len(host) == 62 { torAddressBytes, err := base32.StdEncoding.DecodeString( @@ -787,9 +778,6 @@ func ParseHost(host string) (NetAddressType, []byte, error) { if isIPv4(ip) { return IPv4Address, ip.To4(), nil } - if isOnionCatTor(ip) { - return TORv2Address, ip, nil - } return IPv6Address, ip, nil } @@ -1137,10 +1125,8 @@ func getReachabilityFrom(localAddr, remoteAddr *NetAddress) NetAddressReach { return Unreachable } - isRemoteAddrTOR := remoteAddr.Type == TORv2Address || - remoteAddr.Type == TORv3Address - isLocalAddrTOR := localAddr.Type == TORv2Address || - localAddr.Type == TORv3Address + isRemoteAddrTOR := remoteAddr.Type == TORv3Address + isLocalAddrTOR := localAddr.Type == TORv3Address if isRemoteAddrTOR { if isLocalAddrTOR { @@ -1236,7 +1222,7 @@ func (a *AddrManager) GetBestLocalAddress(remoteAddr *NetAddress, supportedNetAd // Send something unroutable if nothing suitable. var ip net.IP - if remoteAddr.Type != IPv4Address && remoteAddr.Type != TORv2Address { + if remoteAddr.Type != IPv4Address { ip = net.IPv6zero } else { ip = net.IPv4zero diff --git a/addrmgr/addrmanager_test.go b/addrmgr/addrmanager_test.go index 0fe3b150e5..b97a1d1655 100644 --- a/addrmgr/addrmanager_test.go +++ b/addrmgr/addrmanager_test.go @@ -20,6 +20,8 @@ import ( // Put some IP in here for convenience. Points to google. var someIP = "173.194.115.66" +var zeroTime = time.Time{} + // defaultNetAddressTypeFilter defines a filter that instructs address manager // operations that accept it to return network addresses of any type. func defaultNetAddressTypeFilter(netAddressType NetAddressType) bool { @@ -619,7 +621,7 @@ func TestValidatePeerNa(t *testing.T) { const unroutableIpv6Address = "::1" const routableIpv4Address = "12.1.2.3" const routableIpv6Address = "2003::" - onionCatTorV2Address := onionCatNet.IP.String() + onionCatTorV3Address := "xa4r2iadxm55fbnqgwwi5mymqdcofiu3w6rpbtqn7b2dyn7mgwj64jyd.onion" rfc4380IPAddress := rfc4380Net.IP.String() rfc3964IPAddress := rfc3964Net.IP.String() rfc6052IPAddress := rfc6052Net.IP.String() @@ -632,33 +634,33 @@ func TestValidatePeerNa(t *testing.T) { valid bool reach NetAddressReach }{{ - name: "torv2 to torv2", - localAddress: onionCatTorV2Address, - remoteAddress: onionCatTorV2Address, + name: "torv3 to torv3", + localAddress: onionCatTorV3Address, + remoteAddress: onionCatTorV3Address, valid: false, reach: Private, }, { - name: "routable ipv4 to torv2", + name: "routable ipv4 to torv3", localAddress: routableIpv4Address, - remoteAddress: onionCatTorV2Address, + remoteAddress: onionCatTorV3Address, valid: true, reach: Ipv4, }, { - name: "unroutable ipv4 to torv2", + name: "unroutable ipv4 to torv3", localAddress: unroutableIpv4Address, - remoteAddress: onionCatTorV2Address, + remoteAddress: onionCatTorV3Address, valid: false, reach: Default, }, { - name: "routable ipv6 to torv2", + name: "routable ipv6 to torv3", localAddress: routableIpv6Address, - remoteAddress: onionCatTorV2Address, + remoteAddress: onionCatTorV3Address, valid: false, reach: Default, }, { - name: "unroutable ipv6 to torv2", + name: "unroutable ipv6 to torv3", localAddress: unroutableIpv6Address, - remoteAddress: onionCatTorV2Address, + remoteAddress: onionCatTorV3Address, valid: false, reach: Default, }, { @@ -755,20 +757,44 @@ func TestValidatePeerNa(t *testing.T) { addressManager := New("testValidatePeerNa") for _, test := range tests { - localIP := net.ParseIP(test.localAddress) - remoteIP := net.ParseIP(test.remoteAddress) - localNa := NewNetAddressIPPort(localIP, 8333, wire.SFNodeNetwork) - remoteNa := NewNetAddressIPPort(remoteIP, 8333, wire.SFNodeNetwork) + localAddrType, localAddrBytes, err := ParseHost(test.localAddress) + if err != nil { + t.Errorf("%q: failed to parse local address '%v': %v", test.name, + test.localAddress, err) + return + } + remoteAddrType, remoteAddrBytes, err := ParseHost(test.remoteAddress) + if err != nil { + t.Errorf("%q: failed to parse remote address '%v': %v", test.name, + test.remoteAddress, err) + return + } + + localNa, err := NewNetAddressByType(localAddrType, localAddrBytes, + 8333, zeroTime, wire.SFNodeNetwork) + if err != nil { + t.Errorf("%q: failed to create local network address '%v': %v", + test.name, test.localAddress, err) + return + } + remoteNa, err := NewNetAddressByType(remoteAddrType, remoteAddrBytes, + 8333, zeroTime, wire.SFNodeNetwork) + if err != nil { + t.Errorf("%q: failed to create remote network address '%v': %v", + test.name, test.remoteAddress, err) + return + } valid, reach := addressManager.ValidatePeerNa(localNa, remoteNa) if valid != test.valid { t.Errorf("%q: unexpected return value for valid - want '%v', "+ "got '%v'", test.name, test.valid, valid) - continue + return } if reach != test.reach { t.Errorf("%q: unexpected return value for reach - want '%v', "+ "got '%v'", test.name, test.reach, reach) + return } } } diff --git a/addrmgr/netaddress.go b/addrmgr/netaddress.go index 2af6a98528..b3b8ebdd70 100644 --- a/addrmgr/netaddress.go +++ b/addrmgr/netaddress.go @@ -42,15 +42,11 @@ func (netAddr *NetAddress) IsRoutable() bool { } // ipString returns a string representation of the network address' IP field. -// If the ip is in the range used for TORv2 addresses then it will be -// transformed into the respective .onion address. It does not include the -// port. +// If the ip is in the range used for TOR addresses then it will be transformed +// into the respective .onion address. It does not include the port. func (netAddr *NetAddress) ipString() string { netIP := netAddr.IP switch netAddr.Type { - case TORv2Address: - base32 := base32.StdEncoding.EncodeToString(netIP[6:]) - return strings.ToLower(base32) + ".onion" case TORv3Address: addrBytes := netIP checksum := calcTORv3Checksum(addrBytes) @@ -99,9 +95,6 @@ func canonicalizeIP(addrType NetAddressType, addrBytes []byte) []byte { switch { case len == 16 && addrType == IPv4Address: return net.IP(addrBytes).To4() - case len == 10 && addrType == TORv2Address: - prefix := []byte{0xfd, 0x87, 0xd8, 0x7e, 0xeb, 0x43} - return append(prefix, addrBytes...) case addrType == IPv6Address: return net.IP(addrBytes).To16() } @@ -116,8 +109,6 @@ func deriveNetAddressType(claimedType NetAddressType, addrBytes []byte) (NetAddr switch { case isIPv4(addrBytes): return IPv4Address, nil - case len == 16 && isOnionCatTor(addrBytes): - return TORv2Address, nil case len == 16: return IPv6Address, nil case len == 32 && claimedType == TORv3Address: @@ -190,8 +181,8 @@ func (a *AddrManager) newAddressFromString(addr string) (*NetAddress, error) { // NewNetAddressIPPort creates a new address manager network address given an // ip, port, and the supported service flags for the address. The provided ip -// MUST be an IPv4, IPv6, or TORv2 address since this method does not perform -// error checking on the derived network address type. +// MUST be a valid address since this method does not perform error checking on +// the derived network address type. func NewNetAddressIPPort(ip net.IP, port uint16, services wire.ServiceFlag) *NetAddress { netAddressType, _ := deriveNetAddressType(UnknownAddressType, ip) timestamp := time.Unix(time.Now().Unix(), 0) diff --git a/addrmgr/netaddress_test.go b/addrmgr/netaddress_test.go index a53a7eba2b..3d07a14bcd 100644 --- a/addrmgr/netaddress_test.go +++ b/addrmgr/netaddress_test.go @@ -14,6 +14,15 @@ import ( "github.com/decred/dcrd/wire" ) +var ( + torV3AddressString = "xa4r2iadxm55fbnqgwwi5mymqdcofiu3w6rpbtqn7b2dyn7mgwj64jyd.onion" + torV3AddressBytes = []byte{ + 0xb8, 0x39, 0x1d, 0x20, 0x03, 0xbb, 0x3b, 0xd2, + 0x85, 0xb0, 0x35, 0xac, 0x8e, 0xb3, 0x0c, 0x80, + 0xc4, 0xe2, 0xa2, 0x9b, 0xb7, 0xa2, 0xf0, 0xce, + 0x0d, 0xf8, 0x74, 0x3c, 0x37, 0xec, 0x35, 0x93} +) + // TestNewNetAddressByType verifies that the TestNewNetAddressByType constructor // converts a network address with expected field values. func TestNewNetAddressByType(t *testing.T) { @@ -26,78 +35,53 @@ func TestNewNetAddressByType(t *testing.T) { addrType NetAddressType addrBytes []byte want *NetAddress - }{ - { - name: "4 byte ipv4 address stored as 4 byte ip", - addrType: IPv4Address, - addrBytes: net.ParseIP("127.0.0.1").To4(), - want: &NetAddress{ - IP: []byte{0x7f, 0x00, 0x00, 0x01}, - Port: port, - Services: services, - Timestamp: timestamp, - Type: IPv4Address, - }, + }{{ + name: "4 byte ipv4 address stored as 4 byte ip", + addrType: IPv4Address, + addrBytes: net.ParseIP("127.0.0.1").To4(), + want: &NetAddress{ + IP: []byte{0x7f, 0x00, 0x00, 0x01}, + Port: port, + Services: services, + Timestamp: timestamp, + Type: IPv4Address, }, - { - name: "16 byte ipv4 address stored as 4 byte ip", - addrType: IPv4Address, - addrBytes: net.ParseIP("127.0.0.1").To16(), - want: &NetAddress{ - IP: []byte{0x7f, 0x00, 0x00, 0x01}, - Port: port, - Services: services, - Timestamp: timestamp, - Type: IPv4Address, - }, - }, - { - name: "16 byte ipv6 address stored in 16 bytes", - addrType: IPv6Address, - addrBytes: net.ParseIP("::1"), - want: &NetAddress{ - IP: []byte{ - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01}, - Port: port, - Services: services, - Timestamp: timestamp, - Type: IPv6Address, - }, + }, { + name: "16 byte ipv4 address stored as 4 byte ip", + addrType: IPv4Address, + addrBytes: net.ParseIP("127.0.0.1").To16(), + want: &NetAddress{ + IP: []byte{0x7f, 0x00, 0x00, 0x01}, + Port: port, + Services: services, + Timestamp: timestamp, + Type: IPv4Address, }, - { - name: "16 byte torv2 address stored in 16 bytes", - addrType: TORv2Address, - addrBytes: net.ParseIP("fd87:d87e:eb43::"), - want: &NetAddress{ - IP: []byte{ - 0xfd, 0x87, 0xd8, 0x7e, 0xeb, 0x43, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - }, - Port: port, - Services: services, - Timestamp: timestamp, - Type: TORv2Address, - }, + }, { + name: "16 byte ipv6 address stored in 16 bytes", + addrType: IPv6Address, + addrBytes: net.ParseIP("::1"), + want: &NetAddress{ + IP: []byte{ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01}, + Port: port, + Services: services, + Timestamp: timestamp, + Type: IPv6Address, }, - { - name: "10 byte torv2 public key stored in 16 bytes with prefix", - addrType: TORv2Address, - addrBytes: []byte{ - 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, - }, - want: &NetAddress{ - IP: []byte{ - 0xfd, 0x87, 0xd8, 0x7e, 0xeb, 0x43, 0x01, 0x02, - 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, - }, - Port: port, - Services: services, - Timestamp: timestamp, - Type: TORv2Address, - }, + }, { + name: "32 byte torv3 address stored in 32 bytes", + addrType: TORv3Address, + addrBytes: torV3AddressBytes, + want: &NetAddress{ + IP: torV3AddressBytes, + Port: port, + Services: services, + Timestamp: timestamp, + Type: TORv3Address, }, - } + }} for _, test := range tests { addr, err := NewNetAddressByType(test.addrType, test.addrBytes, port, @@ -178,23 +162,11 @@ func TestKey(t *testing.T) { {host: "fee2::3:3", port: 8335, want: "[fee2::3:3]:8335"}, {host: "fef3::4:4", port: 8336, want: "[fef3::4:4]:8336"}, - // TORv2 - { - host: "fd87:d87e:eb43::", - port: 8333, - want: "aaaaaaaaaaaaaaaa.onion:8333", - }, - { - host: "aaaaaaaaaaaaaaaa.onion", - port: 8334, - want: "aaaaaaaaaaaaaaaa.onion:8334", - }, - // TORv3 { - host: "xa4r2iadxm55fbnqgwwi5mymqdcofiu3w6rpbtqn7b2dyn7mgwj64jyd.onion", + host: torV3AddressString, port: 8333, - want: "xa4r2iadxm55fbnqgwwi5mymqdcofiu3w6rpbtqn7b2dyn7mgwj64jyd.onion:8333", + want: torV3AddressString + ":8333", }, } diff --git a/addrmgr/network.go b/addrmgr/network.go index e2c895f5eb..51aeec21fb 100644 --- a/addrmgr/network.go +++ b/addrmgr/network.go @@ -77,19 +77,6 @@ var ( // rfc6598Net specifies the IPv4 block as defined by RFC6598 (100.64.0.0/10) rfc6598Net = ipNet("100.64.0.0", 10, 32) - // onionCatNet defines the IPv6 address block used to support Tor. - // bitcoind encodes a .onion address as a 16 byte number by decoding the - // address prior to the .onion (i.e. the key hash) base32 into a ten - // byte number. It then stores the first 6 bytes of the address as - // 0xfd, 0x87, 0xd8, 0x7e, 0xeb, 0x43. - // - // This is the same range used by OnionCat, which is part of the - // RFC4193 unique local IPv6 range. - // - // In summary the format is: - // { magic 6 bytes, 10 bytes base32 decode of key hash } - onionCatNet = ipNet("fd87:d87e:eb43::", 48, 128) - // zero4Net defines the IPv4 address block for address staring with 0 // (0.0.0.0/8). zero4Net = ipNet("0.0.0.0", 8, 32) @@ -115,24 +102,15 @@ func isLocal(netIP net.IP) bool { return netIP.IsLoopback() || zero4Net.Contains(netIP) } -// isOnionCatTor returns whether or not the passed address is in the IPv6 range -// used by bitcoin to support Tor (fd87:d87e:eb43::/48). Note that this range -// is the same range used by OnionCat, which is part of the RFC4193 unique local -// IPv6 range. -func isOnionCatTor(netIP net.IP) bool { - return onionCatNet.Contains(netIP) -} - // NetAddressType is used to indicate which network a network address belongs // to. type NetAddressType uint8 const ( - UnknownAddressType NetAddressType = iota - IPv4Address - IPv6Address - TORv2Address - TORv3Address + UnknownAddressType NetAddressType = 0 + IPv4Address NetAddressType = 1 + IPv6Address NetAddressType = 2 + TORv3Address NetAddressType = 4 ) // NetAddressTypeFilter represents a function that returns whether a particular @@ -283,7 +261,7 @@ func IsRoutable(netIP net.IP) bool { return isValid(netIP) && !(isRFC1918(netIP) || isRFC2544(netIP) || isRFC3927(netIP) || isRFC4862(netIP) || isRFC3849(netIP) || isRFC4843(netIP) || isRFC5737(netIP) || isRFC6598(netIP) || - isLocal(netIP) || (isRFC4193(netIP) && !isOnionCatTor(netIP))) + isLocal(netIP) || isRFC4193(netIP)) } // GroupKey returns a string representing the network group an address is part @@ -320,10 +298,6 @@ func (na *NetAddress) GroupKey() string { } return newIP.Mask(net.CIDRMask(16, 32)).String() } - if na.Type == TORv2Address { - // Group is keyed off the first 4 bits of the actual onion key. - return fmt.Sprintf("torv2:%d", netIP[6]&((1<<4)-1)) - } if na.Type == TORv3Address { // Group is keyed off the first 4 bits of the public key. return fmt.Sprintf("torv3:%d", netIP[0]&((1<<4)-1)) diff --git a/addrmgr/network_test.go b/addrmgr/network_test.go index 2c14bbf824..744af945e4 100644 --- a/addrmgr/network_test.go +++ b/addrmgr/network_test.go @@ -9,8 +9,9 @@ import ( "net" "testing" - "github.com/decred/dcrd/wire" "time" + + "github.com/decred/dcrd/wire" ) // TestIPTypes ensures the various functions which determine the type of an IP @@ -178,11 +179,6 @@ func TestGroupKey(t *testing.T) { {name: "ipv6 rfc6052 well-known prefix with ipv4", host: "64:ff9b::0c01:0203", expected: "12.1.0.0"}, {name: "ipv6 rfc6145 translated ipv4", host: "::ffff:0:0c01:0203", expected: "12.1.0.0"}, - // TORv2 - {name: "ipv6 tor onioncat", host: "fd87:d87e:eb43:1234::5678", expected: "torv2:2"}, - {name: "ipv6 tor onioncat 2", host: "fd87:d87e:eb43:1245::6789", expected: "torv2:2"}, - {name: "ipv6 tor onioncat 3", host: "fd87:d87e:eb43:1345::6789", expected: "torv2:3"}, - // TORv3 { name: "torv3", diff --git a/peer/peer.go b/peer/peer.go index 846bab5b91..2040b08eb9 100644 --- a/peer/peer.go +++ b/peer/peer.go @@ -1813,16 +1813,15 @@ func (p *Peer) readRemoteVersionMsg() error { return nil } -// addrmgrToWireNetAddress converts an IPv4, IPv6, or TORv2 address manager -// network address to a wire network address. The host name must not match the +// addrmgrToWireNetAddress converts an IPv4 or IPv6 address manager network +// address to a wire network address. The host name must not match the // configured proxy host name. // // If the address cannot be converted, an IPv4 network address consisting of all // zeroes is returned. func addrmgrToWireNetAddress(addr *addrmgr.NetAddress, proxyAddr string) *wire.NetAddress { if addr.Type != addrmgr.IPv4Address && - addr.Type != addrmgr.IPv6Address && - addr.Type != addrmgr.TORv2Address { + addr.Type != addrmgr.IPv6Address { return wire.NewNetAddressIPPort(zeroIPv4, 0, addr.Services) } diff --git a/server.go b/server.go index 0bb02dbf45..d2e0df1e6f 100644 --- a/server.go +++ b/server.go @@ -361,10 +361,9 @@ func (ps *peerState) forAllPeers(closure func(sp *serverPeer)) { } // hostToNetAddress parses and returns an address manager network address given -// a hostname in a supported format (IPv4, IPv6, TORv2). If the hostname -// cannot be immediately converted from a known address format, it will be -// resolved using a DNS lookup function. If it cannot be resolved, an error is -// returned. +// a hostname in a supported format. If the hostname cannot be immediately +// converted from a known address format, it will be resolved using a DNS lookup +// function. If it cannot be resolved, an error is returned. func (cfg *config) hostToNetAddress(host string, port uint16, services wire.ServiceFlag) (*addrmgr.NetAddress, error) { networkID, addrBytes, err := addrmgr.ParseHost(host) if err != nil { @@ -740,7 +739,6 @@ func isSupportedNetAddressTypeV1(netAddressType addrmgr.NetAddressType) bool { switch netAddressType { case addrmgr.IPv4Address: case addrmgr.IPv6Address: - case addrmgr.TORv2Address: return true } return false diff --git a/server_test.go b/server_test.go index f480f858b0..e533a26fb9 100644 --- a/server_test.go +++ b/server_test.go @@ -9,11 +9,23 @@ import ( "net" "reflect" "testing" + "time" "github.com/decred/dcrd/addrmgr/v2" "github.com/decred/dcrd/wire" ) +var zeroTime = time.Time{} + +var ( + torV3AddressString = "xa4r2iadxm55fbnqgwwi5mymqdcofiu3w6rpbtqn7b2dyn7mgwj64jyd.onion" + torV3AddressBytes = []byte{ + 0xb8, 0x39, 0x1d, 0x20, 0x03, 0xbb, 0x3b, 0xd2, + 0x85, 0xb0, 0x35, 0xac, 0x8e, 0xb3, 0x0c, 0x80, + 0xc4, 0xe2, 0xa2, 0x9b, 0xb7, 0xa2, 0xf0, 0xce, + 0x0d, 0xf8, 0x74, 0x3c, 0x37, 0xec, 0x35, 0x93} +) + // TestHostToNetAddress ensures that HostToNetAddress behaves as expected // given valid and invalid host name arguments. func TestHostToNetAddress(t *testing.T) { @@ -29,66 +41,54 @@ func TestHostToNetAddress(t *testing.T) { lookupFunc func(host string) ([]net.IP, error) wantErr bool want *addrmgr.NetAddress - }{ - { - name: "valid onion address", - host: "a5ccbdkubbr2jlcp.onion", - port: 8333, - lookupFunc: nil, - wantErr: false, - want: addrmgr.NewNetAddressIPPort( - net.ParseIP("fd87:d87e:eb43:744:208d:5408:63a4:ac4f"), 8333, - services), - }, - { - name: "invalid onion address", - host: "0000000000000000.onion", - port: 8333, - lookupFunc: nil, - wantErr: true, - want: nil, - }, - { - name: "unresolvable host name", - host: hostnameForLookup, - port: 8333, - lookupFunc: func(host string) ([]net.IP, error) { - return nil, fmt.Errorf("unresolvable host %v", host) - }, - wantErr: true, - want: nil, + }{{ + name: "valid TORv3 onion address", + host: torV3AddressString, + port: 8333, + lookupFunc: nil, + wantErr: false, + want: (func() *addrmgr.NetAddress { + addr, _ := addrmgr.NewNetAddressByType(addrmgr.TORv3Address, + torV3AddressBytes, 8333, zeroTime, services) + return addr + })(), + }, { + name: "unresolvable host name", + host: hostnameForLookup, + port: 8333, + lookupFunc: func(host string) ([]net.IP, error) { + return nil, fmt.Errorf("unresolvable host %v", host) }, - { - name: "not resolved host name", - host: hostnameForLookup, - port: 8333, - lookupFunc: func(host string) ([]net.IP, error) { - return nil, nil - }, - wantErr: true, - want: nil, + wantErr: true, + want: nil, + }, { + name: "not resolved host name", + host: hostnameForLookup, + port: 8333, + lookupFunc: func(host string) ([]net.IP, error) { + return nil, nil }, - { - name: "resolved host name", - host: hostnameForLookup, - port: 8333, - lookupFunc: func(host string) ([]net.IP, error) { - return []net.IP{net.ParseIP("127.0.0.1")}, nil - }, - wantErr: false, - want: addrmgr.NewNetAddressIPPort(net.ParseIP("127.0.0.1"), 8333, - services), + wantErr: true, + want: nil, + }, { + name: "resolved host name", + host: hostnameForLookup, + port: 8333, + lookupFunc: func(host string) ([]net.IP, error) { + return []net.IP{net.ParseIP("127.0.0.1")}, nil }, - { - name: "valid ip address", - host: "12.1.2.3", - port: 8333, - lookupFunc: nil, - wantErr: false, - want: addrmgr.NewNetAddressIPPort(net.ParseIP("12.1.2.3"), 8333, - services), - }, - } + wantErr: false, + want: addrmgr.NewNetAddressIPPort(net.ParseIP("127.0.0.1"), 8333, + services), + }, { + name: "valid ip address", + host: "12.1.2.3", + port: 8333, + lookupFunc: nil, + wantErr: false, + want: addrmgr.NewNetAddressIPPort(net.ParseIP("12.1.2.3"), 8333, + services), + }} for _, test := range tests { cfg := &config{ @@ -96,12 +96,21 @@ func TestHostToNetAddress(t *testing.T) { onionlookup: test.lookupFunc, } result, err := cfg.hostToNetAddress(test.host, test.port, services) + if test.wantErr == true && err != nil { + continue + } if test.wantErr == true && err == nil { t.Errorf("%q: expected error but one was not returned", test.name) + return + } + if test.wantErr == false && err != nil { + t.Errorf("%q: failed to convert host to network address", test.name) + return } - if !reflect.DeepEqual(result, test.want) { + if !reflect.DeepEqual(result.String(), test.want.String()) { t.Errorf("%q: unexpected result -- got %v, want %v", test.name, result, test.want) + return } } } From 404ec6997d12c7f86e6f22f45b49ad96eedef3ed Mon Sep 17 00:00:00 2001 From: Sef Boukenken Date: Tue, 23 Feb 2021 00:18:15 -0500 Subject: [PATCH 7/9] peer/wire: Add addrv2 and getaddrv2 message types. This commit modifies the wire protocol by adding two new message types addrv2 and getaddrv2 and bumps the wire protocol version to 10. It does not introduce any new network address types and is intended to implement the minimum framework necessary to support easily adding new address types in future commits. These two new message types are intended to eventually replace their older counterparts, which are getaddr and addr respectively. A peer sending a getaddrv2 or addrv2 message with a protocol version less than 10 is in violation of the wire protocol and the peer is disconnected. Similarly, peers advertising a protocol version greater than or equal to 10 that send an addr or gettaddr message are in violation of the wire protocol and are disconnected. A getaddrv2 message is similar in structure and purpose to a getaddr message in that it has no payload and functions as a request for a peer to reply with an addrv2 message if it has addresses to share. An addrv2 message is similar in structure and function to an addr message with a few key differences: - Port is now encoded as a little endian value rather than big endian. - Timestamp is encoded as a 64 bit value rather than a 32 bit value. - A network address type field is now serialized with each address to indicate the length and type of the address it precedes. - A message with zero addresses is no longer serializable. - Addresses are serialized their most compact form, rather than always being encoded into a 16 byte structure. --- peer/doc.go | 20 +-- peer/go.mod | 1 + peer/log.go | 21 ++- peer/peer.go | 53 ++++++- peer/peer_test.go | 273 +++++++++++++++++++--------------- server.go | 183 +++++++++++++++++++++-- wire/common.go | 6 +- wire/error.go | 5 +- wire/go.sum | 10 ++ wire/message.go | 10 +- wire/message_test.go | 17 ++- wire/msgaddr.go | 17 ++- wire/msgaddr_test.go | 50 +++++-- wire/msgaddrv2.go | 279 +++++++++++++++++++++++++++++++++++ wire/msgaddrv2_test.go | 299 ++++++++++++++++++++++++++++++++++++++ wire/msggetaddr.go | 17 ++- wire/msggetaddr_test.go | 45 +++++- wire/msggetaddrv2.go | 63 ++++++++ wire/msggetaddrv2_test.go | 95 ++++++++++++ wire/netaddressv2.go | 51 +++++++ wire/protocol.go | 6 +- 21 files changed, 1354 insertions(+), 167 deletions(-) create mode 100644 wire/msgaddrv2.go create mode 100644 wire/msgaddrv2_test.go create mode 100644 wire/msggetaddrv2.go create mode 100644 wire/msggetaddrv2_test.go create mode 100644 wire/netaddressv2.go diff --git a/peer/doc.go b/peer/doc.go index 138d13c7c2..041af59c32 100644 --- a/peer/doc.go +++ b/peer/doc.go @@ -104,16 +104,16 @@ of a most-recently used algorithm. Message Sending Helper Functions In addition to the bare QueueMessage function previously described, the -PushAddrMsg, PushGetBlocksMsg, and PushGetHeadersMsg functions are provided as a -convenience. While it is of course possible to create and send these messages -manually via QueueMessage, these helper functions provided additional useful -functionality that is typically desired. - -For example, the PushAddrMsg function automatically limits the addresses to the -maximum number allowed by the message and randomizes the chosen addresses when -there are too many. This allows the caller to simply provide a slice of known -addresses, such as that returned by the addrmgr package, without having to worry -about the details. +PushAddrMsg, PushAddrMsgV2, PushGetBlocksMsg, and PushGetHeadersMsg functions +are provided as a convenience. While it is of course possible to create and +send these messages manually via QueueMessage, these helper functions provide +additional useful functionality that is typically desired. + +For example, the PushAddrMsg and PushAddrMsgV2 functions automatically limit the +addresses to the maximum number allowed by the message and randomizes the chosen +addresses when there are too many. This allows the caller to simply provide a +slice of known addresses, such as that returned by the addrmgr package, without +having to worry about the details. Finally, the PushGetBlocksMsg and PushGetHeadersMsg functions will construct proper messages using a block locator and ignore back to back duplicate diff --git a/peer/go.mod b/peer/go.mod index d5f52544a0..e390948e7f 100644 --- a/peer/go.mod +++ b/peer/go.mod @@ -18,4 +18,5 @@ replace ( github.com/decred/dcrd/dcrec/secp256k1/v4 => ../dcrec/secp256k1 github.com/decred/dcrd/dcrutil/v4 => ../dcrutil github.com/decred/dcrd/txscript/v4 => ../txscript + github.com/decred/dcrd/wire => ../wire ) diff --git a/peer/log.go b/peer/log.go index 9d86296c4b..fd56b9c73a 100644 --- a/peer/log.go +++ b/peer/log.go @@ -35,6 +35,15 @@ func directionString(inbound bool) string { return "outbound" } +// pickNoun returns the singular or plural form of a noun depending on the +// provided count. +func pickNoun(n uint64, singular, plural string) string { + if n == 1 { + return singular + } + return plural +} + // formatLockTime returns a transaction lock time as a human-readable string. func formatLockTime(lockTime uint32) string { // The lock time field of a transaction is either a block height at @@ -111,8 +120,18 @@ func messageSummary(msg wire.Message) string { case *wire.MsgGetAddr: // No summary. + case *wire.MsgGetAddrV2: + // No summary. + case *wire.MsgAddr: - return fmt.Sprintf("%d addr", len(msg.AddrList)) + addrLen := len(msg.AddrList) + addrNoun := pickNoun(uint64(addrLen), "addr", "addrs") + return fmt.Sprintf("%d %s", addrLen, addrNoun) + + case *wire.MsgAddrV2: + addrLen := len(msg.AddrList) + addrNoun := pickNoun(uint64(addrLen), "addr", "addrs") + return fmt.Sprintf("%d %s", addrLen, addrNoun) case *wire.MsgPing: // No summary - perhaps add nonce. diff --git a/peer/peer.go b/peer/peer.go index 2040b08eb9..6bd185a468 100644 --- a/peer/peer.go +++ b/peer/peer.go @@ -27,7 +27,7 @@ import ( const ( // MaxProtocolVersion is the max protocol version the peer supports. - MaxProtocolVersion = wire.InitStateVersion + MaxProtocolVersion = wire.AddrV2Version // outputBufferSize is the number of elements the output channels use. outputBufferSize = 5000 @@ -99,9 +99,15 @@ type MessageListeners struct { // OnGetAddr is invoked when a peer receives a getaddr wire message. OnGetAddr func(p *Peer, msg *wire.MsgGetAddr) + // OnGetAddrV2 is invoked when a peer receives a getaddrV2 wire message. + OnGetAddrV2 func(p *Peer, msg *wire.MsgGetAddrV2) + // OnAddr is invoked when a peer receives an addr wire message. OnAddr func(p *Peer, msg *wire.MsgAddr) + // OnAddrV2 is invoked when a peer receives an addrV2 wire message. + OnAddrV2 func(p *Peer, msg *wire.MsgAddrV2) + // OnPing is invoked when a peer receives a ping wire message. OnPing func(p *Peer, msg *wire.MsgPing) @@ -808,6 +814,41 @@ func (p *Peer) PushAddrMsg(addresses []*wire.NetAddress) ([]*wire.NetAddress, er return msg.AddrList, nil } +// PushAddrV2Msg sends an addrv2 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 number allowed by the message and randomizes the chosen addresses +// when there are too many. It returns the addresses that were actually sent +// and no message will be sent if there are no entries in the provided addresses +// slice. +// +// This function is safe for concurrent access. +func (p *Peer) PushAddrV2Msg(addresses []*wire.NetAddressV2) []*wire.NetAddressV2 { + // Nothing to send. + if len(addresses) == 0 { + return nil + } + + msg := wire.NewMsgAddrV2() + msg.AddrList = make([]*wire.NetAddressV2, len(addresses)) + copy(msg.AddrList, addresses) + + // Randomize the addresses sent if there are more than the maximum allowed. + if len(msg.AddrList) > wire.MaxAddrPerV2Msg { + // Shuffle the address list. + for i := range msg.AddrList { + j := rand.Intn(i + 1) + msg.AddrList[i], msg.AddrList[j] = msg.AddrList[j], msg.AddrList[i] + } + + // Truncate it to the maximum size. + msg.AddrList = msg.AddrList[:wire.MaxAddrPerV2Msg] + } + + p.QueueMessage(msg, nil) + return msg.AddrList +} + // PushGetBlocksMsg sends a getblocks message for the provided block locator // and stop hash. It will ignore back-to-back duplicate requests. // @@ -1275,6 +1316,16 @@ out: p.cfg.Listeners.OnVerAck(p, msg) } + case *wire.MsgGetAddrV2: + if p.cfg.Listeners.OnGetAddrV2 != nil { + p.cfg.Listeners.OnGetAddrV2(p, msg) + } + + case *wire.MsgAddrV2: + if p.cfg.Listeners.OnAddrV2 != nil { + p.cfg.Listeners.OnAddrV2(p, msg) + } + case *wire.MsgGetAddr: if p.cfg.Listeners.OnGetAddr != nil { p.cfg.Listeners.OnGetAddr(p, msg) diff --git a/peer/peer_test.go b/peer/peer_test.go index c32e3c7b68..ae96c96486 100644 --- a/peer/peer_test.go +++ b/peer/peer_test.go @@ -20,6 +20,8 @@ import ( "github.com/decred/go-socks/socks" ) +var zeroTime = time.Time{} + // conn mocks a network connection by implementing the net.Conn interface. It // is used to test peer connection without actually opening a network // connection. @@ -272,8 +274,7 @@ func TestPeerConnection(t *testing.T) { } return inPeer, outPeer, nil }, - }, - { + }, { "socks proxy", func() (*Peer, *Peer, error) { inConn, outConn := pipe( @@ -326,6 +327,12 @@ func TestPeerListeners(t *testing.T) { ok := make(chan wire.Message, 20) peerCfg := &Config{ Listeners: MessageListeners{ + OnGetAddrV2: func(p *Peer, msg *wire.MsgGetAddrV2) { + ok <- msg + }, + OnAddrV2: func(p *Peer, msg *wire.MsgAddrV2) { + ok <- msg + }, OnGetAddr: func(p *Peer, msg *wire.MsgGetAddr) { ok <- msg }, @@ -451,127 +458,142 @@ func TestPeerListeners(t *testing.T) { return } + const pver = wire.ProtocolVersion tests := []struct { listener string msg wire.Message - }{ - { - "OnGetAddr", - wire.NewMsgGetAddr(), - }, - { - "OnAddr", - wire.NewMsgAddr(), - }, - { - "OnPing", - wire.NewMsgPing(42), - }, - { - "OnPong", - wire.NewMsgPong(42), - }, - { - "OnMemPool", - wire.NewMsgMemPool(), - }, - { - "OnTx", - wire.NewMsgTx(), - }, - { - "OnBlock", - wire.NewMsgBlock(wire.NewBlockHeader(0, &chainhash.Hash{}, - &chainhash.Hash{}, &chainhash.Hash{}, 1, [6]byte{}, - 1, 1, 1, 1, 1, 1, 1, 1, 1, [32]byte{}, - binary.LittleEndian.Uint32([]byte{0xb0, 0x1d, 0xfa, 0xce}))), - }, - { - "OnInv", - wire.NewMsgInv(), - }, - { - "OnHeaders", - wire.NewMsgHeaders(), - }, - { - "OnNotFound", - wire.NewMsgNotFound(), - }, - { - "OnGetData", - wire.NewMsgGetData(), - }, - { - "OnGetBlocks", - wire.NewMsgGetBlocks(&chainhash.Hash{}), - }, - { - "OnGetHeaders", - wire.NewMsgGetHeaders(), - }, - { - "OnGetCFilter", - wire.NewMsgGetCFilter(&chainhash.Hash{}, - wire.GCSFilterRegular), - }, - { - "OnGetCFHeaders", - wire.NewMsgGetCFHeaders(), - }, - { - "OnGetCFTypes", - wire.NewMsgGetCFTypes(), - }, - { - "OnCFilter", - wire.NewMsgCFilter(&chainhash.Hash{}, - wire.GCSFilterRegular, []byte("payload")), - }, - { - "OnCFHeaders", - wire.NewMsgCFHeaders(), - }, - { - "OnCFTypes", - wire.NewMsgCFTypes([]wire.FilterType{ - wire.GCSFilterRegular, wire.GCSFilterExtended, - }), - }, - { - "OnFeeFilter", - wire.NewMsgFeeFilter(15000), - }, - { - "OnGetCFilterV2", - wire.NewMsgGetCFilterV2(&chainhash.Hash{}), - }, - { - "OnCFilterV2", - wire.NewMsgCFilterV2(&chainhash.Hash{}, nil, 0, nil), - }, - // only one version message is allowed - // only one verack message is allowed - { - "OnReject", - wire.NewMsgReject("block", wire.RejectDuplicate, "dupe block"), - }, - { - "OnSendHeaders", - wire.NewMsgSendHeaders(), - }, - { - "OnGetInitState", - wire.NewMsgGetInitState(), - }, - { - "OnInitState", - wire.NewMsgInitState(), - }, - } + pver uint32 + }{{ + listener: "OnGetAddrV2", + msg: wire.NewMsgGetAddrV2(), + pver: pver, + }, { + listener: "OnAddrV2", + msg: func() *wire.MsgAddrV2 { + // MsgAddrV2 must have at least one address to be valid. + msgAddrV2 := wire.NewMsgAddrV2() + addr := wire.NewNetAddressV2(wire.IPv4Address, + net.ParseIP("127.0.0.1"), 8333, zeroTime, wire.SFNodeNetwork) + msgAddrV2.AddAddress(addr) + return msgAddrV2 + }(), + pver: pver, + }, { + listener: "OnGetAddr", + msg: wire.NewMsgGetAddr(), + pver: wire.AddrV2Version - 1, + }, { + listener: "OnAddr", + msg: wire.NewMsgAddr(), + pver: wire.AddrV2Version - 1, + }, { + listener: "OnPing", + msg: wire.NewMsgPing(42), + pver: pver, + }, { + listener: "OnPong", + msg: wire.NewMsgPong(42), + pver: pver, + }, { + listener: "OnMemPool", + msg: wire.NewMsgMemPool(), + pver: pver, + }, { + listener: "OnTx", + msg: wire.NewMsgTx(), + pver: pver, + }, { + listener: "OnBlock", + msg: wire.NewMsgBlock(wire.NewBlockHeader(0, &chainhash.Hash{}, + &chainhash.Hash{}, &chainhash.Hash{}, 1, [6]byte{}, + 1, 1, 1, 1, 1, 1, 1, 1, 1, [32]byte{}, + binary.LittleEndian.Uint32([]byte{0xb0, 0x1d, 0xfa, 0xce}))), + pver: pver, + }, { + listener: "OnInv", + msg: wire.NewMsgInv(), + pver: pver, + }, { + listener: "OnHeaders", + msg: wire.NewMsgHeaders(), + pver: pver, + }, { + listener: "OnNotFound", + msg: wire.NewMsgNotFound(), + pver: pver, + }, { + listener: "OnGetData", + msg: wire.NewMsgGetData(), + pver: pver, + }, { + listener: "OnGetBlocks", + msg: wire.NewMsgGetBlocks(&chainhash.Hash{}), + pver: pver, + }, { + listener: "OnGetHeaders", + msg: wire.NewMsgGetHeaders(), + pver: pver, + }, { + listener: "OnGetCFilter", + msg: wire.NewMsgGetCFilter(&chainhash.Hash{}, + wire.GCSFilterRegular), + pver: pver, + }, { + listener: "OnGetCFHeaders", + msg: wire.NewMsgGetCFHeaders(), + pver: pver, + }, { + listener: "OnGetCFTypes", + msg: wire.NewMsgGetCFTypes(), + pver: pver, + }, { + listener: "OnCFilter", + msg: wire.NewMsgCFilter(&chainhash.Hash{}, + wire.GCSFilterRegular, []byte("payload")), + pver: pver, + }, { + listener: "OnCFHeaders", + msg: wire.NewMsgCFHeaders(), + pver: pver, + }, { + listener: "OnCFTypes", + msg: wire.NewMsgCFTypes([]wire.FilterType{ + wire.GCSFilterRegular, wire.GCSFilterExtended, + }), + pver: pver, + }, { + listener: "OnFeeFilter", + msg: wire.NewMsgFeeFilter(15000), + pver: pver, + }, { + listener: "OnGetCFilterV2", + msg: wire.NewMsgGetCFilterV2(&chainhash.Hash{}), + pver: pver, + }, { + listener: "OnCFilterV2", + msg: wire.NewMsgCFilterV2(&chainhash.Hash{}, nil, 0, nil), + pver: pver, + }, { + listener: "OnReject", + msg: wire.NewMsgReject("block", wire.RejectDuplicate, "dupe block"), + pver: wire.RemoveRejectVersion - 1, + }, { + listener: "OnSendHeaders", + msg: wire.NewMsgSendHeaders(), + pver: pver, + }, { + listener: "OnGetInitState", + msg: wire.NewMsgGetInitState(), + pver: pver, + }, { + listener: "OnInitState", + msg: wire.NewMsgInitState(), + pver: pver, + }} t.Logf("Running %d tests", len(tests)) for _, test := range tests { - // Queue the test message + inPeer.protocolVersion = test.pver + outPeer.protocolVersion = test.pver outPeer.QueueMessage(test.msg, nil) select { case <-ok: @@ -688,6 +710,20 @@ func TestOutboundPeer(t *testing.T) { t.Errorf("PushAddrMsg: unexpected err %v\n", err) return } + + var addrsV2 []*wire.NetAddressV2 + for i := 0; i < 5; i++ { + na := &wire.NetAddressV2{} + addrsV2 = append(addrsV2, na) + } + + pushedAddrs := p2.PushAddrV2Msg(addrsV2) + if len(pushedAddrs) != len(addrsV2) { + t.Errorf("PushAddrV2Msg: expected %d addrs, got %d\n", len(addrsV2), + len(pushedAddrs)) + return + } + if err := p2.PushGetBlocksMsg(nil, &chainhash.Hash{}); err != nil { t.Errorf("PushGetBlocksMsg: unexpected err %v\n", err) return @@ -699,6 +735,7 @@ func TestOutboundPeer(t *testing.T) { // Test Queue Messages p2.QueueMessage(wire.NewMsgGetAddr(), nil) + p2.QueueMessage(wire.NewMsgGetAddrV2(), nil) p2.QueueMessage(wire.NewMsgPing(1), nil) p2.QueueMessage(wire.NewMsgMemPool(), nil) p2.QueueMessage(wire.NewMsgGetData(), nil) diff --git a/server.go b/server.go index d2e0df1e6f..e7e2da6b17 100644 --- a/server.go +++ b/server.go @@ -74,7 +74,7 @@ const ( connectionRetryInterval = time.Second * 5 // maxProtocolVersion is the max protocol version the server supports. - maxProtocolVersion = wire.InitStateVersion + maxProtocolVersion = wire.AddrV2Version // These fields are used to track known addresses on a per-peer basis. // @@ -657,6 +657,23 @@ func wireToAddrmgrNetAddresses(netAddr []*wire.NetAddress) []*addrmgr.NetAddress return addrs } +// wireToAddrmgrNetAddressesV2 converts a collection of version 2 wire network +// addresses to a collection of address manager network addresses. If any +// addresses are not able to be converted, an error is returned. +func wireToAddrmgrNetAddressesV2(netAddr []*wire.NetAddressV2) ([]*addrmgr.NetAddress, error) { + addrs := make([]*addrmgr.NetAddress, len(netAddr)) + for i, wireAddr := range netAddr { + addrmgrAddrType := addrmgr.NetAddressType(wireAddr.Type) + addr, err := addrmgr.NewNetAddressByType(addrmgrAddrType, wireAddr.IP, + wireAddr.Port, wireAddr.Timestamp, wireAddr.Services) + if err != nil { + return nil, err + } + addrs[i] = addr + } + return addrs, nil +} + // addrmgrToWireNetAddress converts an address manager net address to a wire net // address. func addrmgrToWireNetAddress(netAddr *addrmgr.NetAddress) *wire.NetAddress { @@ -664,8 +681,18 @@ func addrmgrToWireNetAddress(netAddr *addrmgr.NetAddress) *wire.NetAddress { netAddr.IP, netAddr.Port) } +// addrmgrToWireNetAddressV2 converts an address manager net address to a wire net +// address. +func addrmgrToWireNetAddressV2(netAddr *addrmgr.NetAddress) *wire.NetAddressV2 { + wireNetAddrType := wire.NetAddressType(netAddr.Type) + return wire.NewNetAddressV2(wireNetAddrType, netAddr.IP, netAddr.Port, + netAddr.Timestamp, netAddr.Services) +} + // pushAddrMsg sends an addr message to the connected peer using the provided -// addresses. +// addresses. Any network address passed to this function must have +// already been filtered to ensure that it is supported by the protocol +// version of the peer it will be sent to. func (sp *serverPeer) pushAddrMsg(addresses []*addrmgr.NetAddress) { // Filter addresses already known to the peer. addrs := make([]*wire.NetAddress, 0, len(addresses)) @@ -686,6 +713,29 @@ func (sp *serverPeer) pushAddrMsg(addresses []*addrmgr.NetAddress) { sp.addKnownAddresses(knownNetAddrs) } +// pushAddrV2Msg sends an addrv2 message to the connected peer using the +// provided addresses. Any network address passed to this function must have +// already been filtered to ensure that it is supported by the protocol +// version of the peer it will be sent to. Failure to do this may result in +// the current local node being banned by the remote peer. +func (sp *serverPeer) pushAddrV2Msg(addresses []*addrmgr.NetAddress) { + // Filter addresses already known to the peer. + addrs := make([]*wire.NetAddressV2, 0, len(addresses)) + for _, addr := range addresses { + if !sp.addressKnown(addr) { + addrV2 := addrmgrToWireNetAddressV2(addr) + addrs = append(addrs, addrV2) + } + } + pushedAddrs := sp.PushAddrV2Msg(addrs) + + // Convert the addresses sent to the peer back to address manager network + // address types. Since the addresses were originally sourced from the + // address manager, converting them back will not result in an error. + addrmgrPushedAddrs, _ := wireToAddrmgrNetAddressesV2(pushedAddrs) + sp.addKnownAddresses(addrmgrPushedAddrs) +} + // addBanScore increases the persistent and decaying ban score fields by the // values passed as parameters. If the resulting score exceeds half of the ban // threshold, a warning is logged including the reason provided. Further, if @@ -734,7 +784,7 @@ func hasServices(advertised, desired wire.ServiceFlag) bool { } // isSupportedNetAddressTypeV1 returns whether the provided address manager -// network address type is supported by the addr wire message. +// network address type is supported by the version 1 wire network address type. func isSupportedNetAddressTypeV1(netAddressType addrmgr.NetAddressType) bool { switch netAddressType { case addrmgr.IPv4Address: @@ -744,11 +794,26 @@ func isSupportedNetAddressTypeV1(netAddressType addrmgr.NetAddressType) bool { return false } +// isSupportedNetAddressTypeV2 returns whether the provided address manager +// network address type is supported by a version 2 wire network address type. +func isSupportedNetAddressTypeV2(netAddressType addrmgr.NetAddressType) bool { + switch netAddressType { + case addrmgr.IPv4Address: + case addrmgr.IPv6Address: + return true + } + return false +} + // getNetAddressTypeFilter returns a function that determines whether a // specific address manager network address type is supported by the // provided protocol version. -func getNetAddressTypeFilter(pver uint32) addrmgr.NetAddressTypeFilter { - return isSupportedNetAddressTypeV1 +func getNetAddressTypeFilter(protocolVersion uint32) addrmgr.NetAddressTypeFilter { + if protocolVersion < wire.AddrV2Version { + return isSupportedNetAddressTypeV1 + } else { + return isSupportedNetAddressTypeV2 + } } // OnVersion is invoked when a peer receives a version wire message and is used @@ -810,16 +875,26 @@ func (sp *serverPeer) OnVersion(_ *peer.Peer, msg *wire.MsgVersion) { naTypeFilter := getNetAddressTypeFilter(msgProtocolVersion) lna := addrManager.GetBestLocalAddress(remoteAddr, naTypeFilter) if lna.IsRoutable() { - // Filter addresses the peer already knows about. addresses := []*addrmgr.NetAddress{lna} - sp.pushAddrMsg(addresses) + if msgProtocolVersion >= wire.AddrV2Version { + sp.pushAddrV2Msg(addresses) + } else { + sp.pushAddrMsg(addresses) + } + } else { + srvrLog.Debugf("Local address %s is not routable and will not "+ + "be broadcast to outbound peer %v", lna.Key(), sp.Addr()) } } // Request known addresses if the server address manager needs // more. if addrManager.NeedMoreAddresses() { - sp.QueueMessage(wire.NewMsgGetAddr(), nil) + if msgProtocolVersion >= wire.AddrV2Version { + sp.QueueMessage(wire.NewMsgGetAddrV2(), nil) + } else { + sp.QueueMessage(wire.NewMsgGetAddr(), nil) + } } // Mark the address as a known good address. @@ -1084,7 +1159,7 @@ func (sp *serverPeer) OnGetInitState(p *peer.Peer, msg *wire.MsgGetInitState) { sp.QueueMessage(initMsg, nil) } -// OnInitState is invoked when a peer receives a initstate wire message. It +// OnInitState is invoked when a peer receives an initstate wire message. It // requests the data advertised in the message from the peer. func (sp *serverPeer) OnInitState(p *peer.Peer, msg *wire.MsgInitState) { err := sp.server.syncManager.RequestFromPeer(sp.Peer, msg.BlockHashes, @@ -1443,6 +1518,45 @@ func (sp *serverPeer) OnGetAddr(p *peer.Peer, msg *wire.MsgGetAddr) { sp.pushAddrMsg(addrCache) } +// OnGetAddrV2 is invoked when a peer receives a getaddrv2 wire message and is +// used to provide the peer with known addresses from the address manager. +func (sp *serverPeer) OnGetAddrV2(p *peer.Peer, msg *wire.MsgGetAddrV2) { + // Don't return any addresses when running on the simulation and regression + // test networks. This helps prevent the networks from becoming another + // public test network since they will not be able to learn about other + // peers that have not specifically been provided. + if cfg.SimNet || cfg.RegNet { + return + } + + // Do not accept getaddrv2 requests from outbound peers. This reduces + // fingerprinting attacks. + if !p.Inbound() { + peerLog.Errorf("Received getaddrv2 request from outbound peer %s", + sp.Peer) + sp.server.BanPeer(sp) + return + } + + // Only respond with addresses once per connection. This helps reduce + // traffic and further reduces fingerprinting attacks. + if sp.addrsSent { + peerLog.Errorf("Received more than one getaddrv2 request from %s", + sp.Peer) + sp.server.BanPeer(sp) + return + } + sp.addrsSent = true + + // Get the current known addresses from the address manager. + pver := sp.ProtocolVersion() + naTypeFilter := getNetAddressTypeFilter(pver) + addrCache := sp.server.addrManager.AddressCache(naTypeFilter) + + // Push the addresses. + sp.pushAddrV2Msg(addrCache) +} + // OnAddr is invoked when a peer receives an addr wire message and is used to // notify the server about advertised addresses. func (sp *serverPeer) OnAddr(p *peer.Peer, msg *wire.MsgAddr) { @@ -1490,6 +1604,52 @@ func (sp *serverPeer) OnAddr(p *peer.Peer, msg *wire.MsgAddr) { sp.server.addrManager.AddAddresses(addrList, remoteAddr) } +// OnAddrV2 is invoked when a peer receives an addrv2 wire message and is used +// to notify the server about advertised addresses. +func (sp *serverPeer) OnAddrV2(p *peer.Peer, msg *wire.MsgAddrV2) { + // Ignore addresses when running on the simulation and regression test + // networks. This helps prevent the networks from becoming another public + // test network since they will not be able to learn about other peers that + // have not specifically been provided. + if cfg.SimNet || cfg.RegNet { + return + } + + // Do not add more addresses if the peer is disconnecting. + if !p.Connected() { + peerLog.Debugf("Not adding addresses from disconnecting peer %v", sp) + return + } + + addrList, err := wireToAddrmgrNetAddressesV2(msg.AddrList) + if err != nil { + // If the peer sent an address that cannot be used to construct a valid + // address manager network address, disconnect and ban the peer. + peerLog.Errorf("Failed to add address from peer %v: %v", sp, err) + sp.server.BanPeer(sp) + return + } + + now := time.Now() + for _, netAddr := range addrList { + // Set the timestamp to 5 days ago if it's more than 10 minutes + // in the future so this address is one of the first to be + // removed when space is needed. + if netAddr.Timestamp.After(now.Add(time.Minute * 10)) { + netAddr.Timestamp = now.Add(-1 * time.Hour * 24 * 5) + } + + // Add address to known addresses for this peer. + sp.addKnownAddress(netAddr) + } + + // Add addresses to server address manager. The address manager handles + // the details of things such as preventing duplicate addresses, max + // addresses, and last seen updates. + srcAddr := p.NA() + sp.server.addrManager.AddAddresses(addrList, srcAddr) +} + // 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(p *peer.Peer, bytesRead int, msg wire.Message, err error) { @@ -1717,13 +1877,14 @@ func (s *server) handleAddPeerMsg(state *peerState, sp *serverPeer) bool { return false } - // Disconnect banned peers. host, _, err := net.SplitHostPort(sp.Addr()) if err != nil { srvrLog.Debugf("can't split hostport %v", err) sp.Disconnect() return false } + + // Disconnect banned peers. if banEnd, ok := state.banned[host]; ok { if time.Now().Before(banEnd) { srvrLog.Debugf("Peer %s is banned for another %v - disconnecting", @@ -2231,6 +2392,8 @@ func newPeerConfig(sp *serverPeer) *peer.Config { OnGetCFilterV2: sp.OnGetCFilterV2, OnGetCFHeaders: sp.OnGetCFHeaders, OnGetCFTypes: sp.OnGetCFTypes, + OnGetAddrV2: sp.OnGetAddrV2, + OnAddrV2: sp.OnAddrV2, OnGetAddr: sp.OnGetAddr, OnAddr: sp.OnAddr, OnRead: sp.OnRead, diff --git a/wire/common.go b/wire/common.go index 404c72f3d8..095c816a59 100644 --- a/wire/common.go +++ b/wire/common.go @@ -1,5 +1,5 @@ // Copyright (c) 2013-2016 The btcsuite developers -// Copyright (c) 2015-2020 The Decred developers +// Copyright (c) 2015-2021 The Decred developers // Use of this source code is governed by an ISC // license that can be found in the LICENSE file. @@ -277,7 +277,7 @@ func readElement(r io.Reader, element interface{}) error { *e = int64Time(time.Unix(int64(rv), 0)) return nil - // Message header checksum. + // Message header checksum or IPv4 address. case *[4]byte: _, err := io.ReadFull(r, e[:]) if err != nil { @@ -417,7 +417,7 @@ func writeElement(w io.Writer, element interface{}) error { } return nil - // Message header checksum. + // Message header checksum or IPv4 address. case [4]byte: _, err := w.Write(e[:]) if err != nil { diff --git a/wire/error.go b/wire/error.go index df41e642ff..7c1e9d3e6d 100644 --- a/wire/error.go +++ b/wire/error.go @@ -1,5 +1,5 @@ // Copyright (c) 2013-2015 The btcsuite developers -// Copyright (c) 2015-2020 The Decred developers +// Copyright (c) 2015-2021 The Decred developers // Use of this source code is governed by an ISC // license that can be found in the LICENSE file. @@ -48,6 +48,9 @@ const ( // is received. ErrPayloadChecksum + // ErrTooFewAddrs is returned when an address list has zero addresses. + ErrTooFewAddrs + // ErrTooManyAddrs is returned when an address list exceeds the maximum // allowed. ErrTooManyAddrs diff --git a/wire/go.sum b/wire/go.sum index 1fb8a4c466..9b76bf389e 100644 --- a/wire/go.sum +++ b/wire/go.sum @@ -4,3 +4,13 @@ github.com/decred/dcrd/chaincfg/chainhash v1.0.2 h1:rt5Vlq/jM3ZawwiacWjPa+smINyL github.com/decred/dcrd/chaincfg/chainhash v1.0.2/go.mod h1:BpbrGgrPTr3YJYRN3Bm+D9NuaFd+zGyNeIKgrhCXK60= github.com/decred/dcrd/crypto/blake256 v1.0.0 h1:/8DMNYp9SGi5f0w7uCm6d6M4OU2rGFK09Y2A4Xv7EE0= github.com/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn47hh8kt6rqSlvmrXFAc= +github.com/decred/slog v1.1.0 h1:uz5ZFfmaexj1rEDgZvzQ7wjGkoSPjw2LCh8K+K1VrW4= +github.com/decred/slog v1.1.0/go.mod h1:kVXlGnt6DHy2fV5OjSeuvCJ0OmlmTF6LFpEPMu/fOY0= +golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2 h1:It14KIkyBFYkHkwZ7k45minvA9aorojkyjGk9KJ5B/w= +golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68 h1:nxC68pudNYkKU6jWhgrqdreuFiOQWj1Fs7T3VrH4Pjw= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= diff --git a/wire/message.go b/wire/message.go index 09a362071c..dff0265f85 100644 --- a/wire/message.go +++ b/wire/message.go @@ -1,5 +1,5 @@ // Copyright (c) 2013-2016 The btcsuite developers -// Copyright (c) 2015-2020 The Decred developers +// Copyright (c) 2015-2021 The Decred developers // Use of this source code is governed by an ISC // license that can be found in the LICENSE file. @@ -30,6 +30,8 @@ const MaxMessagePayload = (1024 * 1024 * 32) // 32MB const ( CmdVersion = "version" CmdVerAck = "verack" + CmdGetAddrV2 = "getaddrv2" + CmdAddrV2 = "addrv2" CmdGetAddr = "getaddr" CmdAddr = "addr" CmdGetBlocks = "getblocks" @@ -84,6 +86,12 @@ func makeEmptyMessage(command string) (Message, error) { case CmdVerAck: msg = &MsgVerAck{} + case CmdGetAddrV2: + msg = &MsgGetAddrV2{} + + case CmdAddrV2: + msg = &MsgAddrV2{} + case CmdGetAddr: msg = &MsgGetAddr{} diff --git a/wire/message_test.go b/wire/message_test.go index b29f14d418..bb405cc5b3 100644 --- a/wire/message_test.go +++ b/wire/message_test.go @@ -19,6 +19,8 @@ import ( "github.com/decred/dcrd/chaincfg/chainhash" ) +var timestamp = time.Unix(time.Now().Unix(), 0) + // makeHeader is a convenience function to make a message header in the form of // a byte slice. It is used to force errors when reading messages. func makeHeader(dcrnet CurrencyNet, command string, @@ -57,6 +59,11 @@ func TestMessage(t *testing.T) { msgVersion := NewMsgVersion(me, you, 123123, 0) msgVerack := NewMsgVerAck() + msgGetAddrV2 := NewMsgGetAddrV2() + msgAddrV2 := NewMsgAddrV2() + ipv4Addr := NewNetAddressV2(IPv4Address, net.ParseIP("127.0.0.1").To4(), 8333, + timestamp, SFNodeNetwork) + msgAddrV2.AddAddress(ipv4Addr) msgGetAddr := NewMsgGetAddr() msgAddr := NewMsgAddr() msgGetBlocks := NewMsgGetBlocks(&chainhash.Hash{}) @@ -90,8 +97,10 @@ func TestMessage(t *testing.T) { }{ {msgVersion, msgVersion, pver, MainNet, 125}, {msgVerack, msgVerack, pver, MainNet, 24}, - {msgGetAddr, msgGetAddr, pver, MainNet, 24}, - {msgAddr, msgAddr, pver, MainNet, 25}, + {msgGetAddrV2, msgGetAddrV2, pver, MainNet, 24}, + {msgAddrV2, msgAddrV2, pver, MainNet, 48}, + {msgGetAddr, msgGetAddr, AddrV2Version - 1, MainNet, 24}, + {msgAddr, msgAddr, AddrV2Version - 1, MainNet, 25}, {msgGetBlocks, msgGetBlocks, pver, MainNet, 61}, {msgBlock, msgBlock, pver, MainNet, 522}, {msgInv, msgInv, pver, MainNet, 25}, @@ -287,7 +296,7 @@ func TestReadMessageWireErrors(t *testing.T) { // Exceed max allowed payload for a message of a specific type. [5] { exceedTypePayloadBytes, - pver, + AddrV2Version - 1, dcrnet, len(exceedTypePayloadBytes), &MessageError{}, @@ -317,7 +326,7 @@ func TestReadMessageWireErrors(t *testing.T) { // Message with a valid header, but wrong format. [8] { badMessageBytes, - pver, + AddrV2Version - 1, dcrnet, len(badMessageBytes), &MessageError{}, diff --git a/wire/msgaddr.go b/wire/msgaddr.go index 0418a03ed2..78ac319e7f 100644 --- a/wire/msgaddr.go +++ b/wire/msgaddr.go @@ -1,5 +1,5 @@ // Copyright (c) 2013-2015 The btcsuite developers -// Copyright (c) 2015-2020 The Decred developers +// Copyright (c) 2015-2021 The Decred developers // Use of this source code is governed by an ISC // license that can be found in the LICENSE file. @@ -60,6 +60,12 @@ func (msg *MsgAddr) ClearAddresses() { // This is part of the Message interface implementation. func (msg *MsgAddr) BtcDecode(r io.Reader, pver uint32) error { const op = "MsgAddr.BtcDecode" + if pver >= AddrV2Version { + msg := fmt.Sprintf("%s message invalid for protocol version %d", + msg.Command(), pver) + return messageError(op, ErrMsgInvalidForPVer, msg) + } + count, err := ReadVarInt(r, pver) if err != nil { return err @@ -89,6 +95,12 @@ func (msg *MsgAddr) BtcDecode(r io.Reader, pver uint32) error { // This is part of the Message interface implementation. func (msg *MsgAddr) BtcEncode(w io.Writer, pver uint32) error { const op = "MsgAddr.BtcEncode" + if pver >= AddrV2Version { + msg := fmt.Sprintf("%s message invalid for protocol version %d", + msg.Command(), pver) + return messageError(op, ErrMsgInvalidForPVer, msg) + } + // Protocol versions before MultipleAddressVersion only allowed 1 address // per message. count := len(msg.AddrList) @@ -122,6 +134,9 @@ func (msg *MsgAddr) Command() string { // MaxPayloadLength returns the maximum length the payload can be for the // receiver. This is part of the Message interface implementation. func (msg *MsgAddr) MaxPayloadLength(pver uint32) uint32 { + if pver >= AddrV2Version { + return 0 + } // Num addresses (size of varInt for max address per message) + max allowed // addresses * max address size. return uint32(VarIntSerializeSize(MaxAddrPerMsg)) + diff --git a/wire/msgaddr_test.go b/wire/msgaddr_test.go index 97654fe5f4..54922494a5 100644 --- a/wire/msgaddr_test.go +++ b/wire/msgaddr_test.go @@ -1,5 +1,5 @@ // Copyright (c) 2013-2016 The btcsuite developers -// Copyright (c) 2015-2020 The Decred developers +// Copyright (c) 2015-2021 The Decred developers // Use of this source code is governed by an ISC // license that can be found in the LICENSE file. @@ -17,9 +17,40 @@ import ( "github.com/davecgh/go-spew/spew" ) +// TestAddrLatest tests the MsgAddr API against the latest protocol +// version to ensure it is no longer valid. +func TestAddrLatest(t *testing.T) { + pver := ProtocolVersion + msg := NewMsgAddr() + + // Ensure max payload is expected value. + wantPayload := uint32(0) + maxPayload := msg.MaxPayloadLength(pver) + if maxPayload != wantPayload { + t.Errorf("MaxPayloadLength: wrong max payload length for protocol "+ + "version %d - got %v, want %v", pver, maxPayload, wantPayload) + } + + // Ensure encode fails with the latest protocol version. + var buf bytes.Buffer + err := msg.BtcEncode(&buf, pver) + if !errors.Is(err, ErrMsgInvalidForPVer) { + t.Errorf("MsgGetAddr encode unexpected err -- got %v, want %v", err, + ErrMsgInvalidForPVer) + } + + // Ensure decode fails with the latest protocol version. + var readMsg MsgAddr + err = readMsg.BtcDecode(&buf, pver) + if !errors.Is(err, ErrMsgInvalidForPVer) { + t.Errorf("MsgGetAddr decode unexpected err -- got %v, want %v", err, + ErrMsgInvalidForPVer) + } +} + // TestAddr tests the MsgAddr API. func TestAddr(t *testing.T) { - pver := ProtocolVersion + pver := AddrV2Version - 1 // Ensure the command is expected value. wantCmd := "addr" @@ -29,7 +60,7 @@ func TestAddr(t *testing.T) { cmd, wantCmd) } - // Ensure max payload is expected value for latest protocol version. + // Ensure max payload is expected value for last supported protocol version. // Num addresses (size of varInt for max address ) + max allowed addresses. wantPayload := uint32(30003) maxPayload := msg.MaxPayloadLength(pver) @@ -86,8 +117,9 @@ func TestAddr(t *testing.T) { } // TestAddrWire tests the MsgAddr wire encode and decode for various numbers -// of addresses and protocol versions. +// of addresses up to the latest supported protocol version. func TestAddrWire(t *testing.T) { + const lastSupportedProtocolVersion = AddrV2Version - 1 // A couple of NetAddresses to use for testing. na := &NetAddress{ Timestamp: time.Unix(0x495fab29, 0), // 2009-01-03 12:15:05 -0600 CST @@ -132,20 +164,20 @@ func TestAddrWire(t *testing.T) { buf []byte // Wire encoding pver uint32 // Protocol version for wire encoding }{ - // Latest protocol version with no addresses. + // Last supported protocol version with no addresses. { noAddr, noAddr, noAddrEncoded, - ProtocolVersion, + lastSupportedProtocolVersion, }, - // Latest protocol version with multiple addresses. + // Last supported protocol version with multiple addresses. { multiAddr, multiAddr, multiAddrEncoded, - ProtocolVersion, + lastSupportedProtocolVersion, }, } @@ -183,7 +215,7 @@ func TestAddrWire(t *testing.T) { // TestAddrWireErrors performs negative tests against wire encode and decode // of MsgAddr to confirm error paths work correctly. func TestAddrWireErrors(t *testing.T) { - pver := ProtocolVersion + pver := AddrV2Version - 1 // A couple of NetAddresses to use for testing. na := &NetAddress{ diff --git a/wire/msgaddrv2.go b/wire/msgaddrv2.go new file mode 100644 index 0000000000..dcd7d871df --- /dev/null +++ b/wire/msgaddrv2.go @@ -0,0 +1,279 @@ +// Copyright (c) 2021 The Decred developers +// Use of this source code is governed by an ISC +// license that can be found in the LICENSE file. + +package wire + +import ( + "fmt" + "io" + "net" + "time" +) + +// MaxAddrPerV2Msg is the maximum number of addresses that can be in a single +// Decred addrv2 protocol message. +const MaxAddrPerV2Msg = 1000 + +// MsgAddrV2 implements the Message interface and represents a wire +// addrv2 message. It is used to provide a list of known active peers on the +// network. An active peer is considered one that has transmitted a message +// within the last 3 hours. Nodes which have not transmitted in that time +// frame should be forgotten. Each message is limited to a maximum number of +// addresses. +type MsgAddrV2 struct { + // AddrList contains the addresses that will be sent to or have been + // received from a peer. Instead of manually appending addresses to this + // field directly, consumers should use the convenience functions on an + // instance of this message to add addresses. + AddrList []*NetAddressV2 +} + +// AddAddress adds a known address to the message. If the maximum number of +// addresses has been reached, then an error is returned. +func (msg *MsgAddrV2) AddAddress(na *NetAddressV2) error { + const op = "MsgAddrV2.AddAddress" + if len(msg.AddrList)+1 > MaxAddrPerV2Msg { + msg := fmt.Sprintf("too many addresses in message [max %v]", + MaxAddrPerV2Msg) + return messageError(op, ErrTooManyAddrs, msg) + } + + msg.AddrList = append(msg.AddrList, na) + return nil +} + +// AddAddresses adds multiple known addresses to the message. If the number of +// addresses exceeds the maximum allowed then an error is returned. +func (msg *MsgAddrV2) AddAddresses(netAddrs ...*NetAddressV2) error { + for _, na := range netAddrs { + err := msg.AddAddress(na) + if err != nil { + return err + } + } + return nil +} + +// ClearAddresses removes all addresses from the message. +func (msg *MsgAddrV2) ClearAddresses() { + msg.AddrList = []*NetAddressV2{} +} + +// readNetAddressV2 reads an encoded version 2 wire network address from the +// provided reader. +func readNetAddressV2(op string, r io.Reader, pver uint32) (*NetAddressV2, error) { + type netAddress struct { + Timestamp time.Time + Services ServiceFlag + Port uint16 + Type NetAddressType + } + na := &netAddress{} + + err := readElement(r, (*int64Time)(&na.Timestamp)) + if err != nil { + return nil, err + } + + // Read the service flags. + err = readElement(r, &na.Services) + if err != nil { + return nil, err + } + + // Read the network id to determine the expected length of the ip field. + err = readElement(r, &na.Type) + if err != nil { + return nil, err + } + + // Read the ip bytes with a length varying by the network id type. + var ipBytes []byte + switch na.Type { + case IPv4Address: + var ip [4]byte + err := readElement(r, &ip) + if err != nil { + return nil, err + } + ipBytes = ip[:] + case IPv6Address: + var ip [16]byte + err := readElement(r, &ip) + if err != nil { + return nil, err + } + ipBytes = ip[:] + default: + msg := fmt.Sprintf("unsupported network address type %v", na.Type) + return nil, messageError(op, ErrInvalidMsg, msg) + } + + err = readElement(r, &na.Port) + if err != nil { + return nil, err + } + + return NewNetAddressV2(na.Type, ipBytes, na.Port, na.Timestamp, + na.Services), nil +} + +// writeNetAddressV2 serializes an address manager network address to the +// provided writer. +func writeNetAddressV2(op string, w io.Writer, pver uint32, na *NetAddressV2) error { + err := writeElement(w, na.Timestamp.Unix()) + if err != nil { + return err + } + + err = writeElements(w, na.Services, na.Type) + if err != nil { + return err + } + + netAddrIP := na.IP + switch na.Type { + case IPv4Address: + var ip [4]byte + if netAddrIP != nil { + copy(ip[:], netAddrIP) + } + err = writeElement(w, ip) + if err != nil { + return err + } + case IPv6Address: + var ip [16]byte + if netAddrIP != nil { + copy(ip[:], net.IP(netAddrIP).To16()) + } + err = writeElement(w, ip) + if err != nil { + return err + } + default: + msg := fmt.Sprintf("unrecognized network address type %v", na.Type) + return messageError(op, ErrInvalidMsg, msg) + } + + return writeElement(w, na.Port) +} + +// maxNetAddressPayloadV2 returns the max payload size for an address manager +// network address based on the protocol version. +func maxNetAddressPayloadV2(pver uint32) uint32 { + const timestampSize = 8 + const servicesSize = 8 + const addressTypeSize = 1 + const maxAddressSize = 16 + const portSize = 2 + return timestampSize + servicesSize + addressTypeSize + maxAddressSize + + portSize +} + +// BtcDecode decodes r using the wire protocol encoding into the receiver. +// This is part of the Message interface implementation. +func (msg *MsgAddrV2) BtcDecode(r io.Reader, pver uint32) error { + const op = "MsgAddrV2.BtcDecode" + + // Ensure peers sending msgaddrv2 are on the expected minimum version. + if pver < AddrV2Version { + msg := fmt.Sprintf("%s message invalid for protocol version %d", + msg.Command(), pver) + return messageError(op, ErrMsgInvalidForPVer, msg) + } + + // Read the total number of addresses in this message. + count, err := ReadVarInt(r, pver) + if err != nil { + return err + } + + if count == 0 { + return messageError(op, ErrTooFewAddrs, + "no addresses for message [count 0, min 1]") + } + + // Limit to max addresses per message. + if count > MaxAddrPerV2Msg { + msg := fmt.Sprintf("too many addresses for message [count %v, max %v]", + count, MaxAddrPerV2Msg) + return messageError(op, ErrTooManyAddrs, msg) + } + + msg.AddrList = make([]*NetAddressV2, 0, count) + + for i := uint64(0); i < count; i++ { + netAddr, err := readNetAddressV2(op, r, pver) + if err != nil { + return err + } + msg.AddAddress(netAddr) + } + return nil +} + +// BtcEncode encodes the receiver to w using the wire protocol encoding. +// This is part of the Message interface implementation. +func (msg *MsgAddrV2) BtcEncode(w io.Writer, pver uint32) error { + const op = "MsgAddrV2.BtcEncode" + if pver < AddrV2Version { + msg := fmt.Sprintf("%s message invalid for protocol version %d", + msg.Command(), pver) + return messageError(op, ErrMsgInvalidForPVer, msg) + } + + count := len(msg.AddrList) + if count > MaxAddrPerV2Msg { + msg := fmt.Sprintf("too many addresses for message [count %v, max %v]", + count, MaxAddrPerV2Msg) + return messageError(op, ErrTooManyAddrs, msg) + } + + if count == 0 { + return messageError(op, ErrTooFewAddrs, + "no addresses for message [count 0, min 1]") + } + + err := WriteVarInt(w, pver, uint64(count)) + if err != nil { + return err + } + + for _, na := range msg.AddrList { + err = writeNetAddressV2(op, w, pver, na) + if err != nil { + return err + } + } + + return nil +} + +// Command returns the protocol command string for the message. This is part +// of the Message interface implementation. +func (msg *MsgAddrV2) Command() string { + return CmdAddrV2 +} + +// MaxPayloadLength returns the maximum length the payload can be for the +// receiver. This is part of the Message interface implementation. +func (msg *MsgAddrV2) MaxPayloadLength(pver uint32) uint32 { + if pver < AddrV2Version { + return 0 + } + + // Num addresses (size of varInt for max address per message) + max allowed + // addresses * max address size. + return uint32(VarIntSerializeSize(MaxAddrPerV2Msg)) + + (MaxAddrPerV2Msg * maxNetAddressPayloadV2(pver)) +} + +// NewMsgAddrV2 returns a new wire addrv2 message that conforms to the +// Message interface. See MsgAddr for details. +func NewMsgAddrV2() *MsgAddrV2 { + return &MsgAddrV2{ + AddrList: make([]*NetAddressV2, 0, MaxAddrPerV2Msg), + } +} diff --git a/wire/msgaddrv2_test.go b/wire/msgaddrv2_test.go new file mode 100644 index 0000000000..e105b04e26 --- /dev/null +++ b/wire/msgaddrv2_test.go @@ -0,0 +1,299 @@ +// Copyright (c) 2021 The Decred developers +// Use of this source code is governed by an ISC +// license that can be found in the LICENSE file. + +package wire + +import ( + "bytes" + "errors" + "io" + "reflect" + "testing" + "time" + + "github.com/davecgh/go-spew/spew" +) + +// newNetAddressV2 is a convenience function for constructing a new v2 network +// address. +func newNetAddressV2(addrType NetAddressType, addrBytes []byte, port uint16) *NetAddressV2 { + timestamp := time.Unix(0x495fab29, 0) // 2009-01-03 12:15:05 -0600 CST + netAddr := NewNetAddressV2(addrType, addrBytes, port, timestamp, + SFNodeNetwork) + return netAddr +} + +var ( + ipv4IpBytes = []byte{0x7f, 0x00, 0x00, 0x01} + + ipv6IpBytes = []byte{ + 0x26, 0x20, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, + } + + serializedIPv4NetAddressBytes = []byte{ + 0x29, 0xab, 0x5f, 0x49, 0x00, 0x00, 0x00, 0x00, // Timestamp + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Services + 0x01, // Address type + 0x7f, 0x00, 0x00, 0x01, + 0x8d, 0x20, // Port + } + + serializedIPv6NetAddressBytes = []byte{ + 0x29, 0xab, 0x5f, 0x49, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x02, + 0x26, 0x20, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, + 0x8e, 0x20, + } +) + +var ( + ipv4Address = newNetAddressV2(IPv4Address, ipv4IpBytes, 8333) + ipv6Address = newNetAddressV2(IPv6Address, ipv6IpBytes, 8334) +) + +// TestMaxPayloadLength verifies the maximum payload length equals the expected +// value at various protocol versions and does not exceed the maximum message +// size for any protocol message. +func TestMaxPayloadLength(t *testing.T) { + tests := []struct { + name string + pver uint32 + want uint32 + }{{ + name: "protocol version 9", + pver: AddrV2Version - 1, + want: 0, + }, { + name: "protocol version 10", + pver: AddrV2Version, + want: 35003, + }, { + name: "latest protocol version", + pver: ProtocolVersion, + want: 35003, + }} + + for _, test := range tests { + // Ensure max payload is expected value for latest protocol version. + msg := NewMsgAddrV2() + result := msg.MaxPayloadLength(test.pver) + if result != test.want { + t.Errorf("%s: wrong max payload length - got %v, want %d", + test.name, result, test.want) + } + + // Ensure max payload length is not more than the maximum allowed for + // any protocol message. + if result > MaxMessagePayload { + t.Fatalf("%s: payload length exceeds maximum message payload - "+ + "got %d, want less than %d.", test.name, result, + MaxMessagePayload) + } + } +} + +// TestAddrV2 tests the MsgAddrV2 API. +func TestAddrV2(t *testing.T) { + // Ensure the command is expected value. + wantCmd := "addrv2" + msg := NewMsgAddrV2() + if cmd := msg.Command(); cmd != wantCmd { + t.Errorf("NewMsgAddrV2: wrong command - got %v want %v", + cmd, wantCmd) + } + + // Ensure NetAddresses are added properly. + err := msg.AddAddress(ipv4Address) + if err != nil { + t.Errorf("AddAddress: %v", err) + } + if !reflect.DeepEqual(msg.AddrList[0], ipv4Address) { + t.Errorf("AddAddress: wrong address added - got %v, want %v", + spew.Sprint(msg.AddrList[0]), spew.Sprint(ipv4Address)) + } + + // Ensure the address list is cleared properly. + msg.ClearAddresses() + if len(msg.AddrList) != 0 { + t.Errorf("ClearAddresses: address list is not empty - "+ + "got %v [%v], want %v", len(msg.AddrList), + spew.Sprint(msg.AddrList[0]), 0) + } + + // Ensure adding more than the max allowed addresses per message returns + // error. + for i := 0; i < MaxAddrPerV2Msg+1; i++ { + err = msg.AddAddress(ipv4Address) + } + if err == nil { + t.Errorf("AddAddress: expected error on too many addresses " + + "not received") + } + + // Make sure adding multiple addresses also returns an error when the + // message is at max capacity. + err = msg.AddAddresses(ipv4Address) + if err == nil { + t.Errorf("AddAddresses: expected error on too many addresses " + + "not received") + } +} + +// TestAddrWire tests the MsgAddrV2 wire encode and decode for various numbers +// of addresses at the latest protocol version. +func TestAddrV2Wire(t *testing.T) { + pver := ProtocolVersion + tests := []struct { + name string + addrs []*NetAddressV2 + wantBytes []byte + }{{ + name: "latest protocol version with one address", + addrs: []*NetAddressV2{ + ipv4Address, + }, + wantBytes: bytes.Join([][]byte{ + {0x01}, + serializedIPv4NetAddressBytes, + }, []byte{}), + }, { + name: "latest protocol version with multiple addresses", + addrs: []*NetAddressV2{ + ipv4Address, + ipv6Address, + }, + wantBytes: bytes.Join([][]byte{ + {0x02}, + serializedIPv4NetAddressBytes, + serializedIPv6NetAddressBytes, + }, []byte{}), + }} + + t.Logf("Running %d tests", len(tests)) + for i, test := range tests { + subject := NewMsgAddrV2() + subject.AddAddresses(test.addrs...) + + // Encode the message to the wire format and ensure it serializes + // correctly. + var buf bytes.Buffer + err := subject.BtcEncode(&buf, pver) + if err != nil { + t.Errorf("%q: error encoding message - %v", test.name, err) + continue + } + if !reflect.DeepEqual(buf.Bytes(), test.wantBytes) { + t.Errorf("%q: mismatched bytes -- got: %s want: %s", test.name, + spew.Sdump(buf.Bytes()), spew.Sdump(test.wantBytes)) + continue + } + + // Decode the message from wire format and ensure it deserializes + // correctly. + var msg MsgAddrV2 + rbuf := bytes.NewReader(test.wantBytes) + err = msg.BtcDecode(rbuf, pver) + if err != nil { + t.Errorf("%q: error decoding message - %v", test.name, err) + continue + } + if !reflect.DeepEqual(&msg, subject) { + t.Errorf("%q: mismatched message - got: %s want: %s", i, + spew.Sdump(msg), spew.Sdump(subject)) + continue + } + } +} + +// TestAddrWireErrors performs negative tests against wire encode and decode +// of MsgAddrV2 to confirm error paths work correctly. +func TestAddrV2WireErrors(t *testing.T) { + pver := ProtocolVersion + na := ipv4Address + addrs := []*NetAddressV2{na} + + tests := []struct { + name string + addrs []*NetAddressV2 // Value to encode + bytes []byte // Wire encoding + pver uint32 // Protocol version for wire encoding + ioLimit int // Max size of fixed buffer to induce errors + writeErr error // Expected write error + readErr error // Expected read error + }{{ + name: "unsupported protocol version", + pver: AddrV2Version - 1, + addrs: addrs, + bytes: []byte{0x01}, + ioLimit: 1, + writeErr: ErrMsgInvalidForPVer, + readErr: ErrMsgInvalidForPVer, + }, { + name: "zero byte i/o limit", + pver: pver, + addrs: addrs, + bytes: []byte{0x00}, + ioLimit: 0, + writeErr: io.ErrShortWrite, + readErr: io.EOF, + }, { + name: "one byte i/o limit", + pver: pver, + addrs: addrs, + bytes: []byte{0x01}, + ioLimit: 1, + writeErr: io.ErrShortWrite, + readErr: io.EOF, + }, { + name: "message with no addresses", + pver: pver, + addrs: nil, + bytes: []byte{0x00}, + ioLimit: 1, + writeErr: ErrTooFewAddrs, + readErr: ErrTooFewAddrs, + }, { + name: "message with too many addresses", + pver: pver, + addrs: func() []*NetAddressV2 { + var addrs []*NetAddressV2 + for i := 0; i < MaxAddrPerV2Msg+1; i++ { + addrs = append(addrs, na) + } + return addrs + }(), + bytes: []byte{0xfd, 0xe9, 0x03}, + ioLimit: 3, + writeErr: ErrTooManyAddrs, + readErr: ErrTooManyAddrs, + }} + + t.Logf("Running %d tests", len(tests)) + for _, test := range tests { + subject := NewMsgAddrV2() + subject.AddrList = test.addrs + + // Encode to wire format. + w := newFixedWriter(test.ioLimit) + err := subject.BtcEncode(w, test.pver) + if !errors.Is(err, test.writeErr) { + t.Errorf("%q: wrong error - got: %v, want: %v", test.name, err, + test.writeErr) + continue + } + + // Decode from wire format. + var msg MsgAddrV2 + r := newFixedReader(test.ioLimit, test.bytes) + err = msg.BtcDecode(r, test.pver) + if !errors.Is(err, test.readErr) { + t.Errorf("%q: wrong error - got: %v, want: %v", test.name, err, + test.readErr) + continue + } + } +} diff --git a/wire/msggetaddr.go b/wire/msggetaddr.go index b2fc6a37da..bffda91058 100644 --- a/wire/msggetaddr.go +++ b/wire/msggetaddr.go @@ -1,11 +1,12 @@ // Copyright (c) 2013-2015 The btcsuite developers -// Copyright (c) 2015-2016 The Decred developers +// Copyright (c) 2015-2021 The Decred developers // Use of this source code is governed by an ISC // license that can be found in the LICENSE file. package wire import ( + "fmt" "io" ) @@ -20,12 +21,26 @@ type MsgGetAddr struct{} // BtcDecode decodes r using the Decred protocol encoding into the receiver. // This is part of the Message interface implementation. func (msg *MsgGetAddr) BtcDecode(r io.Reader, pver uint32) error { + const op = "MsgGetAddr.BtcDecode" + if pver >= AddrV2Version { + msg := fmt.Sprintf("%s message invalid for protocol version %d", + msg.Command(), pver) + return messageError(op, ErrMsgInvalidForPVer, msg) + } + return nil } // BtcEncode encodes the receiver to w using the Decred protocol encoding. // This is part of the Message interface implementation. func (msg *MsgGetAddr) BtcEncode(w io.Writer, pver uint32) error { + const op = "MsgGetAddr.BtcEncode" + if pver >= AddrV2Version { + msg := fmt.Sprintf("%s message invalid for protocol version %d", + msg.Command(), pver) + return messageError(op, ErrMsgInvalidForPVer, msg) + } + return nil } diff --git a/wire/msggetaddr_test.go b/wire/msggetaddr_test.go index e8ed84ee0c..586d24f531 100644 --- a/wire/msggetaddr_test.go +++ b/wire/msggetaddr_test.go @@ -1,5 +1,5 @@ // Copyright (c) 2013-2016 The btcsuite developers -// Copyright (c) 2015-2019 The Decred developers +// Copyright (c) 2015-2021 The Decred developers // Use of this source code is governed by an ISC // license that can be found in the LICENSE file. @@ -7,12 +7,44 @@ package wire import ( "bytes" + "errors" "reflect" "testing" "github.com/davecgh/go-spew/spew" ) +// TestGetAddrLatest tests the MsgGetAddr API against the latest protocol +// version to ensure it is no longer valid. +func TestGetAddrLatest(t *testing.T) { + pver := ProtocolVersion + msg := NewMsgGetAddr() + + // Ensure max payload is expected value. + wantPayload := uint32(0) + maxPayload := msg.MaxPayloadLength(pver) + if maxPayload != wantPayload { + t.Errorf("MaxPayloadLength: wrong max payload length for protocol "+ + "version %d - got %v, want %v", pver, maxPayload, wantPayload) + } + + // Ensure encode fails with the latest protocol version. + var buf bytes.Buffer + err := msg.BtcEncode(&buf, pver) + if !errors.Is(err, ErrMsgInvalidForPVer) { + t.Errorf("MsgGetAddr encode unexpected err -- got %v, want %v", err, + ErrMsgInvalidForPVer) + } + + // Ensure decode fails with the latest protocol version. + var readMsg MsgGetAddr + err = readMsg.BtcDecode(&buf, pver) + if !errors.Is(err, ErrMsgInvalidForPVer) { + t.Errorf("MsgGetAddr decode unexpected err -- got %v, want %v", err, + ErrMsgInvalidForPVer) + } +} + // TestGetAddr tests the MsgGetAddr API. func TestGetAddr(t *testing.T) { pver := ProtocolVersion @@ -43,9 +75,10 @@ func TestGetAddr(t *testing.T) { } } -// TestGetAddrWire tests the MsgGetAddr wire encode and decode for various -// protocol versions. -func TestGetAddrWire(t *testing.T) { +// TestGetAddrWireLastSupported tests the MsgGetAddr wire encode and decode for +// the last supported protocol versions. +func TestGetAddrWireLastSupported(t *testing.T) { + const lastSupportedProtocolVersion = AddrV2Version - 1 msgGetAddr := NewMsgGetAddr() msgGetAddrEncoded := []byte{} @@ -55,12 +88,12 @@ func TestGetAddrWire(t *testing.T) { buf []byte // Wire encoding pver uint32 // Protocol version for wire encoding }{ - // Latest protocol version. + // Last supported protocol version. { msgGetAddr, msgGetAddr, msgGetAddrEncoded, - ProtocolVersion, + lastSupportedProtocolVersion, }, } diff --git a/wire/msggetaddrv2.go b/wire/msggetaddrv2.go new file mode 100644 index 0000000000..44ba65eb08 --- /dev/null +++ b/wire/msggetaddrv2.go @@ -0,0 +1,63 @@ +// Copyright (c) 2013-2015 The btcsuite developers +// Copyright (c) 2015-2021 The Decred developers +// Use of this source code is governed by an ISC +// license that can be found in the LICENSE file. + +package wire + +import ( + "fmt" + "io" +) + +// MsgGetAddrV2 implements the Message interface and represents a decred +// getaddr message. It is used to request a list of known active peers on the +// network from a peer to help identify potential nodes. The list is returned +// via one or more addrv2 messages (MsgAddrV2). +// +// This message has no payload. +type MsgGetAddrV2 struct{} + +// BtcDecode decodes r using the wire protocol encoding into the receiver. +// This is part of the Message interface implementation. +func (msg *MsgGetAddrV2) BtcDecode(r io.Reader, pver uint32) error { + const op = "MsgGetAddrV2.BtcDecode" + if pver < AddrV2Version { + msg := fmt.Sprintf("%s message invalid for protocol version %d", + msg.Command(), pver) + return messageError(op, ErrMsgInvalidForPVer, msg) + } + + return nil +} + +// BtcEncode encodes the receiver to w using the wire protocol encoding. +// This is part of the Message interface implementation. +func (msg *MsgGetAddrV2) BtcEncode(w io.Writer, pver uint32) error { + const op = "MsgGetAddrV2.BtcEncode" + if pver < AddrV2Version { + msg := fmt.Sprintf("%s message invalid for protocol version %d", + msg.Command(), pver) + return messageError(op, ErrMsgInvalidForPVer, msg) + } + + return nil +} + +// Command returns the protocol command string for the message. This is part +// of the Message interface implementation. +func (msg *MsgGetAddrV2) Command() string { + return CmdGetAddrV2 +} + +// MaxPayloadLength returns the maximum length the payload can be for the +// receiver. This is part of the Message interface implementation. +func (msg *MsgGetAddrV2) MaxPayloadLength(pver uint32) uint32 { + return 0 +} + +// NewMsgGetAddrV2 returns a new Decred getaddr message that conforms to the +// Message interface. See MsgGetAddr for details. +func NewMsgGetAddrV2() *MsgGetAddrV2 { + return &MsgGetAddrV2{} +} diff --git a/wire/msggetaddrv2_test.go b/wire/msggetaddrv2_test.go new file mode 100644 index 0000000000..0174dcbf0d --- /dev/null +++ b/wire/msggetaddrv2_test.go @@ -0,0 +1,95 @@ +// Copyright (c) 2013-2016 The btcsuite developers +// Copyright (c) 2015-2021 The Decred developers +// Use of this source code is governed by an ISC +// license that can be found in the LICENSE file. + +package wire + +import ( + "bytes" + "reflect" + "testing" + + "github.com/davecgh/go-spew/spew" +) + +// TestGetAddr tests the MsgGetAddr API. +func TestGetAddrV2(t *testing.T) { + pver := ProtocolVersion + + // Ensure the command is expected value. + wantCmd := "getaddrv2" + msg := NewMsgGetAddrV2() + if cmd := msg.Command(); cmd != wantCmd { + t.Errorf("NewMsgGetAddr: wrong command - got %v want %v", + cmd, wantCmd) + } + + // Ensure max payload is expected value for latest protocol version. + wantPayload := uint32(0) + maxPayload := msg.MaxPayloadLength(pver) + if maxPayload != wantPayload { + t.Errorf("MaxPayloadLength: wrong max payload length for "+ + "protocol version %d - got %v, want %v", pver, + maxPayload, wantPayload) + } + + // Ensure max payload length is not more than MaxMessagePayload. + if maxPayload > MaxMessagePayload { + t.Fatalf("MaxPayloadLength: payload length (%v) for protocol "+ + "version %d exceeds MaxMessagePayload (%v).", maxPayload, pver, + MaxMessagePayload) + } +} + +// TestGetAddrV2Wire tests the MsgGetAddr wire encode and decode for various +// protocol versions. +func TestGetAddrV2Wire(t *testing.T) { + msgGetAddr := NewMsgGetAddr() + msgGetAddrEncoded := []byte{} + + tests := []struct { + in *MsgGetAddr // Message to encode + out *MsgGetAddr // Expected decoded message + buf []byte // Wire encoding + pver uint32 // Protocol version for wire encoding + }{ + // Latest protocol version. + { + msgGetAddr, + msgGetAddr, + msgGetAddrEncoded, + AddrV2Version - 1, + }, + } + + t.Logf("Running %d tests", len(tests)) + for i, test := range tests { + // Encode the message to wire format. + var buf bytes.Buffer + err := test.in.BtcEncode(&buf, test.pver) + if err != nil { + t.Errorf("BtcEncode #%d error %v", i, err) + continue + } + if !bytes.Equal(buf.Bytes(), test.buf) { + t.Errorf("BtcEncode #%d\n got: %s want: %s", i, + spew.Sdump(buf.Bytes()), spew.Sdump(test.buf)) + continue + } + + // Decode the message from wire format. + var msg MsgGetAddr + rbuf := bytes.NewReader(test.buf) + err = msg.BtcDecode(rbuf, test.pver) + if err != nil { + t.Errorf("BtcDecode #%d error %v", i, err) + continue + } + if !reflect.DeepEqual(&msg, test.out) { + t.Errorf("BtcDecode #%d\n got: %s want: %s", i, + spew.Sdump(msg), spew.Sdump(test.out)) + continue + } + } +} diff --git a/wire/netaddressv2.go b/wire/netaddressv2.go new file mode 100644 index 0000000000..51ffcef554 --- /dev/null +++ b/wire/netaddressv2.go @@ -0,0 +1,51 @@ +// Copyright (c) 2021 The Decred developers +// Use of this source code is governed by an ISC +// license that can be found in the LICENSE file. + +package wire + +import ( + "time" +) + +// NetAddressType is used to indicate which network a network address belongs +// to. +type NetAddressType uint8 + +const ( + UnknownAddressType NetAddressType = iota + IPv4Address + IPv6Address +) + +// NetAddressV2 defines information about a peer on the network. +type NetAddressV2 struct { + // Type represents the type of network that the network address belongs to. + Type NetAddressType + + // IP address of the peer. It is defined as a byte array to support various + // address types that are not standard to the net module and therefore not + // entirely appropriate to store as a net.IP. + IP []byte + + // Port is the port of the remote peer. + Port uint16 + + // Timestamp is the last time the address was seen. + Timestamp time.Time + + // Services represents the service flags supported by this network address. + Services ServiceFlag +} + +// NewNetAddressV2 creates a new network address using the provided +// parameters without validation. +func NewNetAddressV2(netAddressType NetAddressType, addrBytes []byte, port uint16, timestamp time.Time, services ServiceFlag) *NetAddressV2 { + return &NetAddressV2{ + Type: netAddressType, + IP: addrBytes, + Port: port, + Services: services, + Timestamp: timestamp, + } +} diff --git a/wire/protocol.go b/wire/protocol.go index a49ddf1e99..eee2fd54ad 100644 --- a/wire/protocol.go +++ b/wire/protocol.go @@ -17,7 +17,7 @@ const ( InitialProcotolVersion uint32 = 1 // ProtocolVersion is the latest protocol version this package supports. - ProtocolVersion uint32 = 9 + ProtocolVersion uint32 = 10 // NodeBloomVersion is the protocol version which added the SFNodeBloom // service flag (unused). @@ -51,6 +51,10 @@ const ( // RemoveRejectVersion is the protocol version which removes support for the // reject message. RemoveRejectVersion uint32 = 9 + + // AddrV2Version is the protocol version which adds the addrv2 and + // getaddrv2 messages. + AddrV2Version uint32 = 10 ) // ServiceFlag identifies services supported by a Decred peer. From c8b98b152029ec1cecfbe1eef97f171793d5a380 Mon Sep 17 00:00:00 2001 From: Sef Boukenken Date: Sun, 26 Sep 2021 23:18:15 -0400 Subject: [PATCH 8/9] connmgr: Allow seeding additional address types. This commit adds support for address types supported by the address manager when recieving addresses from a seeder. --- connmgr/go.mod | 3 +++ connmgr/go.sum | 10 ++++++++++ connmgr/seed.go | 30 ++++++++++++++++++++-------- server.go | 53 ++++++++++++++++++++----------------------------- 4 files changed, 56 insertions(+), 40 deletions(-) diff --git a/connmgr/go.mod b/connmgr/go.mod index 7ddb06c8f8..e52b24eeda 100644 --- a/connmgr/go.mod +++ b/connmgr/go.mod @@ -3,6 +3,9 @@ module github.com/decred/dcrd/connmgr/v3 go 1.13 require ( + github.com/decred/dcrd/addrmgr/v2 v2.0.0 github.com/decred/dcrd/wire v1.5.0 github.com/decred/slog v1.2.0 ) + +replace github.com/decred/dcrd/addrmgr/v2 => ../addrmgr diff --git a/connmgr/go.sum b/connmgr/go.sum index 15921729de..6711115bca 100644 --- a/connmgr/go.sum +++ b/connmgr/go.sum @@ -2,9 +2,19 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/decred/dcrd/chaincfg/chainhash v1.0.2 h1:rt5Vlq/jM3ZawwiacWjPa+smINyLRN07EO0cNBV6DGU= github.com/decred/dcrd/chaincfg/chainhash v1.0.2/go.mod h1:BpbrGgrPTr3YJYRN3Bm+D9NuaFd+zGyNeIKgrhCXK60= +github.com/decred/dcrd/chaincfg/chainhash v1.0.3 h1:PF2czcYZGW3dz4i/35AUfVAgnqHl9TMNQt1ADTYGOoE= +github.com/decred/dcrd/chaincfg/chainhash v1.0.3/go.mod h1:BpbrGgrPTr3YJYRN3Bm+D9NuaFd+zGyNeIKgrhCXK60= github.com/decred/dcrd/crypto/blake256 v1.0.0 h1:/8DMNYp9SGi5f0w7uCm6d6M4OU2rGFK09Y2A4Xv7EE0= github.com/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn47hh8kt6rqSlvmrXFAc= github.com/decred/dcrd/wire v1.5.0 h1:3SgcEzSjqAMQvOugP0a8iX7yQSpiVT1yNi9bc4iOXVg= github.com/decred/dcrd/wire v1.5.0/go.mod h1:fzAjVqw32LkbAZIt5mnrvBR751GTa3e0rRQdOIhPY3w= github.com/decred/slog v1.2.0 h1:soHAxV52B54Di3WtKLfPum9OFfWqwtf/ygf9njdfnPM= github.com/decred/slog v1.2.0/go.mod h1:kVXlGnt6DHy2fV5OjSeuvCJ0OmlmTF6LFpEPMu/fOY0= +golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2 h1:It14KIkyBFYkHkwZ7k45minvA9aorojkyjGk9KJ5B/w= +golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68 h1:nxC68pudNYkKU6jWhgrqdreuFiOQWj1Fs7T3VrH4Pjw= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= diff --git a/connmgr/seed.go b/connmgr/seed.go index 9cc6404fb3..a21c38c0e7 100644 --- a/connmgr/seed.go +++ b/connmgr/seed.go @@ -16,6 +16,7 @@ import ( "strconv" "time" + "github.com/decred/dcrd/addrmgr/v2" "github.com/decred/dcrd/wire" ) @@ -92,7 +93,7 @@ type node struct { // The available filters can be set via the exported functions that start with // the prefix SeedFilter. See the documentation for each function for more // details. -func SeedAddrs(ctx context.Context, seeder string, dialFn DialFunc, filters ...func(f *HttpsSeederFilters)) ([]*wire.NetAddress, error) { +func SeedAddrs(ctx context.Context, seeder string, dialFn DialFunc, filters ...func(f *HttpsSeederFilters)) ([]*addrmgr.NetAddress, error) { // Set any caller provided filters. var seederFilters HttpsSeederFilters for _, f := range filters { @@ -164,7 +165,7 @@ func SeedAddrs(ctx context.Context, seeder string, dialFn DialFunc, filters ...f // Convert the response to net addresses. randSource := mrand.New(mrand.NewSource(time.Now().UnixNano())) - addrs := make([]*wire.NetAddress, 0, len(nodes)) + addrs := make([]*addrmgr.NetAddress, 0, len(nodes)) for _, node := range nodes { host, portStr, err := net.SplitHostPort(node.Host) if err != nil { @@ -176,10 +177,17 @@ func SeedAddrs(ctx context.Context, seeder string, dialFn DialFunc, filters ...f log.Warnf("seeder returned invalid port %q", node.Host) continue } - ip := net.ParseIP(host) - if ip == nil { - log.Warnf("seeder returned a hostname that is not an IP address %q", - host) + + netAddressType, ipBytes, err := addrmgr.ParseHost(host) + if err != nil { + log.Warnf("seeder returned a hostname that could not be parsed: "+ + "%q, %q", host, err) + continue + } + + if netAddressType == addrmgr.UnknownAddressType { + log.Warnf("seeder returned a hostname with an unknown address "+ + "type: %q", host) continue } @@ -188,8 +196,14 @@ func SeedAddrs(ctx context.Context, seeder string, dialFn DialFunc, filters ...f // since they are a more authoritative source than other random peers. offsetSecs := secondsIn3Days + randSource.Int31n(secondsIn4Days) ts := time.Now().Add(-1 * time.Second * time.Duration(offsetSecs)) - na := wire.NewNetAddressTimestamp(ts, wire.ServiceFlag(node.Services), - ip, uint16(port)) + na, err := addrmgr.NewNetAddressByType(netAddressType, ipBytes, + uint16(port), ts, wire.ServiceFlag(node.Services)) + if err != nil { + log.Warnf("failed to construct network address from seeder "+ + "hostname: %q, %q", host, err) + continue + } + addrs = append(addrs, na) } diff --git a/server.go b/server.go index e7e2da6b17..28353137ec 100644 --- a/server.go +++ b/server.go @@ -639,20 +639,15 @@ func (sp *serverPeer) relayTxDisabled() bool { return isDisabled } -// wireToAddrmgrNetAddress converts a wire NetAddress to an address manager -// NetAddress. -func wireToAddrmgrNetAddress(netAddr *wire.NetAddress) *addrmgr.NetAddress { - newNetAddr := addrmgr.NewNetAddressIPPort(netAddr.IP, netAddr.Port, netAddr.Services) - newNetAddr.Timestamp = netAddr.Timestamp - return newNetAddr -} - -// wireToAddrmgrNetAddresses converts a collection of wire net addresses to a -// collection of address manager net addresses. +// wireToAddrmgrNetAddresses converts a collection of version 1 wire network +// addresses to a collection of address manager network addresses. func wireToAddrmgrNetAddresses(netAddr []*wire.NetAddress) []*addrmgr.NetAddress { addrs := make([]*addrmgr.NetAddress, len(netAddr)) for i, wireAddr := range netAddr { - addrs[i] = wireToAddrmgrNetAddress(wireAddr) + newNetAddr := addrmgr.NewNetAddressIPPort(wireAddr.IP, wireAddr.Port, + wireAddr.Services) + newNetAddr.Timestamp = wireAddr.Timestamp + addrs[i] = newNetAddr } return addrs } @@ -674,21 +669,6 @@ func wireToAddrmgrNetAddressesV2(netAddr []*wire.NetAddressV2) ([]*addrmgr.NetAd return addrs, nil } -// addrmgrToWireNetAddress converts an address manager net address to a wire net -// address. -func addrmgrToWireNetAddress(netAddr *addrmgr.NetAddress) *wire.NetAddress { - return wire.NewNetAddressTimestamp(netAddr.Timestamp, netAddr.Services, - netAddr.IP, netAddr.Port) -} - -// addrmgrToWireNetAddressV2 converts an address manager net address to a wire net -// address. -func addrmgrToWireNetAddressV2(netAddr *addrmgr.NetAddress) *wire.NetAddressV2 { - wireNetAddrType := wire.NetAddressType(netAddr.Type) - return wire.NewNetAddressV2(wireNetAddrType, netAddr.IP, netAddr.Port, - netAddr.Timestamp, netAddr.Services) -} - // pushAddrMsg sends an addr message to the connected peer using the provided // addresses. Any network address passed to this function must have // already been filtered to ensure that it is supported by the protocol @@ -698,7 +678,8 @@ func (sp *serverPeer) pushAddrMsg(addresses []*addrmgr.NetAddress) { addrs := make([]*wire.NetAddress, 0, len(addresses)) for _, addr := range addresses { if !sp.addressKnown(addr) { - wireNetAddr := addrmgrToWireNetAddress(addr) + wireNetAddr := wire.NewNetAddressTimestamp(addr.Timestamp, + addr.Services, addr.IP, addr.Port) addrs = append(addrs, wireNetAddr) } } @@ -723,7 +704,9 @@ func (sp *serverPeer) pushAddrV2Msg(addresses []*addrmgr.NetAddress) { addrs := make([]*wire.NetAddressV2, 0, len(addresses)) for _, addr := range addresses { if !sp.addressKnown(addr) { - addrV2 := addrmgrToWireNetAddressV2(addr) + wireNetAddrType := wire.NetAddressType(addr.Type) + addrV2 := wire.NewNetAddressV2(wireNetAddrType, addr.IP, addr.Port, + addr.Timestamp, addr.Services) addrs = append(addrs, addrV2) } } @@ -1981,7 +1964,14 @@ func (s *server) handleAddPeerMsg(state *peerState, sp *serverPeer) bool { net = addrmgr.IPv6Address } - localAddr := wireToAddrmgrNetAddress(na) + localAddr, err := addrmgr.NewNetAddressByType(net, na.IP, na.Port, + na.Timestamp, na.Services) + if err != nil { + srvrLog.Errorf("unable to construct peer network address: %v", + err) + return true + } + valid, reach := s.addrManager.ValidatePeerNa(localAddr, remoteAddr) if !valid { return true @@ -3292,10 +3282,9 @@ func (s *server) querySeeders(ctx context.Context) { const httpsPort = 443 srcAddr, err := cfg.hostToNetAddress(seeder, httpsPort, 0) if err != nil { - srcAddr = wireToAddrmgrNetAddress(addrs[0]) + srcAddr = addrs[0] } - addresses := wireToAddrmgrNetAddresses(addrs) - s.addrManager.AddAddresses(addresses, srcAddr) + s.addrManager.AddAddresses(addrs, srcAddr) }(seeder) } } From 3632c29c5dd82cb7227177d68adcd31c4edaced8 Mon Sep 17 00:00:00 2001 From: Sef Boukenken Date: Sun, 14 Mar 2021 16:01:18 -0400 Subject: [PATCH 9/9] peer/wire: Relay TORv3 addresses. This commit relays TORv3 addresses across the peer to peer network and bumps the wire protocol version to 11. --- peer/peer.go | 2 +- server.go | 5 ++-- wire/msgaddrv2.go | 58 ++++++++++++++++++++++++++++++++---------- wire/msgaddrv2_test.go | 42 +++++++++++++++++++++++++++--- wire/netaddressv2.go | 1 + wire/protocol.go | 6 ++++- 6 files changed, 93 insertions(+), 21 deletions(-) diff --git a/peer/peer.go b/peer/peer.go index 6bd185a468..752de36f04 100644 --- a/peer/peer.go +++ b/peer/peer.go @@ -27,7 +27,7 @@ import ( const ( // MaxProtocolVersion is the max protocol version the peer supports. - MaxProtocolVersion = wire.AddrV2Version + MaxProtocolVersion = wire.RelayTORv3Version // outputBufferSize is the number of elements the output channels use. outputBufferSize = 5000 diff --git a/server.go b/server.go index 28353137ec..7ba3c3dd4c 100644 --- a/server.go +++ b/server.go @@ -74,7 +74,7 @@ const ( connectionRetryInterval = time.Second * 5 // maxProtocolVersion is the max protocol version the server supports. - maxProtocolVersion = wire.AddrV2Version + maxProtocolVersion = wire.RelayTORv3Version // These fields are used to track known addresses on a per-peer basis. // @@ -783,6 +783,7 @@ func isSupportedNetAddressTypeV2(netAddressType addrmgr.NetAddressType) bool { switch netAddressType { case addrmgr.IPv4Address: case addrmgr.IPv6Address: + case addrmgr.TORv3Address: return true } return false @@ -792,7 +793,7 @@ func isSupportedNetAddressTypeV2(netAddressType addrmgr.NetAddressType) bool { // specific address manager network address type is supported by the // provided protocol version. func getNetAddressTypeFilter(protocolVersion uint32) addrmgr.NetAddressTypeFilter { - if protocolVersion < wire.AddrV2Version { + if protocolVersion < wire.RelayTORv3Version { return isSupportedNetAddressTypeV1 } else { return isSupportedNetAddressTypeV2 diff --git a/wire/msgaddrv2.go b/wire/msgaddrv2.go index dcd7d871df..bb89543c1f 100644 --- a/wire/msgaddrv2.go +++ b/wire/msgaddrv2.go @@ -90,23 +90,31 @@ func readNetAddressV2(op string, r io.Reader, pver uint32) (*NetAddressV2, error // Read the ip bytes with a length varying by the network id type. var ipBytes []byte - switch na.Type { - case IPv4Address: + switch { + case na.Type == IPv4Address: var ip [4]byte err := readElement(r, &ip) if err != nil { return nil, err } ipBytes = ip[:] - case IPv6Address: + case na.Type == IPv6Address: var ip [16]byte err := readElement(r, &ip) if err != nil { return nil, err } ipBytes = ip[:] + case na.Type == TORv3Address && pver >= RelayTORv3Version: + var ip [32]byte + err := readElement(r, &ip) + if err != nil { + return nil, err + } + ipBytes = ip[:] default: - msg := fmt.Sprintf("unsupported network address type %v", na.Type) + msg := fmt.Sprintf("unsupported network address type %v for "+ + "protocol version %d", na.Type, pver) return nil, messageError(op, ErrInvalidMsg, msg) } @@ -133,8 +141,8 @@ func writeNetAddressV2(op string, w io.Writer, pver uint32, na *NetAddressV2) er } netAddrIP := na.IP - switch na.Type { - case IPv4Address: + switch { + case na.Type == IPv4Address: var ip [4]byte if netAddrIP != nil { copy(ip[:], netAddrIP) @@ -143,7 +151,7 @@ func writeNetAddressV2(op string, w io.Writer, pver uint32, na *NetAddressV2) er if err != nil { return err } - case IPv6Address: + case na.Type == IPv6Address: var ip [16]byte if netAddrIP != nil { copy(ip[:], net.IP(netAddrIP).To16()) @@ -152,8 +160,18 @@ func writeNetAddressV2(op string, w io.Writer, pver uint32, na *NetAddressV2) er if err != nil { return err } + case na.Type == TORv3Address && pver >= RelayTORv3Version: + var ip [32]byte + if len(na.IP) == 32 { + copy(ip[:], net.IP(na.IP)) + } + err = writeElement(w, ip) + if err != nil { + return err + } default: - msg := fmt.Sprintf("unrecognized network address type %v", na.Type) + msg := fmt.Sprintf("unsupported network address type %v for "+ + "protocol version %d", na.Type, pver) return messageError(op, ErrInvalidMsg, msg) } @@ -163,11 +181,25 @@ func writeNetAddressV2(op string, w io.Writer, pver uint32, na *NetAddressV2) er // maxNetAddressPayloadV2 returns the max payload size for an address manager // network address based on the protocol version. func maxNetAddressPayloadV2(pver uint32) uint32 { - const timestampSize = 8 - const servicesSize = 8 - const addressTypeSize = 1 - const maxAddressSize = 16 - const portSize = 2 + if pver < RelayTORv3Version { + const ( + timestampSize = 8 + servicesSize = 8 + addressTypeSize = 1 + maxAddressSize = 16 + portSize = 2 + ) + return timestampSize + servicesSize + addressTypeSize + maxAddressSize + + portSize + } + + const ( + timestampSize = 8 + servicesSize = 8 + addressTypeSize = 1 + maxAddressSize = 32 + portSize = 2 + ) return timestampSize + servicesSize + addressTypeSize + maxAddressSize + portSize } diff --git a/wire/msgaddrv2_test.go b/wire/msgaddrv2_test.go index e105b04e26..1433a787fe 100644 --- a/wire/msgaddrv2_test.go +++ b/wire/msgaddrv2_test.go @@ -32,6 +32,13 @@ var ( 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, } + torV3IpBytes = []byte{ + 0xb8, 0x39, 0x1d, 0x20, 0x03, 0xbb, 0x3b, 0xd2, + 0x85, 0xb0, 0x35, 0xac, 0x8e, 0xb3, 0x0c, 0x80, + 0xc4, 0xe2, 0xa2, 0x9b, 0xb7, 0xa2, 0xf0, 0xce, + 0x0d, 0xf8, 0x74, 0x3c, 0x37, 0xec, 0x35, 0x93, + } + serializedIPv4NetAddressBytes = []byte{ 0x29, 0xab, 0x5f, 0x49, 0x00, 0x00, 0x00, 0x00, // Timestamp 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Services @@ -48,11 +55,23 @@ var ( 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x8e, 0x20, } + + serializedTORv3NetAddressBytes = []byte{ + 0x29, 0xab, 0x5f, 0x49, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x03, + 0xb8, 0x39, 0x1d, 0x20, 0x03, 0xbb, 0x3b, 0xd2, + 0x85, 0xb0, 0x35, 0xac, 0x8e, 0xb3, 0x0c, 0x80, + 0xc4, 0xe2, 0xa2, 0x9b, 0xb7, 0xa2, 0xf0, 0xce, + 0x0d, 0xf8, 0x74, 0x3c, 0x37, 0xec, 0x35, 0x93, + 0x90, 0x20, + } ) var ( - ipv4Address = newNetAddressV2(IPv4Address, ipv4IpBytes, 8333) - ipv6Address = newNetAddressV2(IPv6Address, ipv6IpBytes, 8334) + ipv4Address = newNetAddressV2(IPv4Address, ipv4IpBytes, 8333) + ipv6Address = newNetAddressV2(IPv6Address, ipv6IpBytes, 8334) + torv3Address = newNetAddressV2(TORv3Address, torV3IpBytes, 8336) ) // TestMaxPayloadLength verifies the maximum payload length equals the expected @@ -71,10 +90,14 @@ func TestMaxPayloadLength(t *testing.T) { name: "protocol version 10", pver: AddrV2Version, want: 35003, + }, { + name: "protocol version 11", + pver: RelayTORv3Version, + want: 51003, }, { name: "latest protocol version", pver: ProtocolVersion, - want: 35003, + want: 51003, }} for _, test := range tests { @@ -165,11 +188,13 @@ func TestAddrV2Wire(t *testing.T) { addrs: []*NetAddressV2{ ipv4Address, ipv6Address, + torv3Address, }, wantBytes: bytes.Join([][]byte{ - {0x02}, + {0x03}, serializedIPv4NetAddressBytes, serializedIPv6NetAddressBytes, + serializedTORv3NetAddressBytes, }, []byte{}), }} @@ -215,6 +240,7 @@ func TestAddrV2WireErrors(t *testing.T) { pver := ProtocolVersion na := ipv4Address addrs := []*NetAddressV2{na} + addrv2 := NewMsgAddrV2() tests := []struct { name string @@ -270,6 +296,14 @@ func TestAddrV2WireErrors(t *testing.T) { ioLimit: 3, writeErr: ErrTooManyAddrs, readErr: ErrTooManyAddrs, + }, { + name: "torv3 address invalid on protocol version 10", + pver: RelayTORv3Version - 1, + addrs: []*NetAddressV2{torv3Address}, + bytes: []byte{0x01}, + ioLimit: int(addrv2.MaxPayloadLength(RelayTORv3Version - 1)), + writeErr: ErrInvalidMsg, + readErr: ErrInvalidMsg, }} t.Logf("Running %d tests", len(tests)) diff --git a/wire/netaddressv2.go b/wire/netaddressv2.go index 51ffcef554..73dd7b5892 100644 --- a/wire/netaddressv2.go +++ b/wire/netaddressv2.go @@ -16,6 +16,7 @@ const ( UnknownAddressType NetAddressType = iota IPv4Address IPv6Address + TORv3Address ) // NetAddressV2 defines information about a peer on the network. diff --git a/wire/protocol.go b/wire/protocol.go index eee2fd54ad..4142ddf41e 100644 --- a/wire/protocol.go +++ b/wire/protocol.go @@ -17,7 +17,7 @@ const ( InitialProcotolVersion uint32 = 1 // ProtocolVersion is the latest protocol version this package supports. - ProtocolVersion uint32 = 10 + ProtocolVersion uint32 = 11 // NodeBloomVersion is the protocol version which added the SFNodeBloom // service flag (unused). @@ -55,6 +55,10 @@ const ( // AddrV2Version is the protocol version which adds the addrv2 and // getaddrv2 messages. AddrV2Version uint32 = 10 + + // RelayTORv3Version is the protocol version which adds support for relaying + // TORv3 addresses. + RelayTORv3Version uint32 = 11 ) // ServiceFlag identifies services supported by a Decred peer.