diff --git a/p2p/enode/node_test.go b/p2p/enode/node_test.go index f38c77415e..eb52d5d6ec 100644 --- a/p2p/enode/node_test.go +++ b/p2p/enode/node_test.go @@ -74,6 +74,7 @@ func TestNodeEndpoints(t *testing.T) { wantUDP int wantTCP int wantQUIC int + wantDNS string } tests := []endpointTest{ { @@ -268,6 +269,54 @@ func TestNodeEndpoints(t *testing.T) { wantIP: netip.MustParseAddr("2001::ff00:0042:8329"), wantQUIC: 9001, }, + { + name: "dns-only", + node: func() *Node { + var r enr.Record + n := SignNull(&r, id) + n.dnsName = "example.com" + n.tcp = 30303 + n.udp = 30303 + return n + }(), + wantTCP: 30303, + wantUDP: 30303, + wantDNS: "example.com", + }, + { + name: "dns-with-ports", + node: func() *Node { + var r enr.Record + r.Set(enr.TCP(9000)) + r.Set(enr.UDP(9001)) + n := SignNull(&r, id) + n.dnsName = "node.example.org" + n.tcp = 9000 + n.udp = 9001 + return n + }(), + wantTCP: 9000, + wantUDP: 9001, + wantDNS: "node.example.org", + }, + { + name: "dns-with-ip-fallback", + node: func() *Node { + var r enr.Record + r.Set(enr.IPv4Addr(netip.MustParseAddr("192.168.1.1"))) + r.Set(enr.TCP(9000)) + r.Set(enr.UDP(9000)) + n := SignNull(&r, id) + n.dnsName = "node.example.org" + n.tcp = 9000 + n.udp = 9000 + return n + }(), + wantIP: netip.MustParseAddr("192.168.1.1"), + wantTCP: 9000, + wantUDP: 9000, + wantDNS: "node.example.org", + }, } for _, test := range tests { @@ -284,6 +333,9 @@ func TestNodeEndpoints(t *testing.T) { if quic, _ := test.node.QUICEndpoint(); test.wantQUIC != int(quic.Port()) { t.Errorf("node has wrong QUIC port %d, want %d", quic.Port(), test.wantQUIC) } + if test.wantDNS != test.node.DNSName() { + t.Errorf("node has wrong DNS name %s, want %s", test.node.DNSName(), test.wantDNS) + } }) } } diff --git a/p2p/enode/urlv4.go b/p2p/enode/urlv4.go index f2ac90664b..9cb50ac856 100644 --- a/p2p/enode/urlv4.go +++ b/p2p/enode/urlv4.go @@ -101,6 +101,32 @@ func NewV4(pubkey *ecdsa.PublicKey, ip net.IP, tcp, udp int) *Node { return n } +func NewV4WithDNS(pubkey *ecdsa.PublicKey, ip net.IP, dnsName string, tcp, udp int) *Node { + var r enr.Record + if len(ip) > 0 { + r.Set(enr.IP(ip)) + } + // Always set TCP/UDP ports regardless of IP + // This is to ensure that the node is always + // considered valid even if the IP is not + // set. + if tcp != 0 { + r.Set(enr.TCP(tcp)) + } + if udp != 0 { + r.Set(enr.UDP(udp)) + } + signV4Compat(&r, pubkey) + n, err := New(v4CompatID{}, &r) + if err != nil { + panic(err) + } + n.dnsName = dnsName + n.tcp = uint16(tcp) + n.udp = uint16(udp) + return n +} + // isNewV4 returns true for nodes created by NewV4. func isNewV4(n *Node) bool { var k s256raw @@ -139,16 +165,18 @@ func parseComplete(rawurl string) (*Node, error) { } // Check if hostname is an IP address and create node accordingly hostname := u.Hostname() - if ip := net.ParseIP(hostname); ip != nil { - // Create node with IP - node := NewV4(id, ip, int(tcpPort), int(udpPort)) - return node, nil + ip := net.ParseIP(hostname) + if ip == nil { + ips, err := lookupIPFunc(hostname) + if err != nil { + return NewV4WithDNS(id, nil, hostname, int(tcpPort), int(udpPort)), nil + } + ip = ips[0] } - - // Create node for DNS name - node := NewV4(id, nil, int(tcpPort), int(udpPort)) - node.dnsName = hostname - return node, nil + if ipv4 := ip.To4(); ipv4 != nil { + ip = ipv4 + } + return NewV4(id, ip, int(tcpPort), int(udpPort)), nil } // parsePubkey parses a hex-encoded secp256k1 public key. @@ -178,20 +206,19 @@ func (n *Node) URLv4() string { nodeid = fmt.Sprintf("%s.%x", scheme, n.id[:]) } u := url.URL{Scheme: "enode"} - if !n.ip.IsValid() { + if !n.ip.IsValid() && n.dnsName == "" { u.Host = nodeid + return u.String() + } + u.User = url.User(nodeid) + if n.dnsName != "" { + u.Host = fmt.Sprintf("%s:%d", n.dnsName, n.TCP()) } else { - u.User = url.User(nodeid) - // Use DNS name if available, otherwise use IP - if n.DNSName() != "" { - u.Host = fmt.Sprintf("%s:%d", n.DNSName(), n.TCP()) - } else { - addr := net.TCPAddr{IP: n.IP(), Port: n.TCP()} - u.Host = addr.String() - } - if n.UDP() != n.TCP() { - u.RawQuery = "discport=" + strconv.Itoa(n.UDP()) - } + addr := net.TCPAddr{IP: n.IP(), Port: n.TCP()} + u.Host = addr.String() + } + if n.UDP() != n.TCP() { + u.RawQuery = "discport=" + strconv.Itoa(n.UDP()) } return u.String() } diff --git a/p2p/enode/urlv4_test.go b/p2p/enode/urlv4_test.go index 33de96cc57..98728e6177 100644 --- a/p2p/enode/urlv4_test.go +++ b/p2p/enode/urlv4_test.go @@ -70,10 +70,6 @@ var parseNodeTests = []struct { wantError: enr.ErrInvalidSig.Error(), }, // Complete node URLs with IP address and ports - { - input: "enode://1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439@invalid.:3", - wantError: `no such host`, - }, { input: "enode://1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439@127.0.0.1:foo", wantError: `invalid port`, @@ -82,6 +78,16 @@ var parseNodeTests = []struct { input: "enode://1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439@127.0.0.1:3?discport=foo", wantError: `invalid discport in query`, }, + { + input: "enode://1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439@valid.:3", + wantResult: NewV4WithDNS( + hexPubkey("1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439"), + nil, + "valid.", + 3, + 3, + ), + }, { input: "enode://1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439@127.0.0.1:52150", wantResult: NewV4(