mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-08-18 09:53:48 +00:00
p2p: add dynamic name resolution for the dialer
This commit is contained in:
parent
e0deac7f6f
commit
09f5fd6983
4 changed files with 154 additions and 25 deletions
46
p2p/dial.go
46
p2p/dial.go
|
|
@ -135,6 +135,7 @@ type dialConfig struct {
|
|||
log log.Logger
|
||||
clock mclock.Clock
|
||||
rand *mrand.Rand
|
||||
ttl time.Duration
|
||||
}
|
||||
|
||||
func (cfg dialConfig) withDefaults() dialConfig {
|
||||
|
|
@ -274,13 +275,15 @@ loop:
|
|||
case node := <-d.addStaticCh:
|
||||
id := node.ID()
|
||||
_, exists := d.static[id]
|
||||
d.log.Trace("Adding static node", "id", id, "ip", node.IPAddr(), "added", !exists)
|
||||
d.log.Trace("Adding static node", "id", id, "addr", node.DisplayAddr(), "ip", node.IPAddr(), "added", !exists)
|
||||
if exists {
|
||||
continue loop
|
||||
}
|
||||
task := newDialTask(node, staticDialedConn)
|
||||
d.static[id] = task
|
||||
if d.checkDial(node) == nil {
|
||||
if err := d.checkDial(node); err != nil {
|
||||
d.log.Trace("Discarding dial candidate", "id", node.ID(), "addr", node.DisplayAddr(), "ip", node.IPAddr(), "reason", err)
|
||||
} else {
|
||||
d.addToStaticPool(task)
|
||||
}
|
||||
|
||||
|
|
@ -436,7 +439,7 @@ func (d *dialScheduler) removeFromStaticPool(idx int) {
|
|||
// startDial runs the given dial task in a separate goroutine.
|
||||
func (d *dialScheduler) startDial(task *dialTask) {
|
||||
node := task.dest()
|
||||
d.log.Trace("Starting p2p dial", "id", node.ID(), "ip", node.IPAddr(), "flag", task.flags)
|
||||
d.log.Trace("Starting p2p dial", "id", node.ID(), "addr", node.DisplayAddr(), "ip", node.IPAddr(), "flag", task.flags)
|
||||
hkey := string(node.ID().Bytes())
|
||||
d.history.add(hkey, d.clock.Now().Add(dialHistoryExpiration))
|
||||
d.dialing[node.ID()] = task
|
||||
|
|
@ -473,7 +476,7 @@ func (t *dialTask) dest() *enode.Node {
|
|||
}
|
||||
|
||||
func (t *dialTask) run(d *dialScheduler) {
|
||||
if t.needResolve() && !t.resolve(d) {
|
||||
if !t.resolveIfNeeded(d) {
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -488,8 +491,36 @@ func (t *dialTask) run(d *dialScheduler) {
|
|||
}
|
||||
}
|
||||
|
||||
func (t *dialTask) needResolve() bool {
|
||||
return t.flags&staticDialedConn != 0 && !t.dest().IPAddr().IsValid()
|
||||
// resolveIfNeeded attempts to resolve the node's IP address if it is invalid.
|
||||
// It returns true if the node's IP address is valid after resolution attempts.
|
||||
func (t *dialTask) resolveIfNeeded(d *dialScheduler) bool {
|
||||
node := t.dest()
|
||||
|
||||
if t.flags&staticDialedConn != 0 {
|
||||
if t.resolve(d) && node.IPAddr().IsValid() {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
if node.NeedsDNSResolve() {
|
||||
if t.resolveDNS(d) && node.IPAddr().IsValid() {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// resolveDNS attempts to resolve the DNS name of the destination node.
|
||||
// It returns true if resolution succeeds.
|
||||
func (t *dialTask) resolveDNS(d *dialScheduler) bool {
|
||||
node := t.dest()
|
||||
d.log.Trace("Starting DNS resolution", "id", node.ID(), "addr", node.DisplayAddr())
|
||||
if err := node.RefreshDNS(d.dialConfig.ttl); err != nil {
|
||||
d.log.Trace("DNS resolution failed", "id", node.ID(), "addr", node.DisplayAddr())
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// resolve attempts to find the current endpoint for the destination
|
||||
|
|
@ -533,8 +564,7 @@ func (t *dialTask) dial(d *dialScheduler, dest *enode.Node) error {
|
|||
dialMeter.Mark(1)
|
||||
fd, err := d.dialer.Dial(d.ctx, dest)
|
||||
if err != nil {
|
||||
addr, _ := dest.TCPEndpoint()
|
||||
d.log.Trace("Dial error", "id", dest.ID(), "addr", addr, "conn", t.flags, "err", cleanupDialErr(err))
|
||||
d.log.Trace("Dial error", "id", dest.ID(), "addr", dest.DisplayAddr(), "ip", dest.IPAddr(), "conn", t.flags, "err", cleanupDialErr(err))
|
||||
dialConnectionError.Mark(1)
|
||||
return &dialError{err}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ import (
|
|||
"net"
|
||||
"net/netip"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/p2p/enr"
|
||||
"github.com/ethereum/go-ethereum/rlp"
|
||||
|
|
@ -41,6 +42,10 @@ type Node struct {
|
|||
ip netip.Addr
|
||||
udp uint16
|
||||
tcp uint16
|
||||
// dns information
|
||||
dnsName string
|
||||
dnsResolved time.Time
|
||||
dnsTTL time.Duration
|
||||
}
|
||||
|
||||
// New wraps a node record. The record must be valid according to the given
|
||||
|
|
@ -257,6 +262,84 @@ func (n *Node) String() string {
|
|||
return "enr:" + b64
|
||||
}
|
||||
|
||||
// resolveDNS attempts to resolve a DNS name to an IP address
|
||||
func (n *Node) resolveDNS(dnsName string) (netip.Addr, error) {
|
||||
ips, err := net.LookupIP(dnsName)
|
||||
if err != nil {
|
||||
return netip.Addr{}, err
|
||||
}
|
||||
for _, ip := range ips {
|
||||
if ip4 := ip.To4(); ip4 != nil {
|
||||
addr, ok := netip.AddrFromSlice(ip4)
|
||||
if ok {
|
||||
return addr, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
// Fall back to IPv6 if no IPv4 is available
|
||||
for _, ip := range ips {
|
||||
addr, ok := netip.AddrFromSlice(ip)
|
||||
if ok {
|
||||
return addr, nil
|
||||
}
|
||||
}
|
||||
|
||||
return netip.Addr{}, errors.New("no valid IP address found")
|
||||
}
|
||||
|
||||
// SetDNS sets the DNS name and resolves it to an IP address
|
||||
func (n *Node) SetDNS(dnsName string, ttl time.Duration) error {
|
||||
ip, err := n.resolveDNS(dnsName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
n.dnsName = dnsName
|
||||
n.dnsResolved = time.Now()
|
||||
n.dnsTTL = ttl
|
||||
|
||||
if ip.Is4() {
|
||||
n.setIP4(ip)
|
||||
} else {
|
||||
n.setIP6(ip)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DNSName returns the stored DNS name
|
||||
func (n *Node) DNSName() string {
|
||||
return n.dnsName
|
||||
}
|
||||
|
||||
// RefreshDNS updates the IP address from the stored DNS name
|
||||
func (n *Node) RefreshDNS(ttl time.Duration) error {
|
||||
if n.dnsName == "" {
|
||||
return errors.New("no DNS name set")
|
||||
}
|
||||
return n.SetDNS(n.dnsName, ttl)
|
||||
}
|
||||
|
||||
// DisplayAddr returns either "hostname:port" or "ip:port"
|
||||
func (n *Node) DisplayAddr() string {
|
||||
addr := n.dnsName
|
||||
if addr == "" {
|
||||
addr = n.ip.String()
|
||||
}
|
||||
return fmt.Sprintf("%s:%d", addr, n.tcp)
|
||||
}
|
||||
|
||||
// NeedsDNSResolve returns true if the node has a DNS name that needs resolution
|
||||
func (n *Node) NeedsDNSResolve() bool {
|
||||
if n.dnsName == "" {
|
||||
return false
|
||||
}
|
||||
return !n.ip.IsValid() || time.Since(n.dnsResolved) > n.dnsTTL
|
||||
}
|
||||
|
||||
func (n *Node) GetTTL() time.Duration {
|
||||
return n.dnsTTL
|
||||
}
|
||||
|
||||
// MarshalText implements encoding.TextMarshaler.
|
||||
func (n *Node) MarshalText() ([]byte, error) {
|
||||
return []byte(n.String()), nil
|
||||
|
|
|
|||
|
|
@ -126,20 +126,6 @@ func parseComplete(rawurl string) (*Node, error) {
|
|||
if id, err = parsePubkey(u.User.String()); err != nil {
|
||||
return nil, fmt.Errorf("invalid public key (%v)", err)
|
||||
}
|
||||
// Parse the IP address.
|
||||
ip := net.ParseIP(u.Hostname())
|
||||
if ip == nil {
|
||||
ips, err := lookupIPFunc(u.Hostname())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ip = ips[0]
|
||||
}
|
||||
// Ensure the IP is 4 bytes long for IPv4 addresses.
|
||||
if ipv4 := ip.To4(); ipv4 != nil {
|
||||
ip = ipv4
|
||||
}
|
||||
// Parse the port numbers.
|
||||
if tcpPort, err = strconv.ParseUint(u.Port(), 10, 16); err != nil {
|
||||
return nil, errors.New("invalid port")
|
||||
}
|
||||
|
|
@ -151,7 +137,18 @@ func parseComplete(rawurl string) (*Node, error) {
|
|||
return nil, errors.New("invalid discport in query")
|
||||
}
|
||||
}
|
||||
return NewV4(id, ip, int(tcpPort), int(udpPort)), nil
|
||||
// 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
|
||||
}
|
||||
|
||||
// Create node for DNS name
|
||||
node := NewV4(id, nil, int(tcpPort), int(udpPort))
|
||||
node.dnsName = hostname
|
||||
return node, nil
|
||||
}
|
||||
|
||||
// parsePubkey parses a hex-encoded secp256k1 public key.
|
||||
|
|
@ -184,9 +181,14 @@ func (n *Node) URLv4() string {
|
|||
if !n.ip.IsValid() {
|
||||
u.Host = nodeid
|
||||
} else {
|
||||
addr := net.TCPAddr{IP: n.IP(), Port: n.TCP()}
|
||||
u.User = url.User(nodeid)
|
||||
u.Host = addr.String()
|
||||
// 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())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -64,6 +64,10 @@ const (
|
|||
|
||||
// Maximum amount of time allowed for writing a complete message.
|
||||
frameWriteTimeout = 20 * time.Second
|
||||
|
||||
defaultTTL = 5 * time.Minute
|
||||
minTTL = 1 * time.Second
|
||||
maxTTL = 24 * time.Hour
|
||||
)
|
||||
|
||||
var (
|
||||
|
|
@ -122,6 +126,11 @@ type Config struct {
|
|||
// allowed to connect, even above the peer limit.
|
||||
TrustedNodes []*enode.Node
|
||||
|
||||
// TTL is the time-to-live for DNS discovery queries
|
||||
// This value is passed to the DNS resolvers when performing name
|
||||
// resolutions.
|
||||
TTL time.Duration `toml:",omitempty"`
|
||||
|
||||
// Connectivity can be restricted to certain IP networks.
|
||||
// If this option is set to a non-nil value, only hosts which match one of the
|
||||
// IP networks contained in the list are considered.
|
||||
|
|
@ -620,6 +629,11 @@ func (srv *Server) setupDialScheduler() {
|
|||
if config.dialer == nil {
|
||||
config.dialer = tcpDialer{&net.Dialer{Timeout: defaultDialTimeout}}
|
||||
}
|
||||
if srv.TTL >= minTTL && srv.TTL <= maxTTL {
|
||||
config.ttl = srv.TTL
|
||||
} else {
|
||||
config.ttl = defaultTTL
|
||||
}
|
||||
srv.dialsched = newDialScheduler(config, srv.discmix, srv.SetupConn)
|
||||
for _, n := range srv.StaticNodes {
|
||||
srv.dialsched.addStatic(n)
|
||||
|
|
|
|||
Loading…
Reference in a new issue