From ff7bed95558d67eb36766ee287ec85576872fc0d Mon Sep 17 00:00:00 2001 From: kdeme Date: Tue, 2 Jun 2020 17:21:50 +0200 Subject: [PATCH] Add discoveryv5 debugging tool: dcli --- eth.nimble | 3 +- eth/p2p/discoveryv5/dcli.nim | 147 +++++++++++++++++++++++++++++++++++ 2 files changed, 149 insertions(+), 1 deletion(-) create mode 100644 eth/p2p/discoveryv5/dcli.nim diff --git a/eth.nimble b/eth.nimble index 028437c..97c7e9c 100644 --- a/eth.nimble +++ b/eth.nimble @@ -14,7 +14,8 @@ requires "nim >= 1.2.0", "stew", "nat_traversal", "metrics", - "sqlite3_abi" + "sqlite3_abi", + "confutils" proc runTest(path: string) = echo "\nRunning: ", path diff --git a/eth/p2p/discoveryv5/dcli.nim b/eth/p2p/discoveryv5/dcli.nim new file mode 100644 index 0000000..7a02fc4 --- /dev/null +++ b/eth/p2p/discoveryv5/dcli.nim @@ -0,0 +1,147 @@ +import + sequtils, options, strutils, chronos, chronicles, chronicles/topics_registry, + stew/byteutils, confutils, + eth/keys, eth/trie/db, eth/net/nat, + eth/p2p/discoveryv5/[protocol, discovery_db, enr, node] + +type + DiscoveryCmd* = enum + noCommand + ping + findnode + + DiscoveryConf* = object + logLevel* {. + defaultValue: LogLevel.DEBUG + desc: "Sets the log level." + name: "log-level" .}: LogLevel + + udpPort* {. + defaultValue: 9009 + desc: "UDP listening port." + name: "udp-port" .}: uint16 + + bootnodes* {. + desc: "ENR URI of node to bootstrap discovery with. Argument may be repeated." + name: "bootnode" .}: seq[enr.Record] + + nat* {. + desc: "Specify method to use for determining public address. " & + "Must be one of: any, none, upnp, pmp, extip:." + defaultValue: "any" .}: string + + case cmd* {. + command + defaultValue: noCommand }: DiscoveryCmd + of noCommand: + discard + of ping: + pingTarget* {. + desc: "ENR URI of the node to a send ping message" + name: "node" .}: Node + of findnode: + findNodeTarget* {. + desc: "ENR URI of the node to send a findNode message" + name: "node" .}: Node + distance* {. + defaultValue: 255 + desc: "Distance parameter for the findNode message" + name: "distance" .}: uint32 + +proc parseCmdArg*(T: type enr.Record, p: TaintedString): T = + if not fromURI(result, p): + raise newException(ConfigurationError, "Invalid ENR") + +proc completeCmdArg*(T: type enr.Record, val: TaintedString): seq[string] = + return @[] + +proc parseCmdArg*(T: type Node, p: TaintedString): T = + var record: enr.Record + if not fromURI(record, p): + raise newException(ConfigurationError, "Invalid ENR") + + let n = newNode(record) + if n.isErr: + raise newException(ConfigurationError, $n.error) + + if n[].address.isNone(): + raise newException(ConfigurationError, "ENR without address") + + n[] + +proc completeCmdArg*(T: type Node, val: TaintedString): seq[string] = + return @[] + +proc setupNat(conf: DiscoveryConf): tuple[ip: Option[IpAddress], + tcpPort: Port, + udpPort: Port] {.gcsafe.} = + # defaults + result.tcpPort = Port(conf.udpPort) + result.udpPort = Port(conf.udpPort) + + var nat: NatStrategy + case conf.nat.toLowerAscii: + of "any": + nat = NatAny + of "none": + nat = NatNone + of "upnp": + nat = NatUpnp + of "pmp": + nat = NatPmp + else: + if conf.nat.startsWith("extip:") and isIpAddress(conf.nat[6..^1]): + # any required port redirection is assumed to be done by hand + result.ip = some(parseIpAddress(conf.nat[6..^1])) + nat = NatNone + else: + error "not a valid NAT mechanism, nor a valid IP address", value = conf.nat + quit(QuitFailure) + + if nat != NatNone: + result.ip = getExternalIP(nat) + if result.ip.isSome: + let extPorts = ({.gcsafe.}: + redirectPorts(tcpPort = result.tcpPort, + udpPort = result.udpPort, + description = "Discovery v5 ports")) + if extPorts.isSome: + (result.tcpPort, result.udpPort) = extPorts.get() + +proc run(config: DiscoveryConf) = + let + (ip, tcpPort, udpPort) = setupNat(config) + privKey = PrivateKey.random().expect("Properly intialized private key") + ddb = DiscoveryDB.init(newMemoryDB()) + # TODO: newProtocol should allow for no tcpPort + d = newProtocol(privKey, ddb, ip, tcpPort, udpPort, + bootstrapRecords = config.bootnodes) + + d.open() + + case config.cmd + of ping: + let pong = waitFor d.ping(config.pingTarget) + if pong.isOk(): + echo pong[] + else: + echo "No Pong message returned" + of findnode: + let nodes = waitFor d.findNode(config.findNodeTarget, config.distance) + if nodes.isOk(): + echo "Received valid records:" + for node in nodes[]: + echo $node.record & " - " & $node + else: + echo "No Nodes message returned" + of noCommand: + d.start() + runForever() + +when isMainModule: + let config = DiscoveryConf.load() + + if config.logLevel != LogLevel.NONE: + setLogLevel(config.logLevel) + + run(config)