mirror of https://github.com/waku-org/nwaku.git
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:
parent
2e0db18cde
commit
a7a87f1f10
|
@ -182,7 +182,7 @@ proc setupWakuArchiveDriver(dbUrl: string, vacuum: bool, migrate: bool): SetupRe
|
||||||
ok(driver)
|
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 != "":
|
if dnsDiscovery and dnsDiscoveryUrl != "":
|
||||||
# DNS discovery
|
# DNS discovery
|
||||||
|
|
|
@ -21,7 +21,8 @@ The following options are available:
|
||||||
-l, --log-level Sets the log level [=LogLevel.DEBUG].
|
-l, --log-level Sets the log level [=LogLevel.DEBUG].
|
||||||
-t, --timeout Timeout to consider that the connection failed [=chronos.seconds(10)].
|
-t, --timeout Timeout to consider that the connection failed [=chronos.seconds(10)].
|
||||||
-b, --bootstrap-node Bootstrap ENR node. Argument may be repeated. [=@[""]].
|
-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 Enable the metrics server: true|false [=true].
|
||||||
--metrics-server-address Listening address of the metrics server. [=ValidIpAddress.init("127.0.0.1")].
|
--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].
|
--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"
|
./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
|
||||||
|
|
||||||
Metrics are divided into two categories:
|
Metrics are divided into two categories:
|
||||||
|
|
|
@ -13,6 +13,8 @@ import
|
||||||
eth/keys,
|
eth/keys,
|
||||||
eth/p2p/discoveryv5/enr,
|
eth/p2p/discoveryv5/enr,
|
||||||
libp2p/crypto/crypto,
|
libp2p/crypto/crypto,
|
||||||
|
libp2p/nameresolving/nameresolver,
|
||||||
|
libp2p/nameresolving/dnsresolver,
|
||||||
metrics,
|
metrics,
|
||||||
metrics/chronos_httpserver,
|
metrics/chronos_httpserver,
|
||||||
presto/[route, server, client],
|
presto/[route, server, client],
|
||||||
|
@ -20,6 +22,8 @@ import
|
||||||
|
|
||||||
import
|
import
|
||||||
../../waku/v2/node/discv5/waku_discv5,
|
../../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/peer_manager/peer_manager,
|
||||||
../../waku/v2/node/waku_node,
|
../../waku/v2/node/waku_node,
|
||||||
../../waku/v2/utils/wakuenr,
|
../../waku/v2/utils/wakuenr,
|
||||||
|
@ -39,7 +43,7 @@ proc setDiscoveredPeersCapabilities(
|
||||||
info "capabilities as per ENR waku flag", capability=capability, amount=nOfNodesWithCapability
|
info "capabilities as per ENR waku flag", capability=capability, amount=nOfNodesWithCapability
|
||||||
peer_type_as_per_enr.set(int64(nOfNodesWithCapability), labelValues = [$capability])
|
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],
|
proc setConnectedPeersMetrics(discoveredNodes: seq[Node],
|
||||||
node: WakuNode,
|
node: WakuNode,
|
||||||
timeout: chronos.Duration,
|
timeout: chronos.Duration,
|
||||||
|
@ -81,21 +85,6 @@ proc setConnectedPeersMetrics(discoveredNodes: seq[Node],
|
||||||
let ip = $typedRecord.get().ip.get().join(".")
|
let ip = $typedRecord.get().ip.get().join(".")
|
||||||
allPeers[peerId].ip = ip
|
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)
|
let peer = toRemotePeerInfo(discNode.record)
|
||||||
if not peer.isOk():
|
if not peer.isOk():
|
||||||
warn "error converting record to remote peer info", record=discNode.record
|
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)
|
let timedOut = not await node.connectToNodes(@[peer.get()]).withTimeout(timeout)
|
||||||
if timedOut:
|
if timedOut:
|
||||||
warn "could not connect to peer, timedout", timeout=timeout, peer=peer.get()
|
warn "could not connect to peer, timedout", timeout=timeout, peer=peer.get()
|
||||||
|
# TODO: Add other staates
|
||||||
|
allPeers[peerId].connError = "timedout"
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# after connection, get supported protocols
|
# after connection, get supported protocols
|
||||||
|
@ -147,6 +138,26 @@ proc setConnectedPeersMetrics(discoveredNodes: seq[Node],
|
||||||
peer_user_agents.set(int64(countOfUserAgent), labelValues = [userAgent])
|
peer_user_agents.set(int64(countOfUserAgent), labelValues = [userAgent])
|
||||||
info "user agents participating in the network", userAgent=userAgent, count=countOfUserAgent
|
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
|
# TODO: Split in discovery, connections, and ip2location
|
||||||
# crawls the network discovering peers and trying to connect to them
|
# crawls the network discovering peers and trying to connect to them
|
||||||
|
@ -172,6 +183,9 @@ proc crawlNetwork(node: WakuNode,
|
||||||
# note random discovered nodes can be already known
|
# note random discovered nodes can be already known
|
||||||
await setConnectedPeersMetrics(discoveredNodes, node, conf.timeout, restClient, allPeersRef)
|
await setConnectedPeersMetrics(discoveredNodes, node, conf.timeout, restClient, allPeersRef)
|
||||||
|
|
||||||
|
# populate info from ip addresses
|
||||||
|
await populateInfoFromIp(allPeersRef, restClient)
|
||||||
|
|
||||||
let totalNodes = flatNodes.len
|
let totalNodes = flatNodes.len
|
||||||
let seenNodes = flatNodes.countIt(it.seen)
|
let seenNodes = flatNodes.countIt(it.seen)
|
||||||
|
|
||||||
|
@ -183,6 +197,28 @@ proc crawlNetwork(node: WakuNode,
|
||||||
|
|
||||||
await sleepAsync(crawlInterval)
|
await sleepAsync(crawlInterval)
|
||||||
|
|
||||||
|
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] =
|
proc initAndStartNode(conf: NetworkMonitorConf): Result[WakuNode, string] =
|
||||||
let
|
let
|
||||||
# some hardcoded parameters
|
# some hardcoded parameters
|
||||||
|
@ -198,10 +234,19 @@ proc initAndStartNode(conf: NetworkMonitorConf): Result[WakuNode, string] =
|
||||||
extIp = ValidIpAddress.init("127.0.0.1")
|
extIp = ValidIpAddress.init("127.0.0.1")
|
||||||
node = WakuNode.new(nodeKey, bindIp, nodeTcpPort)
|
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
|
# mount discv5
|
||||||
node.wakuDiscv5 = WakuDiscoveryV5.new(
|
node.wakuDiscv5 = WakuDiscoveryV5.new(
|
||||||
some(extIp), some(nodeTcpPort), some(nodeUdpPort),
|
some(extIp), some(nodeTcpPort), some(nodeUdpPort),
|
||||||
bindIp, nodeUdpPort, conf.bootstrapNodes, false,
|
bindIp, nodeUdpPort, discv5BootstrapEnrs, false,
|
||||||
keys.PrivateKey(nodeKey.skkey), flags, [], node.rng)
|
keys.PrivateKey(nodeKey.skkey), flags, [], node.rng)
|
||||||
|
|
||||||
node.wakuDiscv5.protocol.open()
|
node.wakuDiscv5.protocol.open()
|
||||||
|
|
|
@ -27,6 +27,11 @@ type
|
||||||
name: "bootstrap-node",
|
name: "bootstrap-node",
|
||||||
abbr: "b" }: seq[string]
|
abbr: "b" }: seq[string]
|
||||||
|
|
||||||
|
dnsDiscoveryUrl* {.
|
||||||
|
desc: "URL for DNS node list in format 'enrtree://<key>@<fqdn>'",
|
||||||
|
defaultValue: ""
|
||||||
|
name: "dns-discovery-url" }: string
|
||||||
|
|
||||||
refreshInterval* {.
|
refreshInterval* {.
|
||||||
desc: "How often new peers are discovered and connected to (in seconds)",
|
desc: "How often new peers are discovered and connected to (in seconds)",
|
||||||
defaultValue: 5,
|
defaultValue: 5,
|
||||||
|
|
|
@ -53,6 +53,9 @@ type
|
||||||
supportedProtocols*: seq[string]
|
supportedProtocols*: seq[string]
|
||||||
userAgent*: string
|
userAgent*: string
|
||||||
|
|
||||||
|
# only after a ok/nok connection
|
||||||
|
connError*: string
|
||||||
|
|
||||||
# Stores information about all discovered/connected peers
|
# Stores information about all discovered/connected peers
|
||||||
CustomPeersTableRef* = TableRef[string, CustomPeerInfo]
|
CustomPeersTableRef* = TableRef[string, CustomPeerInfo]
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue