mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-03-28 13:52:57 +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 (
|
||||
"context"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/netip"
|
||||
"slices"
|
||||
"sync"
|
||||
|
|
@ -36,6 +37,7 @@ import (
|
|||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/ethereum/go-ethereum/metrics"
|
||||
"github.com/ethereum/go-ethereum/p2p/enode"
|
||||
"github.com/ethereum/go-ethereum/p2p/enr"
|
||||
"github.com/ethereum/go-ethereum/p2p/netutil"
|
||||
)
|
||||
|
||||
|
|
@ -205,6 +207,13 @@ func (tab *Table) close() {
|
|||
func (tab *Table) setFallbackNodes(nodes []*enode.Node) error {
|
||||
nursery := make([]*enode.Node, 0, len(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 {
|
||||
return fmt.Errorf("bad bootstrap node %q: %v", n, err)
|
||||
}
|
||||
|
|
@ -218,6 +227,42 @@ func (tab *Table) setFallbackNodes(nodes []*enode.Node) error {
|
|||
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.
|
||||
func (tab *Table) isInitDone() bool {
|
||||
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 {
|
||||
key, err := crypto.GenerateKey()
|
||||
if err != nil {
|
||||
|
|
|
|||
Loading…
Reference in a new issue