diff --git a/addrmgr/addrmanager.go b/addrmgr/addrmanager.go index 49909b863d..791c88851e 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 @@ -551,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{ @@ -684,10 +681,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 +698,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 @@ -753,39 +755,33 @@ func (a *AddrManager) reset() { } } -// 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) { - // 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 +// 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 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 len(ips) == 0 { - return nil, fmt.Errorf("no addresses found for %s", host) + } + + if ip := net.ParseIP(host); ip != nil { + if isIPv4(ip) { + return IPv4Address, ip.To4(), nil } - ip = ips[0] + return IPv6Address, ip, nil } - return NewNetAddressIPPort(ip, port, services), nil + return UnknownAddressType, nil, nil } // GetAddress returns a single address that should be routable. It picks a @@ -1129,12 +1125,15 @@ func getReachabilityFrom(localAddr, remoteAddr *NetAddress) NetAddressReach { return Unreachable } - if isOnionCatTor(remoteAddr.IP) { - if isOnionCatTor(localAddr.IP) { + isRemoteAddrTOR := remoteAddr.Type == TORv3Address + isLocalAddrTOR := localAddr.Type == TORv3Address + + if isRemoteAddrTOR { + if isLocalAddrTOR { return Private } - if localAddr.IsRoutable() && isIPv4(localAddr.IP) { + if localAddr.IsRoutable() && localAddr.Type == IPv4Address { return Ipv4 } @@ -1150,15 +1149,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 +1178,7 @@ func getReachabilityFrom(localAddr, remoteAddr *NetAddress) NetAddressReach { return Teredo } - if isIPv4(localAddr.IP) { + if localAddr.Type == IPv4Address { return Ipv4 } @@ -1195,7 +1194,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() @@ -1203,6 +1202,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) { @@ -1219,7 +1222,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 { ip = net.IPv6zero } else { ip = net.IPv4zero @@ -1236,8 +1239,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 @@ -1245,11 +1252,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 5f01929f65..b97a1d1655 100644 --- a/addrmgr/addrmanager_test.go +++ b/addrmgr/addrmanager_test.go @@ -6,7 +6,6 @@ package addrmgr import ( - "errors" "fmt" "net" "os" @@ -21,8 +20,12 @@ import ( // Put some IP in here for convenience. Points to google. var someIP = "173.194.115.66" -func lookupFunc(host string) ([]net.IP, error) { - return nil, errors.New("not implemented") +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 { + return true } // addAddressByIP is a convenience function that adds an address to the @@ -49,7 +52,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. @@ -67,7 +70,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() @@ -90,7 +93,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") @@ -189,7 +192,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) @@ -230,7 +233,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) @@ -260,7 +263,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) @@ -290,7 +293,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 { @@ -319,7 +322,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) @@ -341,7 +344,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) @@ -352,7 +355,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 @@ -437,7 +440,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 { @@ -532,11 +535,11 @@ 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 { - 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 +553,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 +567,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) @@ -603,7 +606,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 { @@ -618,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() @@ -631,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, }, { @@ -752,105 +755,46 @@ 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) - 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) - } - } -} - -// 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) + return } } } @@ -859,7 +803,7 @@ func TestHostToNetAddress(t *testing.T) { // 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/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/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/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..b3b8ebdd70 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. @@ -38,14 +42,17 @@ 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 - if isOnionCatTor(netIP) { - // We know now that na.IP is long enough. - base32 := base32.StdEncoding.EncodeToString(netIP[6:]) + switch netAddr.Type { + 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() @@ -78,8 +85,76 @@ 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 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(claimedType NetAddressType, addrBytes []byte) (NetAddressType, error) { + len := len(addrBytes) + switch { + case isIPv4(addrBytes): + return IPv4Address, 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) + 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(netAddressType, 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 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) + 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..3d07a14bcd 100644 --- a/addrmgr/netaddress_test.go +++ b/addrmgr/netaddress_test.go @@ -9,81 +9,182 @@ import ( "reflect" "testing" + "time" + "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) { + 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: "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, + 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"}, - - // TORv2 - {ip: "fd87:d87e:eb43::", port: 8333, want: "aaaaaaaaaaaaaaaa.onion:8333"}, + {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"}, + + // TORv3 + { + host: torV3AddressString, + port: 8333, + want: torV3AddressString + ":8333", + }, } + 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..51aeec21fb 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). @@ -70,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) @@ -108,41 +102,22 @@ 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 ( - LocalAddress NetAddressType = iota - IPv4Address - IPv6Address - TORv2Address + UnknownAddressType NetAddressType = 0 + IPv4Address NetAddressType = 1 + IPv6Address NetAddressType = 2 + TORv3Address NetAddressType = 4 ) -// 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 - } -} +// 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, @@ -235,6 +210,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. @@ -253,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 @@ -269,7 +277,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) { @@ -277,7 +285,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() @@ -291,9 +298,9 @@ func (na *NetAddress) GroupKey() string { } return newIP.Mask(net.CIDRMask(16, 32)).String() } - if isOnionCatTor(netIP) { - // group is keyed off the first 4 bits of the actual onion key. - return fmt.Sprintf("tor:%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..744af945e4 100644 --- a/addrmgr/network_test.go +++ b/addrmgr/network_test.go @@ -9,6 +9,8 @@ import ( "net" "testing" + "time" + "github.com/decred/dcrd/wire" ) @@ -146,55 +148,60 @@ 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"}, + + // 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/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/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/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/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 ee5213718d..e390948e7f 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,11 @@ 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 + github.com/decred/dcrd/wire => ../wire +) 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/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 3562f02f7f..752de36f04 100644 --- a/peer/peer.go +++ b/peer/peer.go @@ -18,16 +18,16 @@ 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" ) const ( // MaxProtocolVersion is the max protocol version the peer supports. - MaxProtocolVersion = wire.InitStateVersion + MaxProtocolVersion = wire.RelayTORv3Version // outputBufferSize is the number of elements the output channels use. outputBufferSize = 5000 @@ -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 @@ -96,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) @@ -284,40 +293,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 +384,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 +430,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 +563,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() @@ -815,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. // @@ -1282,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) @@ -1820,6 +1864,36 @@ func (p *Peer) readRemoteVersionMsg() error { return nil } +// 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 { + 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 +1906,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 +1929,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 +2138,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/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/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 15f59fc964..7ba3c3dd4c 100644 --- a/server.go +++ b/server.go @@ -6,6 +6,7 @@ package main import ( + "bytes" "context" "crypto/elliptic" "crypto/rand" @@ -73,7 +74,7 @@ const ( connectionRetryInterval = time.Second * 5 // maxProtocolVersion is the max protocol version the server supports. - maxProtocolVersion = wire.InitStateVersion + maxProtocolVersion = wire.RelayTORv3Version // These fields are used to track known addresses on a per-peer basis. // @@ -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++ } } @@ -359,6 +360,34 @@ 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. 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. @@ -381,7 +410,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) @@ -444,9 +473,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 } @@ -611,39 +639,47 @@ 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 } -// 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) +// 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 } // 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)) 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) } } @@ -658,6 +694,31 @@ 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) { + wireNetAddrType := wire.NetAddressType(addr.Type) + addrV2 := wire.NewNetAddressV2(wireNetAddrType, addr.IP, addr.Port, + addr.Timestamp, addr.Services) + 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 @@ -705,6 +766,40 @@ 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 version 1 wire network address type. +func isSupportedNetAddressTypeV1(netAddressType addrmgr.NetAddressType) bool { + switch netAddressType { + case addrmgr.IPv4Address: + case addrmgr.IPv6Address: + return true + } + 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: + case addrmgr.TORv3Address: + 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(protocolVersion uint32) addrmgr.NetAddressTypeFilter { + if protocolVersion < wire.RelayTORv3Version { + return isSupportedNetAddressTypeV1 + } else { + return isSupportedNetAddressTypeV2 + } +} + // 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,7 +815,8 @@ 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() - remoteAddr := wireToAddrmgrNetAddress(sp.NA()) + msgProtocolVersion := uint32(msg.ProtocolVersion) + remoteAddr := sp.NA() addrManager := sp.server.addrManager if !cfg.SimNet && !cfg.RegNet && !isInbound { err := addrManager.SetServices(remoteAddr, msg.Services) @@ -730,7 +826,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,18 +856,29 @@ 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} - 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. @@ -1036,7 +1143,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, @@ -1387,12 +1494,53 @@ 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) } +// 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) { @@ -1436,10 +1584,56 @@ 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) } +// 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) { @@ -1667,13 +1861,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", @@ -1689,7 +1884,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] - "+ @@ -1731,7 +1929,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 @@ -1767,7 +1965,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 @@ -1818,7 +2023,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 { @@ -1836,7 +2041,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) @@ -2050,7 +2255,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, @@ -2106,7 +2311,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 { @@ -2115,7 +2320,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()]-- }) } @@ -2178,20 +2383,16 @@ 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, 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: cfg.hostToNetAddress, Proxy: cfg.Proxy, UserAgentName: userAgentName, UserAgentVersion: userAgentVersion, @@ -2235,7 +2436,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) @@ -3079,14 +3280,12 @@ 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 = addrs[0] } - addresses := wireToAddrmgrNetAddresses(addrs) - s.addrManager.AddAddresses(addresses, srcAddr) + s.addrManager.AddAddresses(addrs, srcAddr) }(seeder) } } @@ -3423,8 +3622,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 @@ -3779,20 +3977,31 @@ 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 + // 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 } + 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 } @@ -3978,7 +4187,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 @@ -4014,17 +4223,35 @@ 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 // lookup over Tor if necessary. - ips, err := dcrdLookup(host) + ips, err := cfg.dcrdLookup(host) if err != nil { return nil, err } @@ -4032,11 +4259,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, @@ -4079,7 +4301,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..e533a26fb9 --- /dev/null +++ b/server_test.go @@ -0,0 +1,116 @@ +// 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" + "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) { + // 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 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) + }, + 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 { + 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.String(), test.want.String()) { + t.Errorf("%q: unexpected result -- got %v, want %v", test.name, + result, test.want) + return + } + } +} 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..bb89543c1f --- /dev/null +++ b/wire/msgaddrv2.go @@ -0,0 +1,311 @@ +// 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 { + case na.Type == IPv4Address: + var ip [4]byte + err := readElement(r, &ip) + if err != nil { + return nil, err + } + ipBytes = ip[:] + 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 for "+ + "protocol version %d", na.Type, pver) + 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 { + case na.Type == IPv4Address: + var ip [4]byte + if netAddrIP != nil { + copy(ip[:], netAddrIP) + } + err = writeElement(w, ip) + if err != nil { + return err + } + case na.Type == IPv6Address: + var ip [16]byte + if netAddrIP != nil { + copy(ip[:], net.IP(netAddrIP).To16()) + } + err = writeElement(w, ip) + 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("unsupported network address type %v for "+ + "protocol version %d", na.Type, pver) + 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 { + 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 +} + +// 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..1433a787fe --- /dev/null +++ b/wire/msgaddrv2_test.go @@ -0,0 +1,333 @@ +// 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, + } + + 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 + 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, + } + + 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) + torv3Address = newNetAddressV2(TORv3Address, torV3IpBytes, 8336) +) + +// 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: "protocol version 11", + pver: RelayTORv3Version, + want: 51003, + }, { + name: "latest protocol version", + pver: ProtocolVersion, + want: 51003, + }} + + 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, + torv3Address, + }, + wantBytes: bytes.Join([][]byte{ + {0x03}, + serializedIPv4NetAddressBytes, + serializedIPv6NetAddressBytes, + serializedTORv3NetAddressBytes, + }, []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} + addrv2 := NewMsgAddrV2() + + 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, + }, { + 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)) + 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..73dd7b5892 --- /dev/null +++ b/wire/netaddressv2.go @@ -0,0 +1,52 @@ +// 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 + TORv3Address +) + +// 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..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 = 9 + ProtocolVersion uint32 = 11 // NodeBloomVersion is the protocol version which added the SFNodeBloom // service flag (unused). @@ -51,6 +51,14 @@ 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 + + // RelayTORv3Version is the protocol version which adds support for relaying + // TORv3 addresses. + RelayTORv3Version uint32 = 11 ) // ServiceFlag identifies services supported by a Decred peer.