diff --git a/p2p/errors.go b/p2p/errors.go index cb6a7051a..91b8f96c2 100644 --- a/p2p/errors.go +++ b/p2p/errors.go @@ -18,3 +18,31 @@ type ErrSwitchAuthenticationFailure struct { func (e ErrSwitchAuthenticationFailure) Error() string { return fmt.Sprintf("Failed to authenticate peer. Dialed %v, but got peer with ID %s", e.Dialed, e.Got) } + +//------------------------------------------------------------------- + +type ErrNetAddressNoID struct { + Addr string +} + +func (e ErrNetAddressNoID) Error() string { + return fmt.Errorf("Address (%s) does not contain ID", e.Addr) +} + +type ErrNetAddressInvalid struct { + Addr string + Err error +} + +func (e ErrNetAddressInvalid) Error() string { + return fmt.Errorf("Invalid address (%s): %v", e.Addr, e.Err) +} + +type ErrNetAddressLookup struct { + Addr string + Err error +} + +func (e ErrNetAddressLookup) Error() string { + return fmt.Errorf("Error looking up host (%s): %v", e.Addr, e.Err) +} diff --git a/p2p/netaddress.go b/p2p/netaddress.go index 6cf86f22e..80b8e18bb 100644 --- a/p2p/netaddress.go +++ b/p2p/netaddress.go @@ -19,11 +19,13 @@ import ( // NetAddress defines information about a peer on the network // including its ID, IP address, and port. type NetAddress struct { - ID ID - IP net.IP - Port uint16 - Name string // optional DNS name - str string + ID ID `json:"id"` + IP net.IP `json:"ip"` + Port uint16 `json:"port"` + Name string `json:"name"` // optional DNS name + + // memoize .String() + str string } // IDAddressString returns id@hostPort. @@ -57,10 +59,11 @@ func NewNetAddress(id ID, addr net.Addr) *NetAddress { // NewNetAddressString returns a new NetAddress using the provided address in // the form of "ID@IP:Port". // Also resolves the host if host is not an IP. +// Errors are of type ErrNetAddressXxx where Xxx is in (NoID, Invalid, Lookup) func NewNetAddressString(addr string) (*NetAddress, error) { spl := strings.Split(addr, "@") if len(spl) < 2 { - return nil, fmt.Errorf("Address (%s) does not contain ID", addr) + return nil, ErrNetAddressNoID{addr} } return NewNetAddressStringWithOptionalID(addr) } @@ -77,11 +80,12 @@ func NewNetAddressStringWithOptionalID(addr string) (*NetAddress, error) { idStr := spl[0] idBytes, err := hex.DecodeString(idStr) if err != nil { - return nil, cmn.ErrorWrap(err, fmt.Sprintf("Address (%s) contains invalid ID", addrWithoutProtocol)) + return nil, ErrNetAddressInvalid{addrWithoutProtocol, err} } if len(idBytes) != IDByteLength { - return nil, fmt.Errorf("Address (%s) contains ID of invalid length (%d). Should be %d hex-encoded bytes", - addrWithoutProtocol, len(idBytes), IDByteLength) + return nil, ErrNetAddressInvalid{ + addrWithoutProtocol, + fmt.Errorf("invalid hex length - got %d, expected %d", len(idBytes), IDByteLength)} } id, addrWithoutProtocol = ID(idStr), spl[1] @@ -89,7 +93,7 @@ func NewNetAddressStringWithOptionalID(addr string) (*NetAddress, error) { host, portStr, err := net.SplitHostPort(addrWithoutProtocol) if err != nil { - return nil, err + return nil, ErrNetAddressInvalid{addrWithoutProtocol, err} } ip := net.ParseIP(host) @@ -97,7 +101,7 @@ func NewNetAddressStringWithOptionalID(addr string) (*NetAddress, error) { if len(host) > 0 { ips, err := net.LookupIP(host) if err != nil { - return nil, err + return nil, ErrNetAddressLookup{host, err} } ip = ips[0] } @@ -105,7 +109,7 @@ func NewNetAddressStringWithOptionalID(addr string) (*NetAddress, error) { port, err := strconv.ParseUint(portStr, 10, 16) if err != nil { - return nil, err + return nil, ErrNetAddressInvalid{portStr, err} } na := NewNetAddressIPPort(ip, uint16(port)) @@ -121,7 +125,7 @@ func NewNetAddressStrings(addrs []string) ([]*NetAddress, []error) { for _, addr := range addrs { netAddr, err := NewNetAddressString(addr) if err != nil { - errs = append(errs, fmt.Errorf("Error in address %s: %v", addr, err)) + errs = append(errs, err) } else { netAddrs = append(netAddrs, netAddr) } diff --git a/p2p/node_info.go b/p2p/node_info.go index 73af1a4af..7d149082c 100644 --- a/p2p/node_info.go +++ b/p2p/node_info.go @@ -21,6 +21,7 @@ func MaxNodeInfoSize() int { // between two peers during the Tendermint P2P handshake. type NodeInfo struct { // Authenticate + // TODO: replace with NetAddress ID ID `json:"id"` // authenticated identifier ListenAddr string `json:"listen_addr"` // accepting incoming @@ -37,7 +38,9 @@ type NodeInfo struct { // Validate checks the self-reported NodeInfo is safe. // It returns an error if there -// are too many Channels or any duplicate Channels. +// are too many Channels, if there are any duplicate Channels, +// if the ListenAddr is malformed, or if the ListenAddr is a host name +// that can not be resolved to some IP. // TODO: constraints for Moniker/Other? Or is that for the UI ? func (info NodeInfo) Validate() error { if len(info.Channels) > maxNumChannels { @@ -52,6 +55,13 @@ func (info NodeInfo) Validate() error { } channels[ch] = struct{}{} } + + // ensure ListenAddr is good + netAddr, err := NewNetAddressString(IDAddressString(info.ID, info.ListenAddr)) + if err != nil { + return err + } + return nil } @@ -116,7 +126,14 @@ OUTER_LOOP: func (info NodeInfo) NetAddress() *NetAddress { netAddr, err := NewNetAddressString(IDAddressString(info.ID, info.ListenAddr)) if err != nil { - panic(err) // everything should be well formed by now + switch err.(type) { + case ErrNetAddressLookup: + // XXX If the peer provided a host name and the lookup fails here + // we're out of luck. + // TODO: use a NetAddress in NodeInfo + default: + panic(err) // everything should be well formed by now + } } return netAddr }