diff --git a/apps/wakunode2/internal_config.nim b/apps/wakunode2/internal_config.nim index 6da34c7c4e..a5de21eee0 100644 --- a/apps/wakunode2/internal_config.nim +++ b/apps/wakunode2/internal_config.nim @@ -1,9 +1,12 @@ import + chronicles, + chronos, + libp2p/crypto/crypto, + libp2p/multiaddress, + libp2p/nameresolving/dnsresolver, std/options, - stew/shims/net, stew/results, - libp2p/crypto/crypto, - libp2p/multiaddress + stew/shims/net import ../../waku/common/utils/nat, ../../waku/node/config, @@ -18,6 +21,23 @@ proc validateExtMultiAddrs*(vals: seq[string]): multiaddrs.add(multiaddr) return ok(multiaddrs) +proc dnsResolve*(domain: string, conf: WakuNodeConf): Future[Result[string, string]] {.async} = + + # Use conf's DNS servers + var nameServers: seq[TransportAddress] + for ip in conf.dnsAddrsNameServers: + nameServers.add(initTAddress(ip, Port(53))) # Assume all servers use port 53 + + let dnsResolver = DnsResolver.new(nameServers) + + # Resolve domain IP + let resolved = await dnsResolver.resolveIp(domain, 0.Port, Domain.AF_UNSPEC) + + if resolved.len > 0: + return ok(resolved[0].host) # Use only first answer + else: + return err("Could not resolve IP from DNS: empty response") + proc networkConfiguration*(conf: WakuNodeConf, clientId: string, ): NetConfigResult = @@ -30,7 +50,7 @@ proc networkConfiguration*(conf: WakuNodeConf, if natRes.isErr(): return err("failed to setup NAT: " & $natRes.error) - let (extIp, extTcpPort, _) = natRes.get() + var (extIp, extTcpPort, _) = natRes.get() let dns4DomainName = if conf.dns4DomainName != "": some(conf.dns4DomainName) @@ -69,6 +89,18 @@ proc networkConfiguration*(conf: WakuNodeConf, relay = conf.relay ) + # Resolve and use DNS domain IP + if dns4DomainName.isSome() and extIp.isNone(): + try: + let dnsRes = waitFor dnsResolve(conf.dns4DomainName, conf) + + if dnsRes.isErr(): + return err($dnsRes.error) # Pass error down the stack + + extIp = some(ValidIpAddress.init(dnsRes.get())) + except CatchableError: + return err("Could not update extIp to resolved DNS IP: " & getCurrentExceptionMsg()) + # Wrap in none because NetConfig does not have a default constructor # TODO: We could change bindIp in NetConfig to be something less restrictive # than ValidIpAddress, which doesn't allow default construction diff --git a/tests/test_wakunode.nim b/tests/test_wakunode.nim index fd6f21808e..7fdc15a98b 100644 --- a/tests/test_wakunode.nim +++ b/tests/test_wakunode.nim @@ -1,7 +1,7 @@ {.used.} import - std/sequtils, + std/[sequtils,strutils], stew/byteutils, stew/shims/net as stewNet, testutils/unittests, @@ -215,6 +215,65 @@ suite "WakuNode": node.announcedAddresses.len == 1 node.announcedAddresses.contains(expectedDns4Addr) + asyncTest "Node uses dns4 resolved ip in announced addresses if no extIp is provided": + let + nodeKey = generateSecp256k1Key() + bindIp = ValidIpAddress.init("0.0.0.0") + bindPort = Port(0) + + domainName = "status.im" + node = newTestWakuNode( + nodeKey, + bindIp, bindPort, + dns4DomainName = some(domainName)) + + var ipStr = "" + var enrIp = node.enr.tryGet("ip", array[4, byte]) + + if enrIp.isSome(): + ipStr &= $ipv4(enrIp.get()) + + # Check that the IP filled is the one received by the DNS lookup + # As IPs may change, we check that it's not empty, not the 0 IP and not localhost + check: + ipStr.len() > 0 + not ipStr.contains("0.0.0.0") + not ipStr.contains("127.0.0.1") + + asyncTest "Node creation fails when invalid dns4 address is provided": + let + nodeKey = generateSecp256k1Key() + bindIp = ValidIpAddress.init("0.0.0.0") + bindPort = Port(0) + + inexistentDomain = "thisdomain.doesnot.exist" + invalidDomain = "" + expectedError = "Could not resolve IP from DNS: empty response" + + var inexistentDomainErr, invalidDomainErr: string = "" + + # Create node with inexistent domain + try: + let node = newTestWakuNode( + nodeKey, + bindIp, bindPort, + dns4DomainName = some(inexistentDomain)) + except Exception as e: + inexistentDomainErr = e.msg + + # Create node with invalid domain + try: + let node = newTestWakuNode( + nodeKey, + bindIp, bindPort, + dns4DomainName = some(invalidDomain)) + except Exception as e: + invalidDomainErr = e.msg + + # Check that exceptions were raised in both cases + check: + inexistentDomainErr == expectedError + invalidDomainErr == expectedError asyncTest "Agent string is set and advertised correctly": let diff --git a/tests/testlib/wakunode.nim b/tests/testlib/wakunode.nim index ff0d4697ba..cf5b157bdb 100644 --- a/tests/testlib/wakunode.nim +++ b/tests/testlib/wakunode.nim @@ -13,6 +13,8 @@ import ../../../waku/node/peer_manager, ../../../waku/waku_enr, ../../../waku/waku_discv5, + ../../apps/wakunode2/internal_config, + ../wakunode2/test_app, ./common @@ -38,10 +40,29 @@ proc newTestWakuNode*(nodeKey: crypto.PrivateKey, discv5UdpPort = none(Port), agentString = none(string), peerStoreCapacity = none(int)): WakuNode = + + var resolvedExtIp = extIp + + # Update extPort to default value if it's missing and there's an extIp or a DNS domain + let extPort = if (extIp.isSome() or dns4DomainName.isSome()) and + extPort.isNone(): + some(Port(60000)) + else: + extPort + + if dns4DomainName.isSome() and extIp.isNone(): + let conf = defaultTestWakuNodeConf() + # If there's an error resolving the IP, an exception is thrown and test fails + let dnsRes = waitFor dnsResolve(dns4DomainName.get(), conf) + if dnsRes.isErr(): + raise newException(Defect, $dnsRes.error) + else: + resolvedExtIp = some(ValidIpAddress.init(dnsRes.get())) + let netConfigRes = NetConfig.init( bindIp = bindIp, bindPort = bindPort, - extIp = extIp, + extIp = resolvedExtIp, extPort = extPort, extMultiAddrs = extMultiAddrs, wsBindPort = wsBindPort, diff --git a/tests/wakunode2/test_app.nim b/tests/wakunode2/test_app.nim index 1779d8cfca..d0edd27df8 100644 --- a/tests/wakunode2/test_app.nim +++ b/tests/wakunode2/test_app.nim @@ -15,12 +15,13 @@ import ../testlib/common, ../testlib/wakucore -proc defaultTestWakuNodeConf(): WakuNodeConf = +proc defaultTestWakuNodeConf*(): WakuNodeConf = WakuNodeConf( listenAddress: ValidIpAddress.init("127.0.0.1"), rpcAddress: ValidIpAddress.init("127.0.0.1"), restAddress: ValidIpAddress.init("127.0.0.1"), metricsServerAddress: ValidIpAddress.init("127.0.0.1"), + dnsAddrsNameServers: @[ValidIpAddress.init("1.1.1.1"), ValidIpAddress.init("1.0.0.1")], nat: "any", maxConnections: 50, topics: @["/waku/2/default-waku/proto"],