mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-07-18 10:50:44 +00:00
p2p/discover: resolve DNS hostnames for bootstrap nodes (#34101)
Fixes #31208
This commit is contained in:
parent
c3467dd8b5
commit
a2496852e9
2 changed files with 105 additions and 0 deletions
|
|
@ -25,6 +25,7 @@ package discover
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"slices"
|
"slices"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
@ -36,6 +37,7 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/log"
|
"github.com/ethereum/go-ethereum/log"
|
||||||
"github.com/ethereum/go-ethereum/metrics"
|
"github.com/ethereum/go-ethereum/metrics"
|
||||||
"github.com/ethereum/go-ethereum/p2p/enode"
|
"github.com/ethereum/go-ethereum/p2p/enode"
|
||||||
|
"github.com/ethereum/go-ethereum/p2p/enr"
|
||||||
"github.com/ethereum/go-ethereum/p2p/netutil"
|
"github.com/ethereum/go-ethereum/p2p/netutil"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -205,6 +207,13 @@ func (tab *Table) close() {
|
||||||
func (tab *Table) setFallbackNodes(nodes []*enode.Node) error {
|
func (tab *Table) setFallbackNodes(nodes []*enode.Node) error {
|
||||||
nursery := make([]*enode.Node, 0, len(nodes))
|
nursery := make([]*enode.Node, 0, len(nodes))
|
||||||
for _, n := range nodes {
|
for _, n := range nodes {
|
||||||
|
if n.Hostname() != "" && !n.IPAddr().IsValid() {
|
||||||
|
resolved, err := resolveBootnodeHostname(n, tab.log)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("bad bootstrap node %q: %v", n, err)
|
||||||
|
}
|
||||||
|
n = resolved
|
||||||
|
}
|
||||||
if err := n.ValidateComplete(); err != nil {
|
if err := n.ValidateComplete(); err != nil {
|
||||||
return fmt.Errorf("bad bootstrap node %q: %v", n, err)
|
return fmt.Errorf("bad bootstrap node %q: %v", n, err)
|
||||||
}
|
}
|
||||||
|
|
@ -218,6 +227,42 @@ func (tab *Table) setFallbackNodes(nodes []*enode.Node) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// resolveBootnodeHostname resolves the DNS hostname of a bootstrap node to an IP address.
|
||||||
|
func resolveBootnodeHostname(n *enode.Node, logger log.Logger) (*enode.Node, error) {
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
ips, err := net.DefaultResolver.LookupNetIP(ctx, "ip", n.Hostname())
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("DNS lookup failed for %q: %v", n.Hostname(), err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var ip4, ip6 netip.Addr
|
||||||
|
for _, ip := range ips {
|
||||||
|
if ip.Is4() && !ip4.IsValid() {
|
||||||
|
ip4 = ip
|
||||||
|
}
|
||||||
|
if ip.Is6() && !ip6.IsValid() {
|
||||||
|
ip6 = ip
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !ip4.IsValid() && !ip6.IsValid() {
|
||||||
|
return nil, fmt.Errorf("no IP addresses found for hostname %q", n.Hostname())
|
||||||
|
}
|
||||||
|
|
||||||
|
rec := n.Record()
|
||||||
|
if ip4.IsValid() {
|
||||||
|
rec.Set(enr.IPv4Addr(ip4))
|
||||||
|
}
|
||||||
|
if ip6.IsValid() {
|
||||||
|
rec.Set(enr.IPv6Addr(ip6))
|
||||||
|
}
|
||||||
|
rec.SetSeq(n.Seq())
|
||||||
|
resolved := enode.SignNull(rec, n.ID()).WithHostname(n.Hostname())
|
||||||
|
logger.Debug("Resolved bootstrap node hostname", "name", n.Hostname(), "ip", resolved.IP())
|
||||||
|
return resolved, nil
|
||||||
|
}
|
||||||
|
|
||||||
// isInitDone returns whether the table's initial seeding procedure has completed.
|
// isInitDone returns whether the table's initial seeding procedure has completed.
|
||||||
func (tab *Table) isInitDone() bool {
|
func (tab *Table) isInitDone() bool {
|
||||||
select {
|
select {
|
||||||
|
|
|
||||||
|
|
@ -490,6 +490,66 @@ func quickcfg() *quick.Config {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestSetFallbackNodes_DNSHostname(t *testing.T) {
|
||||||
|
// Create a node with a DNS hostname but no IP, simulating an enode URL
|
||||||
|
// like enode://<key>@localhost:30303.
|
||||||
|
key := newkey()
|
||||||
|
node := enode.NewV4(&key.PublicKey, nil, 30303, 30303).WithHostname("localhost")
|
||||||
|
|
||||||
|
// Verify the node has a hostname but no valid IP.
|
||||||
|
if node.Hostname() != "localhost" {
|
||||||
|
t.Fatal("expected hostname to be set")
|
||||||
|
}
|
||||||
|
if node.IPAddr().IsValid() {
|
||||||
|
t.Fatal("expected no IP address")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a table and set the hostname node as a bootnode.
|
||||||
|
// This should resolve the hostname to an IP address.
|
||||||
|
db, _ := enode.OpenDB(t.TempDir() + "/node.db")
|
||||||
|
defer db.Close()
|
||||||
|
|
||||||
|
cfg := Config{Log: testlog.Logger(t, log.LvlTrace)}
|
||||||
|
cfg = cfg.withDefaults()
|
||||||
|
tab := &Table{
|
||||||
|
cfg: cfg,
|
||||||
|
log: cfg.Log,
|
||||||
|
refreshReq: make(chan chan struct{}),
|
||||||
|
revalResponseCh: make(chan revalidationResponse),
|
||||||
|
addNodeCh: make(chan addNodeOp),
|
||||||
|
addNodeHandled: make(chan bool),
|
||||||
|
trackRequestCh: make(chan trackRequestOp),
|
||||||
|
initDone: make(chan struct{}),
|
||||||
|
closeReq: make(chan struct{}),
|
||||||
|
closed: make(chan struct{}),
|
||||||
|
ips: netutil.DistinctNetSet{Subnet: tableSubnet, Limit: tableIPLimit},
|
||||||
|
}
|
||||||
|
for i := range tab.buckets {
|
||||||
|
tab.buckets[i] = &bucket{
|
||||||
|
index: i,
|
||||||
|
ips: netutil.DistinctNetSet{Subnet: bucketSubnet, Limit: bucketIPLimit},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
err := tab.setFallbackNodes([]*enode.Node{node})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("setFallbackNodes failed: %v", err)
|
||||||
|
}
|
||||||
|
if len(tab.nursery) != 1 {
|
||||||
|
t.Fatalf("expected 1 nursery node, got %d", len(tab.nursery))
|
||||||
|
}
|
||||||
|
|
||||||
|
// The resolved node should have a valid IP and retain the hostname.
|
||||||
|
resolved := tab.nursery[0]
|
||||||
|
if !resolved.IPAddr().IsValid() {
|
||||||
|
t.Fatal("expected resolved node to have a valid IP")
|
||||||
|
}
|
||||||
|
if resolved.Hostname() != "localhost" {
|
||||||
|
t.Errorf("expected hostname to be preserved, got %q", resolved.Hostname())
|
||||||
|
}
|
||||||
|
t.Logf("resolved localhost to %v", resolved.IPAddr())
|
||||||
|
}
|
||||||
|
|
||||||
func newkey() *ecdsa.PrivateKey {
|
func newkey() *ecdsa.PrivateKey {
|
||||||
key, err := crypto.GenerateKey()
|
key, err := crypto.GenerateKey()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue