chore(networkmonitor): Add DNS discovery (#1446)

* chore(networkmonitor): add dns disc to networkmonitor tool

* chore(networkmonitor): decouple ip api calls from main loop

* chore(networkmonitor): add timeout flag
This commit is contained in:
Alvaro Revuelta 2022-12-05 20:02:21 +01:00 committed by GitHub
parent 2e0db18cde
commit a7a87f1f10
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 90 additions and 32 deletions

View File

@ -182,7 +182,7 @@ proc setupWakuArchiveDriver(dbUrl: string, vacuum: bool, migrate: bool): SetupRe
ok(driver)
proc retrieveDynamicBootstrapNodes(dnsDiscovery: bool, dnsDiscoveryUrl: string, dnsDiscoveryNameServers: seq[ValidIpAddress]): SetupResult[seq[RemotePeerInfo]] =
proc retrieveDynamicBootstrapNodes*(dnsDiscovery: bool, dnsDiscoveryUrl: string, dnsDiscoveryNameServers: seq[ValidIpAddress]): SetupResult[seq[RemotePeerInfo]] =
if dnsDiscovery and dnsDiscoveryUrl != "":
# DNS discovery

View File

@ -21,7 +21,8 @@ The following options are available:
-l, --log-level Sets the log level [=LogLevel.DEBUG].
-t, --timeout Timeout to consider that the connection failed [=chronos.seconds(10)].
-b, --bootstrap-node Bootstrap ENR node. Argument may be repeated. [=@[""]].
-r, --refresh-interval How often new peers are discovered and connected to (in minutes) [=10].
--dns-discovery-url URL for DNS node list in format 'enrtree://<key>@<fqdn>'.
-r, --refresh-interval How often new peers are discovered and connected to (in seconds) [=5].
--metrics-server Enable the metrics server: true|false [=true].
--metrics-server-address Listening address of the metrics server. [=ValidIpAddress.init("127.0.0.1")].
--metrics-server-port Listening HTTP port of the metrics server. [=8008].
@ -37,6 +38,10 @@ Connect to the network through a given bootstrap node, with default parameters.
./build/networkmonitor --log-level=INFO --b="enr:-Nm4QOdTOKZJKTUUZ4O_W932CXIET-M9NamewDnL78P5u9DOGnZlK0JFZ4k0inkfe6iY-0JAaJVovZXc575VV3njeiABgmlkgnY0gmlwhAjS3ueKbXVsdGlhZGRyc7g6ADg2MW5vZGUtMDEuYWMtY24taG9uZ2tvbmctYy53YWt1djIucHJvZC5zdGF0dXNpbS5uZXQGH0DeA4lzZWNwMjU2azGhAo0C-VvfgHiXrxZi3umDiooXMGY9FvYj5_d1Q4EeS7eyg3RjcIJ2X4N1ZHCCIyiFd2FrdTIP"
```
```console
./build/networkmonitor --log-level=INFO --dns-discovery-url=enrtree://AOGECG2SPND25EEFMAJ5WF3KSGJNSGV356DSTL2YVLLZWIV6SAYBM@prod.nodes.status.im
```
## Metrics
Metrics are divided into two categories:

View File

@ -13,6 +13,8 @@ import
eth/keys,
eth/p2p/discoveryv5/enr,
libp2p/crypto/crypto,
libp2p/nameresolving/nameresolver,
libp2p/nameresolving/dnsresolver,
metrics,
metrics/chronos_httpserver,
presto/[route, server, client],
@ -20,6 +22,8 @@ import
import
../../waku/v2/node/discv5/waku_discv5,
../../apps/wakunode2/wakunode2,
../../waku/v2/node/dnsdisc/waku_dnsdisc,
../../waku/v2/node/peer_manager/peer_manager,
../../waku/v2/node/waku_node,
../../waku/v2/utils/wakuenr,
@ -39,7 +43,7 @@ proc setDiscoveredPeersCapabilities(
info "capabilities as per ENR waku flag", capability=capability, amount=nOfNodesWithCapability
peer_type_as_per_enr.set(int64(nOfNodesWithCapability), labelValues = [$capability])
# TODO: Split in discover, connect, populate ips
# TODO: Split in discover, connect
proc setConnectedPeersMetrics(discoveredNodes: seq[Node],
node: WakuNode,
timeout: chronos.Duration,
@ -81,21 +85,6 @@ proc setConnectedPeersMetrics(discoveredNodes: seq[Node],
let ip = $typedRecord.get().ip.get().join(".")
allPeers[peerId].ip = ip
# get more info the peers from its ip address
var location: NodeLocation
try:
# IP-API endpoints are now limited to 45 HTTP requests per minute
# TODO: As network grows, find a better way to now block the whole app
await sleepAsync(1400)
let response = await restClient.ipToLocation(ip)
location = response.data
except:
warn "could not get location", ip=ip
continue
allPeers[peerId].country = location.country
allPeers[peerId].city = location.city
let peer = toRemotePeerInfo(discNode.record)
if not peer.isOk():
warn "error converting record to remote peer info", record=discNode.record
@ -106,6 +95,8 @@ proc setConnectedPeersMetrics(discoveredNodes: seq[Node],
let timedOut = not await node.connectToNodes(@[peer.get()]).withTimeout(timeout)
if timedOut:
warn "could not connect to peer, timedout", timeout=timeout, peer=peer.get()
# TODO: Add other staates
allPeers[peerId].connError = "timedout"
continue
# after connection, get supported protocols
@ -130,7 +121,7 @@ proc setConnectedPeersMetrics(discoveredNodes: seq[Node],
allAgentStrings[nodeUserAgent] += 1
debug "connected to peer", peer=allPeers[customPeerInfo.peerId]
# inform the total connections that we did in this round
let nOfOkConnections = allProtocols.len()
info "number of successful connections", amount=nOfOkConnections
@ -147,6 +138,26 @@ proc setConnectedPeersMetrics(discoveredNodes: seq[Node],
peer_user_agents.set(int64(countOfUserAgent), labelValues = [userAgent])
info "user agents participating in the network", userAgent=userAgent, count=countOfUserAgent
proc populateInfoFromIp(allPeersRef: CustomPeersTableRef,
restClient: RestClientRef) {.async.} =
for peer in allPeersRef.keys():
if allPeersRef[peer].country != "" and allPeersRef[peer].city != "":
continue
# TODO: Update also if last update > x
if allPeersRef[peer].ip == "":
continue
# get more info the peers from its ip address
var location: NodeLocation
try:
# IP-API endpoints are now limited to 45 HTTP requests per minute
await sleepAsync(1400)
let response = await restClient.ipToLocation(allPeersRef[peer].ip)
location = response.data
except:
warn "could not get location", ip=allPeersRef[peer].ip
continue
allPeersRef[peer].country = location.country
allPeersRef[peer].city = location.city
# TODO: Split in discovery, connections, and ip2location
# crawls the network discovering peers and trying to connect to them
@ -154,7 +165,7 @@ proc setConnectedPeersMetrics(discoveredNodes: seq[Node],
proc crawlNetwork(node: WakuNode,
restClient: RestClientRef,
conf: NetworkMonitorConf,
allPeersRef: CustomPeersTableRef) {.async.} =
allPeersRef: CustomPeersTableRef) {.async.} =
let crawlInterval = conf.refreshInterval * 1000
while true:
@ -172,6 +183,9 @@ proc crawlNetwork(node: WakuNode,
# note random discovered nodes can be already known
await setConnectedPeersMetrics(discoveredNodes, node, conf.timeout, restClient, allPeersRef)
# populate info from ip addresses
await populateInfoFromIp(allPeersRef, restClient)
let totalNodes = flatNodes.len
let seenNodes = flatNodes.countIt(it.seen)
@ -183,7 +197,29 @@ proc crawlNetwork(node: WakuNode,
await sleepAsync(crawlInterval)
proc initAndStartNode(conf: NetworkMonitorConf): Result[WakuNode, string] =
proc getBootstrapFromDiscDns(conf: NetworkMonitorConf): Result[seq[enr.Record], string] =
try:
let dnsNameServers = @[ValidIpAddress.init("1.1.1.1"), ValidIpAddress.init("1.0.0.1")]
let dynamicBootstrapNodesRes = retrieveDynamicBootstrapNodes(true, conf.dnsDiscoveryUrl, dnsNameServers)
if not dynamicBootstrapNodesRes.isOk():
error("failed discovering peers from DNS")
let dynamicBootstrapNodes = dynamicBootstrapNodesRes.get()
# select dynamic bootstrap nodes that have an ENR containing a udp port.
# Discv5 only supports UDP https://github.com/ethereum/devp2p/blob/master/discv5/discv5-theory.md)
var discv5BootstrapEnrs: seq[enr.Record]
for n in dynamicBootstrapNodes:
if n.enr.isSome():
let
enr = n.enr.get()
tenrRes = enr.toTypedRecord()
if tenrRes.isOk() and (tenrRes.get().udp.isSome() or tenrRes.get().udp6.isSome()):
discv5BootstrapEnrs.add(enr)
return ok(discv5BootstrapEnrs)
except:
error("failed discovering peers from DNS")
proc initAndStartNode(conf: NetworkMonitorConf): Result[WakuNode, string] =
let
# some hardcoded parameters
rng = keys.newRng()
@ -191,17 +227,26 @@ proc initAndStartNode(conf: NetworkMonitorConf): Result[WakuNode, string] =
nodeTcpPort = Port(60000)
nodeUdpPort = Port(9000)
flags = initWakuFlags(lightpush = false, filter = false, store = false, relay = true)
try:
let
bindIp = ValidIpAddress.init("0.0.0.0")
extIp = ValidIpAddress.init("127.0.0.1")
node = WakuNode.new(nodeKey, bindIp, nodeTcpPort)
var discv5BootstrapEnrsRes = getBootstrapFromDiscDns(conf)
if not discv5BootstrapEnrsRes.isOk():
error("failed discovering peers from DNS")
var discv5BootstrapEnrs = discv5BootstrapEnrsRes.get()
# parse enrURIs from the configuration and add the resulting ENRs to the discv5BootstrapEnrs seq
for enrUri in conf.bootstrapNodes:
addBootstrapNode(enrUri, discv5BootstrapEnrs)
# mount discv5
node.wakuDiscv5 = WakuDiscoveryV5.new(
some(extIp), some(nodeTcpPort), some(nodeUdpPort),
bindIp, nodeUdpPort, conf.bootstrapNodes, false,
bindIp, nodeUdpPort, discv5BootstrapEnrs, false,
keys.PrivateKey(nodeKey.skkey), flags, [], node.rng)
node.wakuDiscv5.protocol.open()
@ -232,7 +277,7 @@ proc startRestApiServer(conf: NetworkMonitorConf,
proc subscribeAndHandleMessages(node: WakuNode,
pubsubTopic: PubsubTopic,
msgPerContentTopic: ContentTopicMessageTableRef) =
# handle function
proc handler(pubsubTopic: PubsubTopic, data: seq[byte]) {.async, gcsafe.} =
let messageRes = WakuMessage.decode(data)
@ -303,7 +348,7 @@ when isMainModule:
if nodeRes.isErr():
error "could not start node"
quit 1
let node = nodeRes.get()
waitFor node.mountRelay()
@ -314,5 +359,5 @@ when isMainModule:
# spawn the routine that crawls the network
# TODO: split into 3 routines (discovery, connections, ip2location)
asyncSpawn crawlNetwork(node, restClient, conf, allPeersInfo)
runForever()
runForever()

View File

@ -27,6 +27,11 @@ type
name: "bootstrap-node",
abbr: "b" }: seq[string]
dnsDiscoveryUrl* {.
desc: "URL for DNS node list in format 'enrtree://<key>@<fqdn>'",
defaultValue: ""
name: "dns-discovery-url" }: string
refreshInterval* {.
desc: "How often new peers are discovered and connected to (in seconds)",
defaultValue: 5,
@ -58,7 +63,7 @@ type
desc: "Listening HTTP port of the metrics rest server.",
defaultValue: 8009,
name: "metrics-rest-port" }: uint16
proc parseCmdArg*(T: type ValidIpAddress, p: string): T =
try:
@ -83,4 +88,4 @@ proc loadConfig*(T: type NetworkMonitorConf): Result[T, string] =
let conf = NetworkMonitorConf.load()
ok(conf)
except CatchableError:
err(getCurrentExceptionMsg())
err(getCurrentExceptionMsg())

View File

@ -2,7 +2,7 @@ when (NimMajor, NimMinor) < (1, 4):
{.push raises: [Defect].}
else:
{.push raises: [].}
import
std/[json,tables,sequtils],
chronicles,
@ -53,6 +53,9 @@ type
supportedProtocols*: seq[string]
userAgent*: string
# only after a ok/nok connection
connError*: string
# Stores information about all discovered/connected peers
CustomPeersTableRef* = TableRef[string, CustomPeerInfo]
@ -71,11 +74,11 @@ proc installHandler*(router: var RestRouter,
proc startMetricsServer*(serverIp: ValidIpAddress, serverPort: Port): Result[void, string] =
info "Starting metrics HTTP server", serverIp, serverPort
try:
startMetricsHttpServer($serverIp, serverPort)
except Exception as e:
error("Failed to start metrics HTTP server", serverIp=serverIp, serverPort=serverPort, msg=e.msg)
info "Metrics HTTP server started", serverIp, serverPort
ok()
ok()