From d1f507b7f16e359dc2773195edb72a22357e5424 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Thu, 22 Oct 2015 23:46:01 +0200 Subject: p2p/discover: support incomplete node URLs, add Resolve --- p2p/discover/node.go | 72 ++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 53 insertions(+), 19 deletions(-) (limited to 'p2p/discover/node.go') diff --git a/p2p/discover/node.go b/p2p/discover/node.go index dd19df3a2..fac493f01 100644 --- a/p2p/discover/node.go +++ b/p2p/discover/node.go @@ -26,6 +26,7 @@ import ( "math/rand" "net" "net/url" + "regexp" "strconv" "strings" @@ -37,6 +38,7 @@ import ( const nodeIDBits = 512 // Node represents a host on the network. +// The fields of Node may not be modified. type Node struct { IP net.IP // len 4 for IPv4 or 16 for IPv6 UDP, TCP uint16 // port numbers @@ -54,7 +56,9 @@ type Node struct { contested bool } -func newNode(id NodeID, ip net.IP, udpPort, tcpPort uint16) *Node { +// NewNode creates a new node. It is mostly meant to be used for +// testing purposes. +func NewNode(id NodeID, ip net.IP, udpPort, tcpPort uint16) *Node { if ipv4 := ip.To4(); ipv4 != nil { ip = ipv4 } @@ -71,31 +75,47 @@ func (n *Node) addr() *net.UDPAddr { return &net.UDPAddr{IP: n.IP, Port: int(n.UDP)} } +// Incomplete returns true for nodes with no IP address. +func (n *Node) Incomplete() bool { + return n.IP == nil +} + // The string representation of a Node is a URL. // Please see ParseNode for a description of the format. func (n *Node) String() string { - addr := net.TCPAddr{IP: n.IP, Port: int(n.TCP)} - u := url.URL{ - Scheme: "enode", - User: url.User(fmt.Sprintf("%x", n.ID[:])), - Host: addr.String(), - } - if n.UDP != n.TCP { - u.RawQuery = "discport=" + strconv.Itoa(int(n.UDP)) + u := url.URL{Scheme: "enode"} + if n.Incomplete() { + u.Host = fmt.Sprintf("%x", n.ID[:]) + } else { + addr := net.TCPAddr{IP: n.IP, Port: int(n.TCP)} + u.User = url.User(fmt.Sprintf("%x", n.ID[:])) + u.Host = addr.String() + if n.UDP != n.TCP { + u.RawQuery = "discport=" + strconv.Itoa(int(n.UDP)) + } } return u.String() } -// ParseNode parses a node URL. +var incompleteNodeURL = regexp.MustCompile("(?i)^(?:enode://)?([0-9a-f]+)$") + +// ParseNode parses a node designator. +// +// There are two basic forms of node designators +// - incomplete nodes, which only have the public key (node ID) +// - complete nodes, which contain the public key and IP/Port information +// +// For incomplete nodes, the designator must look like one of these // -// A node URL has scheme "enode". +// enode:// +// // -// The hexadecimal node ID is encoded in the username portion of the -// URL, separated from the host by an @ sign. The hostname can only be -// given as an IP address, DNS domain names are not allowed. The port -// in the host name section is the TCP listening port. If the TCP and -// UDP (discovery) ports differ, the UDP port is specified as query -// parameter "discport". +// For complete nodes, the node ID is encoded in the username portion +// of the URL, separated from the host by an @ sign. The hostname can +// only be given as an IP address, DNS domain names are not allowed. +// The port in the host name section is the TCP listening port. If the +// TCP and UDP (discovery) ports differ, the UDP port is specified as +// query parameter "discport". // // In the following example, the node URL describes // a node with IP address 10.3.58.6, TCP listening port 30303 @@ -103,12 +123,26 @@ func (n *Node) String() string { // // enode://@10.3.58.6:30303?discport=30301 func ParseNode(rawurl string) (*Node, error) { + if m := incompleteNodeURL.FindStringSubmatch(rawurl); m != nil { + id, err := HexID(m[1]) + if err != nil { + return nil, fmt.Errorf("invalid node ID (%v)", err) + } + return NewNode(id, nil, 0, 0), nil + } + return parseComplete(rawurl) +} + +func parseComplete(rawurl string) (*Node, error) { var ( id NodeID ip net.IP tcpPort, udpPort uint64 ) u, err := url.Parse(rawurl) + if err != nil { + return nil, err + } if u.Scheme != "enode" { return nil, errors.New("invalid URL scheme, want \"enode\"") } @@ -143,7 +177,7 @@ func ParseNode(rawurl string) (*Node, error) { return nil, errors.New("invalid discport in query") } } - return newNode(id, ip, uint16(udpPort), uint16(tcpPort)), nil + return NewNode(id, ip, uint16(udpPort), uint16(tcpPort)), nil } // MustParseNode parses a node URL. It panics if the URL is not valid. @@ -180,7 +214,7 @@ func HexID(in string) (NodeID, error) { if err != nil { return id, err } else if len(b) != len(id) { - return id, fmt.Errorf("wrong length, need %d hex bytes", len(id)) + return id, fmt.Errorf("wrong length, want %d hex chars", len(id)*2) } copy(id[:], b) return id, nil -- cgit