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