Skip to content

Commit

Permalink
addrmgr: Add NewNetAddressFromParams and ParseHost
Browse files Browse the repository at this point in the history
This commit creates two new functions: ParseHost and
NewNetAddressFromParams. The goal is to allow easier creation of net
addresses without needing to resort to DNS lookups. DNS lookups will be
removed from addrmgr.
  • Loading branch information
matthawkins90 committed Jul 10, 2024
1 parent f8690dd commit c338f5e
Show file tree
Hide file tree
Showing 5 changed files with 187 additions and 20 deletions.
45 changes: 32 additions & 13 deletions addrmgr/addrmanager.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 4 additions & 0 deletions addrmgr/error.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
6 changes: 6 additions & 0 deletions addrmgr/error_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
49 changes: 44 additions & 5 deletions addrmgr/netaddress.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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
Expand Down
103 changes: 101 additions & 2 deletions addrmgr/netaddress_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down Expand Up @@ -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) {
Expand Down

0 comments on commit c338f5e

Please sign in to comment.