2020-06-02 15:21:50 +00:00
|
|
|
import
|
2020-07-12 15:25:18 +00:00
|
|
|
std/[options, strutils],
|
|
|
|
chronos, chronicles, chronicles/topics_registry, confutils, metrics,
|
|
|
|
stew/byteutils, confutils/std/net,
|
2020-10-19 19:04:53 +00:00
|
|
|
eth/keys, eth/net/nat, enr, node, protocol
|
2020-06-02 15:21:50 +00:00
|
|
|
|
|
|
|
type
|
|
|
|
DiscoveryCmd* = enum
|
|
|
|
noCommand
|
|
|
|
ping
|
|
|
|
findnode
|
2020-11-13 11:45:39 +00:00
|
|
|
talkreq
|
2020-06-02 15:21:50 +00:00
|
|
|
|
|
|
|
DiscoveryConf* = object
|
|
|
|
logLevel* {.
|
|
|
|
defaultValue: LogLevel.DEBUG
|
2021-01-26 13:11:22 +00:00
|
|
|
desc: "Sets the log level"
|
2020-06-02 15:21:50 +00:00
|
|
|
name: "log-level" .}: LogLevel
|
|
|
|
|
|
|
|
udpPort* {.
|
|
|
|
defaultValue: 9009
|
2021-01-26 13:11:22 +00:00
|
|
|
desc: "UDP listening port"
|
2020-06-02 15:21:50 +00:00
|
|
|
name: "udp-port" .}: uint16
|
|
|
|
|
2021-01-07 09:15:48 +00:00
|
|
|
listenAddress* {.
|
|
|
|
defaultValue: defaultListenAddress(config)
|
|
|
|
desc: "Listening address for the Discovery v5 traffic"
|
|
|
|
name: "listen-address" }: ValidIpAddress
|
|
|
|
|
2020-06-02 15:21:50 +00:00
|
|
|
bootnodes* {.
|
2021-01-26 13:11:22 +00:00
|
|
|
desc: "ENR URI of node to bootstrap discovery with. Argument may be repeated"
|
2020-06-02 15:21:50 +00:00
|
|
|
name: "bootnode" .}: seq[enr.Record]
|
|
|
|
|
|
|
|
nat* {.
|
|
|
|
desc: "Specify method to use for determining public address. " &
|
2021-01-26 13:11:22 +00:00
|
|
|
"Must be one of: any, none, upnp, pmp, extip:<IP>"
|
2020-06-02 15:21:50 +00:00
|
|
|
defaultValue: "any" .}: string
|
|
|
|
|
2021-01-26 13:11:22 +00:00
|
|
|
enrAutoUpdate* {.
|
|
|
|
defaultValue: false
|
|
|
|
desc: "Discovery can automatically update its ENR with the IP address " &
|
|
|
|
"and UDP port as seen by other nodes it communicates with. " &
|
|
|
|
"This option allows to enable/disable this functionality"
|
|
|
|
name: "enr-auto-update" .}: bool
|
|
|
|
|
2020-06-30 11:35:15 +00:00
|
|
|
nodeKey* {.
|
2021-01-26 13:11:22 +00:00
|
|
|
desc: "P2P node private key as hex",
|
2020-07-12 15:25:18 +00:00
|
|
|
defaultValue: PrivateKey.random(keys.newRng()[])
|
2020-06-30 11:35:15 +00:00
|
|
|
name: "nodekey" .}: PrivateKey
|
|
|
|
|
|
|
|
metricsEnabled* {.
|
|
|
|
defaultValue: false
|
2021-01-26 13:11:22 +00:00
|
|
|
desc: "Enable the metrics server"
|
2020-06-30 11:35:15 +00:00
|
|
|
name: "metrics" .}: bool
|
|
|
|
|
|
|
|
metricsAddress* {.
|
2021-01-07 09:15:48 +00:00
|
|
|
defaultValue: defaultAdminListenAddress(config)
|
2021-01-26 13:11:22 +00:00
|
|
|
desc: "Listening address of the metrics server"
|
2020-06-30 11:35:15 +00:00
|
|
|
name: "metrics-address" .}: ValidIpAddress
|
|
|
|
|
|
|
|
metricsPort* {.
|
|
|
|
defaultValue: 8008
|
2021-01-26 13:11:22 +00:00
|
|
|
desc: "Listening HTTP port of the metrics server"
|
2020-06-30 11:35:15 +00:00
|
|
|
name: "metrics-port" .}: Port
|
|
|
|
|
2020-06-02 15:21:50 +00:00
|
|
|
case cmd* {.
|
|
|
|
command
|
|
|
|
defaultValue: noCommand }: DiscoveryCmd
|
|
|
|
of noCommand:
|
|
|
|
discard
|
|
|
|
of ping:
|
|
|
|
pingTarget* {.
|
2020-06-03 12:17:50 +00:00
|
|
|
argument
|
2020-06-02 15:21:50 +00:00
|
|
|
desc: "ENR URI of the node to a send ping message"
|
|
|
|
name: "node" .}: Node
|
|
|
|
of findnode:
|
|
|
|
distance* {.
|
|
|
|
defaultValue: 255
|
|
|
|
desc: "Distance parameter for the findNode message"
|
|
|
|
name: "distance" .}: uint32
|
2020-06-03 12:17:50 +00:00
|
|
|
# TODO: Order here matters as else the help message does not show all the
|
|
|
|
# information, see: https://github.com/status-im/nim-confutils/issues/15
|
|
|
|
findNodeTarget* {.
|
|
|
|
argument
|
|
|
|
desc: "ENR URI of the node to send a findNode message"
|
|
|
|
name: "node" .}: Node
|
2020-11-13 11:45:39 +00:00
|
|
|
of talkreq:
|
|
|
|
talkreqTarget* {.
|
|
|
|
argument
|
|
|
|
desc: "ENR URI of the node to send a talkreq message"
|
|
|
|
name: "node" .}: Node
|
2020-06-02 15:21:50 +00:00
|
|
|
|
2021-01-07 09:15:48 +00:00
|
|
|
func defaultListenAddress*(conf: DiscoveryConf): ValidIpAddress =
|
|
|
|
(static ValidIpAddress.init("0.0.0.0"))
|
|
|
|
|
|
|
|
func defaultAdminListenAddress*(conf: DiscoveryConf): ValidIpAddress =
|
|
|
|
(static ValidIpAddress.init("127.0.0.1"))
|
|
|
|
|
2020-06-02 15:21:50 +00:00
|
|
|
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 @[]
|
|
|
|
|
2020-06-30 11:35:15 +00:00
|
|
|
proc parseCmdArg*(T: type PrivateKey, p: TaintedString): T =
|
|
|
|
try:
|
|
|
|
result = PrivateKey.fromHex(string(p)).tryGet()
|
|
|
|
except CatchableError as e:
|
|
|
|
raise newException(ConfigurationError, "Invalid private key")
|
|
|
|
|
|
|
|
proc completeCmdArg*(T: type PrivateKey, val: TaintedString): seq[string] =
|
|
|
|
return @[]
|
|
|
|
|
2020-06-11 19:24:52 +00:00
|
|
|
proc setupNat(conf: DiscoveryConf): tuple[ip: Option[ValidIpAddress],
|
2020-06-02 15:21:50 +00:00
|
|
|
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
|
2020-06-11 19:24:52 +00:00
|
|
|
result.ip = some(ValidIpAddress.init(conf.nat[6..^1]))
|
2020-06-02 15:21:50 +00:00
|
|
|
nat = NatNone
|
|
|
|
else:
|
|
|
|
error "not a valid NAT mechanism, nor a valid IP address", value = conf.nat
|
|
|
|
quit(QuitFailure)
|
|
|
|
|
|
|
|
if nat != NatNone:
|
2020-06-11 19:24:52 +00:00
|
|
|
let extIp = getExternalIP(nat)
|
|
|
|
if extIP.isSome:
|
|
|
|
result.ip = some(ValidIpAddress.init extIp.get)
|
2020-06-02 15:21:50 +00:00
|
|
|
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)
|
2020-09-10 12:49:48 +00:00
|
|
|
d = newProtocol(config.nodeKey, ip, tcpPort, udpPort,
|
2021-01-26 13:11:22 +00:00
|
|
|
bootstrapRecords = config.bootnodes, bindIp = config.listenAddress,
|
|
|
|
enrAutoUpdate = config.enrAutoUpdate)
|
2020-06-02 15:21:50 +00:00
|
|
|
|
|
|
|
d.open()
|
|
|
|
|
2020-06-30 11:35:15 +00:00
|
|
|
when defined(insecure):
|
|
|
|
if config.metricsEnabled:
|
|
|
|
let
|
|
|
|
address = config.metricsAddress
|
|
|
|
port = config.metricsPort
|
|
|
|
info "Starting metrics HTTP server", address, port
|
|
|
|
metrics.startHttpServer($address, port)
|
|
|
|
|
2020-06-02 15:21:50 +00:00
|
|
|
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:
|
2020-11-13 11:33:07 +00:00
|
|
|
let nodes = waitFor d.findNode(config.findNodeTarget, @[config.distance])
|
2020-06-02 15:21:50 +00:00
|
|
|
if nodes.isOk():
|
|
|
|
echo "Received valid records:"
|
|
|
|
for node in nodes[]:
|
2020-10-23 14:41:44 +00:00
|
|
|
echo $node.record & " - " & shortLog(node)
|
2020-06-02 15:21:50 +00:00
|
|
|
else:
|
|
|
|
echo "No Nodes message returned"
|
2020-11-13 11:45:39 +00:00
|
|
|
of talkreq:
|
|
|
|
let talkresp = waitFor d.talkreq(config.talkreqTarget, @[], @[])
|
|
|
|
if talkresp.isOk():
|
|
|
|
echo talkresp[]
|
|
|
|
else:
|
|
|
|
echo "No Talk Response message returned"
|
2020-06-02 15:21:50 +00:00
|
|
|
of noCommand:
|
|
|
|
d.start()
|
|
|
|
runForever()
|
|
|
|
|
|
|
|
when isMainModule:
|
|
|
|
let config = DiscoveryConf.load()
|
|
|
|
|
|
|
|
if config.logLevel != LogLevel.NONE:
|
|
|
|
setLogLevel(config.logLevel)
|
|
|
|
|
|
|
|
run(config)
|