github.com/tirogen/go-ethereum@v1.10.12-0.20221226051715-250cfede41b6/cmd/devp2p/discv4cmd.go (about) 1 // Copyright 2019 The go-ethereum Authors 2 // This file is part of go-ethereum. 3 // 4 // go-ethereum is free software: you can redistribute it and/or modify 5 // it under the terms of the GNU General Public License as published by 6 // the Free Software Foundation, either version 3 of the License, or 7 // (at your option) any later version. 8 // 9 // go-ethereum is distributed in the hope that it will be useful, 10 // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 // GNU General Public License for more details. 13 // 14 // You should have received a copy of the GNU General Public License 15 // along with go-ethereum. If not, see <http://www.gnu.org/licenses/>. 16 17 package main 18 19 import ( 20 "fmt" 21 "net" 22 "strconv" 23 "strings" 24 "time" 25 26 "github.com/tirogen/go-ethereum/cmd/devp2p/internal/v4test" 27 "github.com/tirogen/go-ethereum/common" 28 "github.com/tirogen/go-ethereum/crypto" 29 "github.com/tirogen/go-ethereum/internal/flags" 30 "github.com/tirogen/go-ethereum/p2p/discover" 31 "github.com/tirogen/go-ethereum/p2p/enode" 32 "github.com/tirogen/go-ethereum/params" 33 "github.com/urfave/cli/v2" 34 ) 35 36 var ( 37 discv4Command = &cli.Command{ 38 Name: "discv4", 39 Usage: "Node Discovery v4 tools", 40 Subcommands: []*cli.Command{ 41 discv4PingCommand, 42 discv4RequestRecordCommand, 43 discv4ResolveCommand, 44 discv4ResolveJSONCommand, 45 discv4CrawlCommand, 46 discv4TestCommand, 47 }, 48 } 49 discv4PingCommand = &cli.Command{ 50 Name: "ping", 51 Usage: "Sends ping to a node", 52 Action: discv4Ping, 53 ArgsUsage: "<node>", 54 Flags: discoveryNodeFlags, 55 } 56 discv4RequestRecordCommand = &cli.Command{ 57 Name: "requestenr", 58 Usage: "Requests a node record using EIP-868 enrRequest", 59 Action: discv4RequestRecord, 60 ArgsUsage: "<node>", 61 Flags: discoveryNodeFlags, 62 } 63 discv4ResolveCommand = &cli.Command{ 64 Name: "resolve", 65 Usage: "Finds a node in the DHT", 66 Action: discv4Resolve, 67 ArgsUsage: "<node>", 68 Flags: discoveryNodeFlags, 69 } 70 discv4ResolveJSONCommand = &cli.Command{ 71 Name: "resolve-json", 72 Usage: "Re-resolves nodes in a nodes.json file", 73 Action: discv4ResolveJSON, 74 Flags: discoveryNodeFlags, 75 ArgsUsage: "<nodes.json file>", 76 } 77 discv4CrawlCommand = &cli.Command{ 78 Name: "crawl", 79 Usage: "Updates a nodes.json file with random nodes found in the DHT", 80 Action: discv4Crawl, 81 Flags: flags.Merge(discoveryNodeFlags, []cli.Flag{crawlTimeoutFlag}), 82 } 83 discv4TestCommand = &cli.Command{ 84 Name: "test", 85 Usage: "Runs tests against a node", 86 Action: discv4Test, 87 Flags: []cli.Flag{ 88 remoteEnodeFlag, 89 testPatternFlag, 90 testTAPFlag, 91 testListen1Flag, 92 testListen2Flag, 93 }, 94 } 95 ) 96 97 var ( 98 bootnodesFlag = &cli.StringFlag{ 99 Name: "bootnodes", 100 Usage: "Comma separated nodes used for bootstrapping", 101 } 102 nodekeyFlag = &cli.StringFlag{ 103 Name: "nodekey", 104 Usage: "Hex-encoded node key", 105 } 106 nodedbFlag = &cli.StringFlag{ 107 Name: "nodedb", 108 Usage: "Nodes database location", 109 } 110 listenAddrFlag = &cli.StringFlag{ 111 Name: "addr", 112 Usage: "Listening address", 113 } 114 extAddrFlag = &cli.StringFlag{ 115 Name: "extaddr", 116 Usage: "UDP endpoint announced in ENR. You can provide a bare IP address or IP:port as the value of this flag.", 117 } 118 crawlTimeoutFlag = &cli.DurationFlag{ 119 Name: "timeout", 120 Usage: "Time limit for the crawl.", 121 Value: 30 * time.Minute, 122 } 123 remoteEnodeFlag = &cli.StringFlag{ 124 Name: "remote", 125 Usage: "Enode of the remote node under test", 126 EnvVars: []string{"REMOTE_ENODE"}, 127 } 128 ) 129 130 var discoveryNodeFlags = []cli.Flag{ 131 bootnodesFlag, 132 nodekeyFlag, 133 nodedbFlag, 134 listenAddrFlag, 135 extAddrFlag, 136 } 137 138 func discv4Ping(ctx *cli.Context) error { 139 n := getNodeArg(ctx) 140 disc := startV4(ctx) 141 defer disc.Close() 142 143 start := time.Now() 144 if err := disc.Ping(n); err != nil { 145 return fmt.Errorf("node didn't respond: %v", err) 146 } 147 fmt.Printf("node responded to ping (RTT %v).\n", time.Since(start)) 148 return nil 149 } 150 151 func discv4RequestRecord(ctx *cli.Context) error { 152 n := getNodeArg(ctx) 153 disc := startV4(ctx) 154 defer disc.Close() 155 156 respN, err := disc.RequestENR(n) 157 if err != nil { 158 return fmt.Errorf("can't retrieve record: %v", err) 159 } 160 fmt.Println(respN.String()) 161 return nil 162 } 163 164 func discv4Resolve(ctx *cli.Context) error { 165 n := getNodeArg(ctx) 166 disc := startV4(ctx) 167 defer disc.Close() 168 169 fmt.Println(disc.Resolve(n).String()) 170 return nil 171 } 172 173 func discv4ResolveJSON(ctx *cli.Context) error { 174 if ctx.NArg() < 1 { 175 return fmt.Errorf("need nodes file as argument") 176 } 177 nodesFile := ctx.Args().Get(0) 178 inputSet := make(nodeSet) 179 if common.FileExist(nodesFile) { 180 inputSet = loadNodesJSON(nodesFile) 181 } 182 183 // Add extra nodes from command line arguments. 184 var nodeargs []*enode.Node 185 for i := 1; i < ctx.NArg(); i++ { 186 n, err := parseNode(ctx.Args().Get(i)) 187 if err != nil { 188 exit(err) 189 } 190 nodeargs = append(nodeargs, n) 191 } 192 193 // Run the crawler. 194 disc := startV4(ctx) 195 defer disc.Close() 196 c := newCrawler(inputSet, disc, enode.IterNodes(nodeargs)) 197 c.revalidateInterval = 0 198 output := c.run(0) 199 writeNodesJSON(nodesFile, output) 200 return nil 201 } 202 203 func discv4Crawl(ctx *cli.Context) error { 204 if ctx.NArg() < 1 { 205 return fmt.Errorf("need nodes file as argument") 206 } 207 nodesFile := ctx.Args().First() 208 var inputSet nodeSet 209 if common.FileExist(nodesFile) { 210 inputSet = loadNodesJSON(nodesFile) 211 } 212 213 disc := startV4(ctx) 214 defer disc.Close() 215 c := newCrawler(inputSet, disc, disc.RandomNodes()) 216 c.revalidateInterval = 10 * time.Minute 217 output := c.run(ctx.Duration(crawlTimeoutFlag.Name)) 218 writeNodesJSON(nodesFile, output) 219 return nil 220 } 221 222 // discv4Test runs the protocol test suite. 223 func discv4Test(ctx *cli.Context) error { 224 // Configure test package globals. 225 if !ctx.IsSet(remoteEnodeFlag.Name) { 226 return fmt.Errorf("Missing -%v", remoteEnodeFlag.Name) 227 } 228 v4test.Remote = ctx.String(remoteEnodeFlag.Name) 229 v4test.Listen1 = ctx.String(testListen1Flag.Name) 230 v4test.Listen2 = ctx.String(testListen2Flag.Name) 231 return runTests(ctx, v4test.AllTests) 232 } 233 234 // startV4 starts an ephemeral discovery V4 node. 235 func startV4(ctx *cli.Context) *discover.UDPv4 { 236 ln, config := makeDiscoveryConfig(ctx) 237 socket := listen(ctx, ln) 238 disc, err := discover.ListenV4(socket, ln, config) 239 if err != nil { 240 exit(err) 241 } 242 return disc 243 } 244 245 func makeDiscoveryConfig(ctx *cli.Context) (*enode.LocalNode, discover.Config) { 246 var cfg discover.Config 247 248 if ctx.IsSet(nodekeyFlag.Name) { 249 key, err := crypto.HexToECDSA(ctx.String(nodekeyFlag.Name)) 250 if err != nil { 251 exit(fmt.Errorf("-%s: %v", nodekeyFlag.Name, err)) 252 } 253 cfg.PrivateKey = key 254 } else { 255 cfg.PrivateKey, _ = crypto.GenerateKey() 256 } 257 258 if commandHasFlag(ctx, bootnodesFlag) { 259 bn, err := parseBootnodes(ctx) 260 if err != nil { 261 exit(err) 262 } 263 cfg.Bootnodes = bn 264 } 265 266 dbpath := ctx.String(nodedbFlag.Name) 267 db, err := enode.OpenDB(dbpath) 268 if err != nil { 269 exit(err) 270 } 271 ln := enode.NewLocalNode(db, cfg.PrivateKey) 272 return ln, cfg 273 } 274 275 func parseExtAddr(spec string) (ip net.IP, port int, ok bool) { 276 ip = net.ParseIP(spec) 277 if ip != nil { 278 return ip, 0, true 279 } 280 host, portstr, err := net.SplitHostPort(spec) 281 if err != nil { 282 return nil, 0, false 283 } 284 ip = net.ParseIP(host) 285 if ip == nil { 286 return nil, 0, false 287 } 288 port, err = strconv.Atoi(portstr) 289 if err != nil { 290 return nil, 0, false 291 } 292 return ip, port, true 293 } 294 295 func listen(ctx *cli.Context, ln *enode.LocalNode) *net.UDPConn { 296 addr := ctx.String(listenAddrFlag.Name) 297 if addr == "" { 298 addr = "0.0.0.0:0" 299 } 300 socket, err := net.ListenPacket("udp4", addr) 301 if err != nil { 302 exit(err) 303 } 304 305 // Configure UDP endpoint in ENR from listener address. 306 usocket := socket.(*net.UDPConn) 307 uaddr := socket.LocalAddr().(*net.UDPAddr) 308 if uaddr.IP.IsUnspecified() { 309 ln.SetFallbackIP(net.IP{127, 0, 0, 1}) 310 } else { 311 ln.SetFallbackIP(uaddr.IP) 312 } 313 ln.SetFallbackUDP(uaddr.Port) 314 315 // If an ENR endpoint is set explicitly on the command-line, override 316 // the information from the listening address. Note this is careful not 317 // to set the UDP port if the external address doesn't have it. 318 extAddr := ctx.String(extAddrFlag.Name) 319 if extAddr != "" { 320 ip, port, ok := parseExtAddr(extAddr) 321 if !ok { 322 exit(fmt.Errorf("-%s: invalid external address %q", extAddrFlag.Name, extAddr)) 323 } 324 ln.SetStaticIP(ip) 325 if port != 0 { 326 ln.SetFallbackUDP(port) 327 } 328 } 329 330 return usocket 331 } 332 333 func parseBootnodes(ctx *cli.Context) ([]*enode.Node, error) { 334 s := params.RinkebyBootnodes 335 if ctx.IsSet(bootnodesFlag.Name) { 336 input := ctx.String(bootnodesFlag.Name) 337 if input == "" { 338 return nil, nil 339 } 340 s = strings.Split(input, ",") 341 } 342 nodes := make([]*enode.Node, len(s)) 343 var err error 344 for i, record := range s { 345 nodes[i], err = parseNode(record) 346 if err != nil { 347 return nil, fmt.Errorf("invalid bootstrap node: %v", err) 348 } 349 } 350 return nodes, nil 351 }