From b44abf56a966016cbb651648ac2d7b6705e80b11 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Tue, 6 Dec 2022 16:25:53 +0100 Subject: [PATCH] cmd/devp2p: add --extaddr flag (#26312) The new flag allows configuring an explicit endpoint which is to be announced in the DHT. This feature was originally developed for the discv5 wormhole experiment (#25798), but it's useful in other contexts as well. --- cmd/devp2p/discv4cmd.go | 61 +++++++++++++++++++++++++++++++++++------ cmd/devp2p/discv5cmd.go | 17 ++++++------ 2 files changed, 61 insertions(+), 17 deletions(-) diff --git a/cmd/devp2p/discv4cmd.go b/cmd/devp2p/discv4cmd.go index 9d35880b1..94e61c36f 100644 --- a/cmd/devp2p/discv4cmd.go +++ b/cmd/devp2p/discv4cmd.go @@ -19,6 +19,7 @@ package main import ( "fmt" "net" + "strconv" "strings" "time" @@ -50,34 +51,34 @@ var ( Usage: "Sends ping to a node", Action: discv4Ping, ArgsUsage: "", - Flags: v4NodeFlags, + Flags: discoveryNodeFlags, } discv4RequestRecordCommand = &cli.Command{ Name: "requestenr", Usage: "Requests a node record using EIP-868 enrRequest", Action: discv4RequestRecord, ArgsUsage: "", - Flags: v4NodeFlags, + Flags: discoveryNodeFlags, } discv4ResolveCommand = &cli.Command{ Name: "resolve", Usage: "Finds a node in the DHT", Action: discv4Resolve, ArgsUsage: "", - Flags: v4NodeFlags, + Flags: discoveryNodeFlags, } discv4ResolveJSONCommand = &cli.Command{ Name: "resolve-json", Usage: "Re-resolves nodes in a nodes.json file", Action: discv4ResolveJSON, - Flags: v4NodeFlags, + Flags: discoveryNodeFlags, ArgsUsage: "", } discv4CrawlCommand = &cli.Command{ Name: "crawl", Usage: "Updates a nodes.json file with random nodes found in the DHT", Action: discv4Crawl, - Flags: flags.Merge(v4NodeFlags, []cli.Flag{crawlTimeoutFlag}), + Flags: flags.Merge(discoveryNodeFlags, []cli.Flag{crawlTimeoutFlag}), } discv4TestCommand = &cli.Command{ Name: "test", @@ -110,6 +111,10 @@ var ( Name: "addr", Usage: "Listening address", } + extAddrFlag = &cli.StringFlag{ + Name: "extaddr", + Usage: "UDP endpoint announced in ENR. You can provide a bare IP address or IP:port as the value of this flag.", + } crawlTimeoutFlag = &cli.DurationFlag{ Name: "timeout", Usage: "Time limit for the crawl.", @@ -122,11 +127,12 @@ var ( } ) -var v4NodeFlags = []cli.Flag{ +var discoveryNodeFlags = []cli.Flag{ bootnodesFlag, nodekeyFlag, nodedbFlag, listenAddrFlag, + extAddrFlag, } func discv4Ping(ctx *cli.Context) error { @@ -228,7 +234,7 @@ func discv4Test(ctx *cli.Context) error { // startV4 starts an ephemeral discovery V4 node. func startV4(ctx *cli.Context) *discover.UDPv4 { ln, config := makeDiscoveryConfig(ctx) - socket := listen(ln, ctx.String(listenAddrFlag.Name)) + socket := listen(ctx, ln) disc, err := discover.ListenV4(socket, ln, config) if err != nil { exit(err) @@ -266,7 +272,28 @@ func makeDiscoveryConfig(ctx *cli.Context) (*enode.LocalNode, discover.Config) { return ln, cfg } -func listen(ln *enode.LocalNode, addr string) *net.UDPConn { +func parseExtAddr(spec string) (ip net.IP, port int, ok bool) { + ip = net.ParseIP(spec) + if ip != nil { + return ip, 0, true + } + host, portstr, err := net.SplitHostPort(spec) + if err != nil { + return nil, 0, false + } + ip = net.ParseIP(host) + if ip == nil { + return nil, 0, false + } + port, err = strconv.Atoi(portstr) + if err != nil { + return nil, 0, false + } + return ip, port, true +} + +func listen(ctx *cli.Context, ln *enode.LocalNode) *net.UDPConn { + addr := ctx.String(listenAddrFlag.Name) if addr == "" { addr = "0.0.0.0:0" } @@ -274,6 +301,8 @@ func listen(ln *enode.LocalNode, addr string) *net.UDPConn { if err != nil { exit(err) } + + // Configure UDP endpoint in ENR from listener address. usocket := socket.(*net.UDPConn) uaddr := socket.LocalAddr().(*net.UDPAddr) if uaddr.IP.IsUnspecified() { @@ -282,6 +311,22 @@ func listen(ln *enode.LocalNode, addr string) *net.UDPConn { ln.SetFallbackIP(uaddr.IP) } ln.SetFallbackUDP(uaddr.Port) + + // If an ENR endpoint is set explicitly on the command-line, override + // the information from the listening address. Note this is careful not + // to set the UDP port if the external address doesn't have it. + extAddr := ctx.String(extAddrFlag.Name) + if extAddr != "" { + ip, port, ok := parseExtAddr(extAddr) + if !ok { + exit(fmt.Errorf("-%s: invalid external address %q", extAddrFlag.Name, extAddr)) + } + ln.SetStaticIP(ip) + if port != 0 { + ln.SetFallbackUDP(port) + } + } + return usocket } diff --git a/cmd/devp2p/discv5cmd.go b/cmd/devp2p/discv5cmd.go index 298196034..343e2a0d5 100644 --- a/cmd/devp2p/discv5cmd.go +++ b/cmd/devp2p/discv5cmd.go @@ -22,6 +22,7 @@ import ( "github.com/ethereum/go-ethereum/cmd/devp2p/internal/v5test" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/internal/flags" "github.com/ethereum/go-ethereum/p2p/discover" "github.com/urfave/cli/v2" ) @@ -42,18 +43,21 @@ var ( Name: "ping", Usage: "Sends ping to a node", Action: discv5Ping, + Flags: discoveryNodeFlags, } discv5ResolveCommand = &cli.Command{ Name: "resolve", Usage: "Finds a node in the DHT", Action: discv5Resolve, - Flags: []cli.Flag{bootnodesFlag}, + Flags: discoveryNodeFlags, } discv5CrawlCommand = &cli.Command{ Name: "crawl", Usage: "Updates a nodes.json file with random nodes found in the DHT", Action: discv5Crawl, - Flags: []cli.Flag{bootnodesFlag, crawlTimeoutFlag}, + Flags: flags.Merge(discoveryNodeFlags, []cli.Flag{ + crawlTimeoutFlag, + }), } discv5TestCommand = &cli.Command{ Name: "test", @@ -70,12 +74,7 @@ var ( Name: "listen", Usage: "Runs a node", Action: discv5Listen, - Flags: []cli.Flag{ - bootnodesFlag, - nodekeyFlag, - nodedbFlag, - listenAddrFlag, - }, + Flags: discoveryNodeFlags, } ) @@ -137,7 +136,7 @@ func discv5Listen(ctx *cli.Context) error { // startV5 starts an ephemeral discovery v5 node. func startV5(ctx *cli.Context) *discover.UDPv5 { ln, config := makeDiscoveryConfig(ctx) - socket := listen(ln, ctx.String(listenAddrFlag.Name)) + socket := listen(ctx, ln) disc, err := discover.ListenV5(socket, ln, config) if err != nil { exit(err)