github.com/calmw/ethereum@v0.1.1/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/calmw/ethereum/cmd/devp2p/internal/v4test" 27 "github.com/calmw/ethereum/common" 28 "github.com/calmw/ethereum/crypto" 29 "github.com/calmw/ethereum/internal/flags" 30 "github.com/calmw/ethereum/p2p/discover" 31 "github.com/calmw/ethereum/p2p/enode" 32 "github.com/calmw/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, crawlParallelismFlag}), 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 crawlParallelismFlag = &cli.IntFlag{ 124 Name: "parallel", 125 Usage: "How many parallel discoveries to attempt.", 126 Value: 16, 127 } 128 remoteEnodeFlag = &cli.StringFlag{ 129 Name: "remote", 130 Usage: "Enode of the remote node under test", 131 EnvVars: []string{"REMOTE_ENODE"}, 132 } 133 ) 134 135 var discoveryNodeFlags = []cli.Flag{ 136 bootnodesFlag, 137 nodekeyFlag, 138 nodedbFlag, 139 listenAddrFlag, 140 extAddrFlag, 141 } 142 143 func discv4Ping(ctx *cli.Context) error { 144 n := getNodeArg(ctx) 145 disc := startV4(ctx) 146 defer disc.Close() 147 148 start := time.Now() 149 if err := disc.Ping(n); err != nil { 150 return fmt.Errorf("node didn't respond: %v", err) 151 } 152 fmt.Printf("node responded to ping (RTT %v).\n", time.Since(start)) 153 return nil 154 } 155 156 func discv4RequestRecord(ctx *cli.Context) error { 157 n := getNodeArg(ctx) 158 disc := startV4(ctx) 159 defer disc.Close() 160 161 respN, err := disc.RequestENR(n) 162 if err != nil { 163 return fmt.Errorf("can't retrieve record: %v", err) 164 } 165 fmt.Println(respN.String()) 166 return nil 167 } 168 169 func discv4Resolve(ctx *cli.Context) error { 170 n := getNodeArg(ctx) 171 disc := startV4(ctx) 172 defer disc.Close() 173 174 fmt.Println(disc.Resolve(n).String()) 175 return nil 176 } 177 178 func discv4ResolveJSON(ctx *cli.Context) error { 179 if ctx.NArg() < 1 { 180 return fmt.Errorf("need nodes file as argument") 181 } 182 nodesFile := ctx.Args().Get(0) 183 inputSet := make(nodeSet) 184 if common.FileExist(nodesFile) { 185 inputSet = loadNodesJSON(nodesFile) 186 } 187 188 // Add extra nodes from command line arguments. 189 var nodeargs []*enode.Node 190 for i := 1; i < ctx.NArg(); i++ { 191 n, err := parseNode(ctx.Args().Get(i)) 192 if err != nil { 193 exit(err) 194 } 195 nodeargs = append(nodeargs, n) 196 } 197 198 // Run the crawler. 199 disc := startV4(ctx) 200 defer disc.Close() 201 c := newCrawler(inputSet, disc, enode.IterNodes(nodeargs)) 202 c.revalidateInterval = 0 203 output := c.run(0, 1) 204 writeNodesJSON(nodesFile, output) 205 return nil 206 } 207 208 func discv4Crawl(ctx *cli.Context) error { 209 if ctx.NArg() < 1 { 210 return fmt.Errorf("need nodes file as argument") 211 } 212 nodesFile := ctx.Args().First() 213 var inputSet nodeSet 214 if common.FileExist(nodesFile) { 215 inputSet = loadNodesJSON(nodesFile) 216 } 217 218 disc := startV4(ctx) 219 defer disc.Close() 220 c := newCrawler(inputSet, disc, disc.RandomNodes()) 221 c.revalidateInterval = 10 * time.Minute 222 output := c.run(ctx.Duration(crawlTimeoutFlag.Name), ctx.Int(crawlParallelismFlag.Name)) 223 writeNodesJSON(nodesFile, output) 224 return nil 225 } 226 227 // discv4Test runs the protocol test suite. 228 func discv4Test(ctx *cli.Context) error { 229 // Configure test package globals. 230 if !ctx.IsSet(remoteEnodeFlag.Name) { 231 return fmt.Errorf("Missing -%v", remoteEnodeFlag.Name) 232 } 233 v4test.Remote = ctx.String(remoteEnodeFlag.Name) 234 v4test.Listen1 = ctx.String(testListen1Flag.Name) 235 v4test.Listen2 = ctx.String(testListen2Flag.Name) 236 return runTests(ctx, v4test.AllTests) 237 } 238 239 // startV4 starts an ephemeral discovery V4 node. 240 func startV4(ctx *cli.Context) *discover.UDPv4 { 241 ln, config := makeDiscoveryConfig(ctx) 242 socket := listen(ctx, ln) 243 disc, err := discover.ListenV4(socket, ln, config) 244 if err != nil { 245 exit(err) 246 } 247 return disc 248 } 249 250 func makeDiscoveryConfig(ctx *cli.Context) (*enode.LocalNode, discover.Config) { 251 var cfg discover.Config 252 253 if ctx.IsSet(nodekeyFlag.Name) { 254 key, err := crypto.HexToECDSA(ctx.String(nodekeyFlag.Name)) 255 if err != nil { 256 exit(fmt.Errorf("-%s: %v", nodekeyFlag.Name, err)) 257 } 258 cfg.PrivateKey = key 259 } else { 260 cfg.PrivateKey, _ = crypto.GenerateKey() 261 } 262 263 if commandHasFlag(ctx, bootnodesFlag) { 264 bn, err := parseBootnodes(ctx) 265 if err != nil { 266 exit(err) 267 } 268 cfg.Bootnodes = bn 269 } 270 271 dbpath := ctx.String(nodedbFlag.Name) 272 db, err := enode.OpenDB(dbpath) 273 if err != nil { 274 exit(err) 275 } 276 ln := enode.NewLocalNode(db, cfg.PrivateKey) 277 return ln, cfg 278 } 279 280 func parseExtAddr(spec string) (ip net.IP, port int, ok bool) { 281 ip = net.ParseIP(spec) 282 if ip != nil { 283 return ip, 0, true 284 } 285 host, portstr, err := net.SplitHostPort(spec) 286 if err != nil { 287 return nil, 0, false 288 } 289 ip = net.ParseIP(host) 290 if ip == nil { 291 return nil, 0, false 292 } 293 port, err = strconv.Atoi(portstr) 294 if err != nil { 295 return nil, 0, false 296 } 297 return ip, port, true 298 } 299 300 func listen(ctx *cli.Context, ln *enode.LocalNode) *net.UDPConn { 301 addr := ctx.String(listenAddrFlag.Name) 302 if addr == "" { 303 addr = "0.0.0.0:0" 304 } 305 socket, err := net.ListenPacket("udp4", addr) 306 if err != nil { 307 exit(err) 308 } 309 310 // Configure UDP endpoint in ENR from listener address. 311 usocket := socket.(*net.UDPConn) 312 uaddr := socket.LocalAddr().(*net.UDPAddr) 313 if uaddr.IP.IsUnspecified() { 314 ln.SetFallbackIP(net.IP{127, 0, 0, 1}) 315 } else { 316 ln.SetFallbackIP(uaddr.IP) 317 } 318 ln.SetFallbackUDP(uaddr.Port) 319 320 // If an ENR endpoint is set explicitly on the command-line, override 321 // the information from the listening address. Note this is careful not 322 // to set the UDP port if the external address doesn't have it. 323 extAddr := ctx.String(extAddrFlag.Name) 324 if extAddr != "" { 325 ip, port, ok := parseExtAddr(extAddr) 326 if !ok { 327 exit(fmt.Errorf("-%s: invalid external address %q", extAddrFlag.Name, extAddr)) 328 } 329 ln.SetStaticIP(ip) 330 if port != 0 { 331 ln.SetFallbackUDP(port) 332 } 333 } 334 335 return usocket 336 } 337 338 func parseBootnodes(ctx *cli.Context) ([]*enode.Node, error) { 339 s := params.RinkebyBootnodes 340 if ctx.IsSet(bootnodesFlag.Name) { 341 input := ctx.String(bootnodesFlag.Name) 342 if input == "" { 343 return nil, nil 344 } 345 s = strings.Split(input, ",") 346 } 347 nodes := make([]*enode.Node, len(s)) 348 var err error 349 for i, record := range s { 350 nodes[i], err = parseNode(record) 351 if err != nil { 352 return nil, fmt.Errorf("invalid bootstrap node: %v", err) 353 } 354 } 355 return nodes, nil 356 }