discv4cmd.go 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257
  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. package main
  17. import (
  18. "fmt"
  19. "net"
  20. "strings"
  21. "time"
  22. "github.com/ethereum/go-ethereum/common"
  23. "github.com/ethereum/go-ethereum/crypto"
  24. "github.com/ethereum/go-ethereum/p2p/discover"
  25. "github.com/ethereum/go-ethereum/p2p/enode"
  26. "github.com/ethereum/go-ethereum/params"
  27. "gopkg.in/urfave/cli.v1"
  28. )
  29. var (
  30. discv4Command = cli.Command{
  31. Name: "discv4",
  32. Usage: "Node Discovery v4 tools",
  33. Subcommands: []cli.Command{
  34. discv4PingCommand,
  35. discv4RequestRecordCommand,
  36. discv4ResolveCommand,
  37. discv4ResolveJSONCommand,
  38. discv4CrawlCommand,
  39. },
  40. }
  41. discv4PingCommand = cli.Command{
  42. Name: "ping",
  43. Usage: "Sends ping to a node",
  44. Action: discv4Ping,
  45. ArgsUsage: "<node>",
  46. }
  47. discv4RequestRecordCommand = cli.Command{
  48. Name: "requestenr",
  49. Usage: "Requests a node record using EIP-868 enrRequest",
  50. Action: discv4RequestRecord,
  51. ArgsUsage: "<node>",
  52. }
  53. discv4ResolveCommand = cli.Command{
  54. Name: "resolve",
  55. Usage: "Finds a node in the DHT",
  56. Action: discv4Resolve,
  57. ArgsUsage: "<node>",
  58. Flags: []cli.Flag{bootnodesFlag},
  59. }
  60. discv4ResolveJSONCommand = cli.Command{
  61. Name: "resolve-json",
  62. Usage: "Re-resolves nodes in a nodes.json file",
  63. Action: discv4ResolveJSON,
  64. Flags: []cli.Flag{bootnodesFlag},
  65. ArgsUsage: "<nodes.json file>",
  66. }
  67. discv4CrawlCommand = cli.Command{
  68. Name: "crawl",
  69. Usage: "Updates a nodes.json file with random nodes found in the DHT",
  70. Action: discv4Crawl,
  71. Flags: []cli.Flag{bootnodesFlag, crawlTimeoutFlag},
  72. }
  73. )
  74. var (
  75. bootnodesFlag = cli.StringFlag{
  76. Name: "bootnodes",
  77. Usage: "Comma separated nodes used for bootstrapping",
  78. }
  79. nodekeyFlag = cli.StringFlag{
  80. Name: "nodekey",
  81. Usage: "Hex-encoded node key",
  82. }
  83. nodedbFlag = cli.StringFlag{
  84. Name: "nodedb",
  85. Usage: "Nodes database location",
  86. }
  87. listenAddrFlag = cli.StringFlag{
  88. Name: "addr",
  89. Usage: "Listening address",
  90. }
  91. crawlTimeoutFlag = cli.DurationFlag{
  92. Name: "timeout",
  93. Usage: "Time limit for the crawl.",
  94. Value: 30 * time.Minute,
  95. }
  96. )
  97. func discv4Ping(ctx *cli.Context) error {
  98. n := getNodeArg(ctx)
  99. disc := startV4(ctx)
  100. defer disc.Close()
  101. start := time.Now()
  102. if err := disc.Ping(n); err != nil {
  103. return fmt.Errorf("node didn't respond: %v", err)
  104. }
  105. fmt.Printf("node responded to ping (RTT %v).\n", time.Since(start))
  106. return nil
  107. }
  108. func discv4RequestRecord(ctx *cli.Context) error {
  109. n := getNodeArg(ctx)
  110. disc := startV4(ctx)
  111. defer disc.Close()
  112. respN, err := disc.RequestENR(n)
  113. if err != nil {
  114. return fmt.Errorf("can't retrieve record: %v", err)
  115. }
  116. fmt.Println(respN.String())
  117. return nil
  118. }
  119. func discv4Resolve(ctx *cli.Context) error {
  120. n := getNodeArg(ctx)
  121. disc := startV4(ctx)
  122. defer disc.Close()
  123. fmt.Println(disc.Resolve(n).String())
  124. return nil
  125. }
  126. func discv4ResolveJSON(ctx *cli.Context) error {
  127. if ctx.NArg() < 1 {
  128. return fmt.Errorf("need nodes file as argument")
  129. }
  130. nodesFile := ctx.Args().Get(0)
  131. inputSet := make(nodeSet)
  132. if common.FileExist(nodesFile) {
  133. inputSet = loadNodesJSON(nodesFile)
  134. }
  135. // Add extra nodes from command line arguments.
  136. var nodeargs []*enode.Node
  137. for i := 1; i < ctx.NArg(); i++ {
  138. n, err := parseNode(ctx.Args().Get(i))
  139. if err != nil {
  140. exit(err)
  141. }
  142. nodeargs = append(nodeargs, n)
  143. }
  144. // Run the crawler.
  145. disc := startV4(ctx)
  146. defer disc.Close()
  147. c := newCrawler(inputSet, disc, enode.IterNodes(nodeargs))
  148. c.revalidateInterval = 0
  149. output := c.run(0)
  150. writeNodesJSON(nodesFile, output)
  151. return nil
  152. }
  153. func discv4Crawl(ctx *cli.Context) error {
  154. if ctx.NArg() < 1 {
  155. return fmt.Errorf("need nodes file as argument")
  156. }
  157. nodesFile := ctx.Args().First()
  158. var inputSet nodeSet
  159. if common.FileExist(nodesFile) {
  160. inputSet = loadNodesJSON(nodesFile)
  161. }
  162. disc := startV4(ctx)
  163. defer disc.Close()
  164. c := newCrawler(inputSet, disc, disc.RandomNodes())
  165. c.revalidateInterval = 10 * time.Minute
  166. output := c.run(ctx.Duration(crawlTimeoutFlag.Name))
  167. writeNodesJSON(nodesFile, output)
  168. return nil
  169. }
  170. // startV4 starts an ephemeral discovery V4 node.
  171. func startV4(ctx *cli.Context) *discover.UDPv4 {
  172. ln, config := makeDiscoveryConfig(ctx)
  173. socket := listen(ln, ctx.String(listenAddrFlag.Name))
  174. disc, err := discover.ListenV4(socket, ln, config)
  175. if err != nil {
  176. exit(err)
  177. }
  178. return disc
  179. }
  180. func makeDiscoveryConfig(ctx *cli.Context) (*enode.LocalNode, discover.Config) {
  181. var cfg discover.Config
  182. if ctx.IsSet(nodekeyFlag.Name) {
  183. key, err := crypto.HexToECDSA(ctx.String(nodekeyFlag.Name))
  184. if err != nil {
  185. exit(fmt.Errorf("-%s: %v", nodekeyFlag.Name, err))
  186. }
  187. cfg.PrivateKey = key
  188. } else {
  189. cfg.PrivateKey, _ = crypto.GenerateKey()
  190. }
  191. if commandHasFlag(ctx, bootnodesFlag) {
  192. bn, err := parseBootnodes(ctx)
  193. if err != nil {
  194. exit(err)
  195. }
  196. cfg.Bootnodes = bn
  197. }
  198. dbpath := ctx.String(nodedbFlag.Name)
  199. db, err := enode.OpenDB(dbpath)
  200. if err != nil {
  201. exit(err)
  202. }
  203. ln := enode.NewLocalNode(db, cfg.PrivateKey)
  204. return ln, cfg
  205. }
  206. func listen(ln *enode.LocalNode, addr string) *net.UDPConn {
  207. if addr == "" {
  208. addr = "0.0.0.0:0"
  209. }
  210. socket, err := net.ListenPacket("udp4", addr)
  211. if err != nil {
  212. exit(err)
  213. }
  214. usocket := socket.(*net.UDPConn)
  215. uaddr := socket.LocalAddr().(*net.UDPAddr)
  216. ln.SetFallbackIP(net.IP{127, 0, 0, 1})
  217. ln.SetFallbackUDP(uaddr.Port)
  218. return usocket
  219. }
  220. func parseBootnodes(ctx *cli.Context) ([]*enode.Node, error) {
  221. s := params.RinkebyBootnodes
  222. if ctx.IsSet(bootnodesFlag.Name) {
  223. s = strings.Split(ctx.String(bootnodesFlag.Name), ",")
  224. }
  225. nodes := make([]*enode.Node, len(s))
  226. var err error
  227. for i, record := range s {
  228. nodes[i], err = parseNode(record)
  229. if err != nil {
  230. return nil, fmt.Errorf("invalid bootstrap node: %v", err)
  231. }
  232. }
  233. return nodes, nil
  234. }