diff --git a/addrmgr/addrmanager.go b/addrmgr/addrmanager.go index fa21ebeb4..981c97a38 100644 --- a/addrmgr/addrmanager.go +++ b/addrmgr/addrmanager.go @@ -738,27 +738,46 @@ func (a *AddrManager) reset() { } } +// ParseHost deconstructs a given host address to its []byte representation +// and also returns the network address type. If the host cannot be decoded, +// then an unknown address type is returned without error. +func ParseHost(host string) (NetAddressType, []byte) { + // Look for IPv4 or IPv6 addresses + if ip := net.ParseIP(host); ip != nil { + if isIPv4(ip) { + return IPv4Address, ip.To4() + } + return IPv6Address, ip + } + + // The given host address could not be recognized + return UnknownAddressType, nil +} + // HostToNetAddress parses and returns a network address given a hostname in a -// supported format (IPv4, IPv6, TORv2). If the hostname cannot be immediately +// supported format (IPv4, IPv6). 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) { - var ip net.IP - if ip = net.ParseIP(host); ip == nil { - ips, err := a.lookupFunc(host) - if err != nil { - return nil, err - } - if len(ips) == 0 { - return nil, fmt.Errorf("no addresses found for %s", host) - } - ip = ips[0] + addrType, addrBytes := ParseHost(host) + if addrType != 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 NewNetAddressFromParams(addrType, addrBytes, port, now, services) } - - return NewNetAddressFromIPPort(ip, port, services), nil + // Cannot determine the host address type. Must use DNS. + ips, err := a.lookupFunc(host) + if err != nil { + return nil, err + } + if len(ips) == 0 { + return nil, fmt.Errorf("no addresses found for %s", host) + } + return NewNetAddressFromIPPort(ips[0], port, services), nil } // GetAddress returns a single address that should be routable. It picks a diff --git a/addrmgr/error.go b/addrmgr/error.go index 559e7ea70..ba54cda99 100644 --- a/addrmgr/error.go +++ b/addrmgr/error.go @@ -18,6 +18,10 @@ const ( // ErrUnknownAddressType indicates that the network address type could not // be determined from a network address' bytes. ErrUnknownAddressType = ErrorKind("ErrUnknownAddressType") + + // ErrMismatchedAddressType indicates that a network address was expected to + // be a certain type, but the derived type does not match. + ErrMismatchedAddressType = ErrorKind("ErrMismatchedAddressType") ) // Error satisfies the error interface and prints human-readable errors. diff --git a/addrmgr/error_test.go b/addrmgr/error_test.go index 91cf788a8..ed411b933 100644 --- a/addrmgr/error_test.go +++ b/addrmgr/error_test.go @@ -28,6 +28,12 @@ func TestErrors(t *testing.T) { description: "unknown address type", wantErr: ErrUnknownAddressType, }, + { + name: "ErrMismatchedAddressType", + errorKind: ErrMismatchedAddressType, + description: "mismatched address type", + wantErr: ErrMismatchedAddressType, + }, } for _, test := range tests { diff --git a/addrmgr/netaddress.go b/addrmgr/netaddress.go index 9a32d7d99..53d2b11a5 100644 --- a/addrmgr/netaddress.go +++ b/addrmgr/netaddress.go @@ -113,9 +113,42 @@ func canonicalizeIP(addrType NetAddressType, addrBytes []byte) []byte { return addrBytes } -// newNetAddressFromString creates a new address manager network address from the -// provided string. The address is expected to be provided in the format -// host:port. +// checkNetAddressType returns an error if the suggested address type does not +// appear to match the provided address. +func checkNetAddressType(addrType NetAddressType, addrBytes []byte) error { + derivedAddressType, err := deriveNetAddressType(addrBytes) + if err != nil { + return err + } + if addrType != derivedAddressType { + str := fmt.Sprintf("derived address type does not match expected value"+ + " (got %v, expected %v, address bytes %v).", derivedAddressType, + addrType, addrBytes) + return makeError(ErrMismatchedAddressType, str) + } + return nil +} + +// NewNetAddressFromParams creates a new network address from the given +// parameters. If the provided address type does not appear to match the +// address, an error is returned. +func NewNetAddressFromParams(netAddressType NetAddressType, addrBytes []byte, port uint16, timestamp time.Time, services wire.ServiceFlag) (*NetAddress, error) { + canonicalizedIP := canonicalizeIP(netAddressType, addrBytes) + err := checkNetAddressType(netAddressType, canonicalizedIP) + if err != nil { + return nil, err + } + return &NetAddress{ + Type: netAddressType, + IP: canonicalizedIP, + Port: port, + Services: services, + Timestamp: timestamp, + }, nil +} + +// newNetAddressFromString creates a new network address from the given string. +// The address string is expected to be provided in the format "host:port". func (a *AddrManager) newNetAddressFromString(addr string) (*NetAddress, error) { host, portStr, err := net.SplitHostPort(addr) if err != nil { @@ -125,8 +158,14 @@ func (a *AddrManager) newNetAddressFromString(addr string) (*NetAddress, error) if err != nil { return nil, err } - - return a.HostToNetAddress(host, uint16(port), wire.SFNodeNetwork) + addrType, addrBytes := ParseHost(host) + if addrType == UnknownAddressType { + str := fmt.Sprintf("failed to deserialize address %s", addr) + return nil, makeError(ErrUnknownAddressType, str) + } + timestamp := time.Unix(time.Now().Unix(), 0) + return NewNetAddressFromParams(addrType, addrBytes, uint16(port), timestamp, + wire.SFNodeNetwork) } // NewNetAddressFromIPPort creates a new network address given an ip, port, and diff --git a/addrmgr/netaddress_test.go b/addrmgr/netaddress_test.go index 6e488b6b5..b5a82c654 100644 --- a/addrmgr/netaddress_test.go +++ b/addrmgr/netaddress_test.go @@ -103,10 +103,19 @@ func TestKey(t *testing.T) { } for _, test := range tests { - netAddr := NewNetAddressFromIPPort(net.ParseIP(test.ip), test.port, wire.SFNodeNetwork) + host_ip := test.ip + addrType, addrBytes := ParseHost(host_ip) + + netAddr, err := NewNetAddressFromParams(addrType, addrBytes, test.port, + time.Now(), wire.SFNodeNetwork) + if err != nil { + t.Fatalf("failed to construct net address from host %q: %v", + host_ip, err) + } + key := netAddr.Key() if key != test.want { - t.Errorf("unexpected network address key -- got %s, want %s", + t.Errorf("unexpected network address key -- got %q, want %q", key, test.want) continue } @@ -142,6 +151,96 @@ func TestAddService(t *testing.T) { } } +// TestNewNetAddressFromParams verifies that the NewNetAddressFromParams +// constructor correctly creates a network address with expected field values. +func TestNewNetAddressFromParams(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 + error_expected bool + }{ + { + 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, + }, + error_expected: false, + }, + { + 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, + }, + error_expected: false, + }, + { + 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, + }, + error_expected: false, + }, + { + name: "Error: Cannot derive net address type", + addrType: UnknownAddressType, + addrBytes: []byte{0x01, 0x02, 0x03}, + want: nil, + error_expected: true, + }, + { + name: "Error: the provided type doesn't match the bytes", + addrType: IPv6Address, + addrBytes: net.ParseIP("127.0.0.1").To4(), + want: nil, + error_expected: true, + }, + { + name: "Error: no address bytes were provided", + addrType: UnknownAddressType, + addrBytes: nil, + want: nil, + error_expected: true, + }} + + for _, test := range tests { + addr, err := NewNetAddressFromParams(test.addrType, test.addrBytes, + port, timestamp, services) + if err != nil && test.error_expected == false { + 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) + } + } +} + // TestNewNetAddressFromString verifies that the newNetAddressFromString // constructor correctly creates a network address with expected field values. func TestNewNetAddressFromString(t *testing.T) {