DNS Addresses handling (#580)

* add 'dns' multiaddr protocol

* multiaddr: isWire is true for DNS protocols

* resolve dns on connect

* fix typo

* add dns test

* update resolveDns error handling

* handle multiple dns entries

* start of new resolver

* working dns resolver

* use the DnsResolver

* fix json logs

* small overhaul

* fix dns implem in lp2p

* update dnsclient repo

* add dns test to testnative

* dummy dns server for ut

* better mocked

* moved resolving to transport

* moved mockresolver to libp2p

* test resolve in switch test

* try multiple txt & track leaks

* raise e

* catchable error instead of exception

* save failed dns server

* moved resolve back to dialer

* remove nameresolver from dialer
This commit is contained in:
Tanguy Cizain 2021-08-18 09:40:12 +02:00 committed by GitHub
parent e58658d822
commit f274bfe19d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 655 additions and 13 deletions

View File

@ -9,6 +9,7 @@ skipDirs = @["tests", "examples", "Nim", "tools", "scripts", "docs"]
requires "nim >= 1.2.0",
"nimcrypto >= 0.4.1",
"https://github.com/ba0f3/dnsclient.nim == 0.1.0",
"bearssl >= 0.1.4",
"chronicles#ba2817f1",
"chronos >= 2.5.2",

View File

@ -16,6 +16,7 @@ import
muxers/[muxer, mplex/mplex],
protocols/[identify, secure/secure, secure/noise],
connmanager, upgrademngrs/muxedupgrade,
nameresolving/nameresolver,
errors
export
@ -45,6 +46,7 @@ type
maxConnsPerPeer: int
protoVersion: string
agentVersion: string
nameResolver: NameResolver
proc new*(T: type[SwitchBuilder]): T =
@ -129,6 +131,10 @@ proc withAgentVersion*(b: SwitchBuilder, agentVersion: string): SwitchBuilder =
b.agentVersion = agentVersion
b
proc withNameResolver*(b: SwitchBuilder, nameResolver: NameResolver): SwitchBuilder =
b.nameResolver = nameResolver
b
proc build*(b: SwitchBuilder): Switch
{.raises: [Defect, LPError].} =
@ -184,7 +190,8 @@ proc build*(b: SwitchBuilder): Switch
muxers = muxers,
secureManagers = secureManagerInstances,
connManager = connManager,
ms = ms)
ms = ms,
nameResolver = b.nameResolver)
return switch
@ -201,7 +208,8 @@ proc newStandardSwitch*(
maxConnections = MaxConnections,
maxIn = -1,
maxOut = -1,
maxConnsPerPeer = MaxConnectionsPerPeer): Switch
maxConnsPerPeer = MaxConnectionsPerPeer,
nameResolver: NameResolver = nil): Switch
{.raises: [Defect, LPError].} =
if SecureProtocol.Secio in secureManagers:
quit("Secio is deprecated!") # use of secio is unsafe
@ -216,6 +224,7 @@ proc newStandardSwitch*(
.withMaxConnsPerPeer(maxConnsPerPeer)
.withMplex(inTimeout, outTimeout)
.withTcpTransport(transportFlags)
.withNameResolver(nameResolver)
.withNoise()
if privKey.isSome():

View File

@ -13,7 +13,7 @@
import pkg/chronos
import std/[nativesockets, hashes]
import tables, strutils, stew/shims/net
import tables, strutils, sets, stew/shims/net
import multicodec, multihash, multibase, transcoder, vbuffer, peerid,
protobuf/minprotobuf, errors
import stew/[base58, base32, endians2, results]
@ -377,6 +377,10 @@ const
mcodec: multiCodec("unix"), kind: Path, size: 0,
coder: TranscoderUnix
),
MAProtocol(
mcodec: multiCodec("dns"), kind: Length, size: 0,
coder: TranscoderDNS
),
MAProtocol(
mcodec: multiCodec("dns4"), kind: Length, size: 0,
coder: TranscoderDNS
@ -403,11 +407,13 @@ const
)
]
DNSANY* = mapEq("dns")
DNS4* = mapEq("dns4")
DNS6* = mapEq("dns6")
DNSADDR* = mapEq("dnsaddr")
IP4* = mapEq("ip4")
IP6* = mapEq("ip6")
DNS* = mapOr(mapEq("dnsaddr"), DNS4, DNS6)
DNS* = mapOr(DNSANY, DNS4, DNS6, DNSADDR)
IP* = mapOr(IP4, IP6)
TCP* = mapOr(mapAnd(DNS, mapEq("tcp")), mapAnd(IP, mapEq("tcp")))
UDP* = mapOr(mapAnd(DNS, mapEq("udp")), mapAnd(IP, mapEq("udp")))
@ -934,13 +940,24 @@ proc `&=`*(m1: var MultiAddress, m2: MultiAddress) {.
m1.append(m2).tryGet()
proc `==`*(m1: var MultiAddress, m2: MultiAddress): bool =
## Check of two MultiAddress are equal
m1.data == m2.data
proc isWire*(ma: MultiAddress): bool =
## Returns ``true`` if MultiAddress ``ma`` is one of:
## - {IP4}/{TCP, UDP}
## - {IP6}/{TCP, UDP}
## - {UNIX}/{PATH}
var
state = 0
var state = 0
const
wireProtocols = toHashSet([
multiCodec("ip4"), multiCodec("ip6"),
])
wireTransports = toHashSet([
multiCodec("tcp"), multiCodec("udp")
])
try:
for rpart in ma.items():
if rpart.isErr():
@ -953,7 +970,7 @@ proc isWire*(ma: MultiAddress): bool =
return false
let code = rcode.get()
if code == multiCodec("ip4") or code == multiCodec("ip6"):
if code in wireProtocols:
inc(state)
continue
elif code == multiCodec("unix"):
@ -968,7 +985,7 @@ proc isWire*(ma: MultiAddress): bool =
return false
let code = rcode.get()
if code == multiCodec("tcp") or code == multiCodec("udp"):
if code in wireTransports:
inc(state)
result = true
else:

View File

@ -201,6 +201,7 @@ const MultiCodecList = [
("p2p-webrtc-direct", 0x0114), # not in multicodec list
("onion", 0x01BC),
("p2p-circuit", 0x0122),
("dns", 0x35),
("dns4", 0x36),
("dns6", 0x37),
("dnsaddr", 0x38),

View File

@ -0,0 +1,160 @@
## Nim-LibP2P
## Copyright (c) 2021 Status Research & Development GmbH
## Licensed under either of
## * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE))
## * MIT license ([LICENSE-MIT](LICENSE-MIT))
## at your option.
## This file may not be copied, modified, or distributed except according to
## those terms.
{.push raises: [Defect].}
import
std/[streams, strutils, sets, sequtils],
chronos, chronicles,
dnsclientpkg/[protocol, types]
import
nameresolver
logScope:
topics = "libp2p dnsresolver"
type
DnsResolver* = ref object of NameResolver
nameServers*: seq[TransportAddress]
proc questionToBuf(address: string, kind: QKind): seq[byte] =
try:
var
header = initHeader()
question = initQuestion(address, kind)
requestStream = header.toStream()
question.toStream(requestStream)
let dataLen = requestStream.getPosition()
requestStream.setPosition(0)
var buf = newSeq[byte](dataLen)
discard requestStream.readData(addr buf[0], dataLen)
return buf
except CatchableError as exc:
info "Failed to created DNS buffer", msg = exc.msg
return newSeq[byte](0)
proc getDnsResponse(
dnsServer: TransportAddress,
address: string,
kind: QKind): Future[Response] {.async.} =
var sendBuf = questionToBuf(address, kind)
if sendBuf.len == 0:
raise newException(ValueError, "Incorrect DNS query")
let receivedDataFuture = newFuture[void]()
proc datagramDataReceived(transp: DatagramTransport,
raddr: TransportAddress): Future[void] {.async, closure.} =
receivedDataFuture.complete()
let sock =
if dnsServer.family == AddressFamily.IPv6:
newDatagramTransport6(datagramDataReceived)
else:
newDatagramTransport(datagramDataReceived)
try:
await sock.sendTo(dnsServer, addr sendBuf[0], sendBuf.len)
await receivedDataFuture or sleepAsync(5.seconds) #unix default
if not receivedDataFuture.finished:
raise newException(IOError, "DNS server timeout")
var
rawResponse = sock.getMessage()
dataStream = newStringStream()
dataStream.writeData(addr rawResponse[0], rawResponse.len)
dataStream.setPosition(0)
return parseResponse(dataStream)
finally:
await sock.closeWait()
method resolveIp*(
self: DnsResolver,
address: string,
port: Port,
domain: Domain = Domain.AF_UNSPEC): Future[seq[TransportAddress]] {.async.} =
trace "Resolving IP using DNS", address, servers = self.nameservers.mapIt($it), domain
for _ in 0 ..< self.nameservers.len:
let server = self.nameservers[0]
var responseFutures: seq[Future[Response]]
if domain == Domain.AF_INET or domain == Domain.AF_UNSPEC:
responseFutures.add(getDnsResponse(server, address, A))
if domain == Domain.AF_INET6 or domain == Domain.AF_UNSPEC:
let fut = getDnsResponse(server, address, AAAA)
if server.family == AddressFamily.IPv6:
trace "IPv6 DNS server, puting AAAA records first", server = $server
responseFutures.insert(fut)
else:
responseFutures.add(fut)
var
resolvedAddresses: OrderedSet[string]
resolveFailed = false
for fut in responseFutures:
try:
let resp = await fut
for answer in resp.answers:
resolvedAddresses.incl(answer.toString())
except CancelledError as e:
raise e
except ValueError as e:
info "Invalid DNS query", address, error=e.msg
return @[]
except CatchableError as e:
info "Failed to query DNS", address, error=e.msg
resolveFailed = true
break
if resolveFailed:
self.nameservers.add(self.nameservers[0])
self.nameservers.delete(0)
continue
trace "Got IPs from DNS server", resolvedAddresses, server = $server
return resolvedAddresses.toSeq().mapIt(initTAddress(it, port))
debug "Failed to resolve address, returning empty set"
return @[]
method resolveTxt*(
self: DnsResolver,
address: string): Future[seq[string]] {.async.} =
trace "Resolving TXT using DNS", address, servers = self.nameservers.mapIt($it)
for _ in 0 ..< self.nameservers.len:
let server = self.nameservers[0]
try:
let response = await getDnsResponse(server, address, TXT)
trace "Got TXT response", server = $server, answer=response.answers.mapIt(it.toString())
return response.answers.mapIt(it.toString())
except CancelledError as e:
raise e
except CatchableError as e:
info "Failed to query DNS", address, error=e.msg
self.nameservers.add(self.nameservers[0])
self.nameservers.delete(0)
continue
debug "Failed to resolve TXT, returning empty set"
return @[]
proc new*(
T: typedesc[DnsResolver],
nameServers: seq[TransportAddress]): T =
T(nameServers: nameServers)

View File

@ -0,0 +1,46 @@
## Nim-LibP2P
## Copyright (c) 2021 Status Research & Development GmbH
## Licensed under either of
## * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE))
## * MIT license ([LICENSE-MIT](LICENSE-MIT))
## at your option.
## This file may not be copied, modified, or distributed except according to
## those terms.
{.push raises: [Defect].}
import
std/[streams, strutils, tables],
chronos, chronicles
import nameresolver
export tables
logScope:
topics = "libp2p mockresolver"
type MockResolver* = ref object of NameResolver
txtResponses*: Table[string, seq[string]]
# key: address, isipv6?
ipResponses*: Table[(string, bool), seq[string]]
method resolveIp*(
self: MockResolver,
address: string,
port: Port,
domain: Domain = Domain.AF_UNSPEC): Future[seq[TransportAddress]] {.async.} =
if domain == Domain.AF_INET or domain == Domain.AF_UNSPEC:
for resp in self.ipResponses.getOrDefault((address, false)):
result.add(initTAddress(resp, port))
if domain == Domain.AF_INET6 or domain == Domain.AF_UNSPEC:
for resp in self.ipResponses.getOrDefault((address, true)):
result.add(initTAddress(resp, port))
method resolveTxt*(
self: MockResolver,
address: string): Future[seq[string]] {.async.} =
return self.txtResponses.getOrDefault(address)
proc new*(T: typedesc[MockResolver]): T = T()

View File

@ -0,0 +1,148 @@
## Nim-LibP2P
## Copyright (c) 2021 Status Research & Development GmbH
## Licensed under either of
## * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE))
## * MIT license ([LICENSE-MIT](LICENSE-MIT))
## at your option.
## This file may not be copied, modified, or distributed except according to
## those terms.
{.push raises: [Defect].}
import std/[sugar, sets, sequtils, strutils]
import
chronos,
chronicles,
stew/[endians2, byteutils]
import ".."/[multiaddress, multicodec]
logScope:
topics = "libp2p nameresolver"
type
NameResolver* = ref object of RootObj
method resolveTxt*(
self: NameResolver,
address: string): Future[seq[string]] {.async, base.} =
## Get TXT record
##
doAssert(false, "Not implemented!")
method resolveIp*(
self: NameResolver,
address: string,
port: Port,
domain: Domain = Domain.AF_UNSPEC): Future[seq[TransportAddress]] {.async, base.} =
## Resolve the specified address
##
doAssert(false, "Not implemented!")
proc getHostname(ma: MultiAddress): string =
var dnsbuf = newSeq[byte](256)
let dnsLen = ma[0].get().protoArgument(dnsbuf).get()
dnsbuf.setLen(dnsLen)
return string.fromBytes(dnsbuf)
proc resolveDnsAddress(
self: NameResolver,
ma: MultiAddress,
domain: Domain = Domain.AF_UNSPEC,
prefix = ""): Future[seq[MultiAddress]]
{.async, raises: [Defect, MaError, TransportAddressError].} =
#Resolve a single address
var pbuf: array[2, byte]
var dnsval = getHostname(ma)
if ma[1].tryGet().protoArgument(pbuf).tryGet() == 0:
raise newException(MaError, "Incorrect port number")
let
port = Port(fromBytesBE(uint16, pbuf))
resolvedAddresses = await self.resolveIp(prefix & dnsval, port, domain)
var addressSuffix = ma
return collect(newSeqOfCap(4)):
for address in resolvedAddresses:
var createdAddress = MultiAddress.init(address).tryGet()[0].tryGet()
for part in ma:
if DNS.match(part.get()): continue
createdAddress &= part.tryGet()
createdAddress
func matchDnsSuffix(m1, m2: MultiAddress): MaResult[bool] =
for partMaybe in m1:
let part = ?partMaybe
if DNS.match(part): continue
let entryProt = ?m2[?part.protoCode()]
if entryProt != part:
return ok(false)
return ok(true)
proc resolveDnsAddr(
self: NameResolver,
ma: MultiAddress,
depth: int = 0): Future[seq[MultiAddress]]
{.async.} =
trace "Resolving dnsaddr", ma
if depth > 6:
info "Stopping DNSADDR recursion, probably malicious", ma
return @[]
var dnsval = getHostname(ma)
let txt = await self.resolveTxt("_dnsaddr." & dnsval)
trace "txt entries", txt
var result: seq[MultiAddress]
for entry in txt:
if not entry.startsWith("dnsaddr="): continue
let entryValue = MultiAddress.init(entry[8..^1]).tryGet()
if not matchDnsSuffix(ma, entryValue).tryGet(): continue
# The spec is not clear wheter only DNSADDR can be recursived
# or any DNS addr. Only handling DNSADDR because it's simpler
# to avoid infinite recursion
if DNSADDR.matchPartial(entryValue):
let resolved = await self.resolveDnsAddr(entryValue, depth + 1)
for r in resolved:
result.add(r)
else:
result.add(entryValue)
if result.len == 0:
debug "Failed to resolve any DNSADDR", ma
return @[ma]
return result
proc resolveMAddresses*(
self: NameResolver,
addrs: seq[MultiAddress]): Future[seq[MultiAddress]] {.async.} =
var res = initOrderedSet[MultiAddress]()
for address in addrs:
if not DNS.matchPartial(address):
res.incl(address)
else:
let code = address[0].get().protoCode().get()
let seq = case code:
of multiCodec("dns"):
await self.resolveDnsAddress(address)
of multiCodec("dns4"):
await self.resolveDnsAddress(address, Domain.AF_INET)
of multiCodec("dns6"):
await self.resolveDnsAddress(address, Domain.AF_INET6)
of multiCodec("dnsaddr"):
await self.resolveDnsAddr(address)
else:
@[address]
for ad in seq:
res.incl(ad)
return res.toSeq

View File

@ -32,6 +32,7 @@ import stream/connection,
muxers/muxer,
utils/semaphore,
connmanager,
nameresolving/nameresolver,
peerid,
peerstore,
errors,
@ -62,6 +63,7 @@ type
acceptFuts: seq[Future[void]]
dialer*: Dial
peerStore*: PeerStore
nameResolver*: NameResolver
proc addConnEventHandler*(s: Switch,
handler: ConnEventHandler,
@ -256,7 +258,8 @@ proc newSwitch*(peerInfo: PeerInfo,
muxers: Table[string, MuxerProvider],
secureManagers: openarray[Secure] = [],
connManager: ConnManager,
ms: MultistreamSelect): Switch
ms: MultistreamSelect,
nameResolver: NameResolver = nil): Switch
{.raises: [Defect, LPError].} =
if secureManagers.len == 0:
raise newException(LPError, "Provide at least one secure manager")
@ -267,7 +270,8 @@ proc newSwitch*(peerInfo: PeerInfo,
transports: transports,
connManager: connManager,
peerStore: PeerStore.new(),
dialer: Dialer.new(peerInfo, connManager, transports, ms))
dialer: Dialer.new(peerInfo, connManager, transports, ms),
nameResolver: nameResolver)
switch.mount(identity)
return switch

View File

@ -16,6 +16,7 @@ export asyncunit
const
StreamTransportTrackerName = "stream.transport"
StreamServerTrackerName = "stream.server"
DgramTransportTrackerName = "datagram.transport"
trackerNames = [
LPStreamTrackerName,
@ -25,8 +26,9 @@ const
BufferStreamTrackerName,
TcpTransportTrackerName,
StreamTransportTrackerName,
ChronosStreamTrackerName,
StreamServerTrackerName
StreamServerTrackerName,
DgramTransportTrackerName,
ChronosStreamTrackerName
]
iterator testTrackers*(extras: openArray[string] = []): TrackerBase =

View File

@ -53,6 +53,10 @@ const
"/ip4/1.2.3.4/tcp/80/unix/a/b/c/d/e/f",
"/ip4/127.0.0.1/ipfs/QmcgpsyWgH8Y8ajJz1Cu72KnS5uo2Aa2LpzU7kinSupNKC/tcp/1234/unix/stdio",
"/ip4/127.0.0.1/p2p/QmcgpsyWgH8Y8ajJz1Cu72KnS5uo2Aa2LpzU7kinSupNKC/tcp/1234/unix/stdio",
"/dns/example.io/udp/65535",
"/dns4/example.io/udp/65535",
"/dns6/example.io/udp/65535",
"/dnsaddr/example.io/udp/65535",
]
FailureVectors = [
@ -257,7 +261,7 @@ const
]
),
PatternVector(pattern: DNS,
good: @["/dnsaddr/example.io", "/dns4/example.io", "/dns6/example.io"],
good: @["/dns/example.io", "/dnsaddr/example.io", "/dns4/example.io", "/dns6/example.io"],
bad: @["/ip4/127.0.0.1"],
),
PatternVector(pattern: WebRTCDirect,

247
tests/testnameresolve.nim Normal file
View File

@ -0,0 +1,247 @@
{.used.}
import std/[streams, strutils, sets, sequtils, tables, algorithm]
import chronos, stew/byteutils
import ../libp2p/[stream/connection,
transports/transport,
transports/tcptransport,
upgrademngrs/upgrade,
multiaddress,
errors,
nameresolving/nameresolver,
nameresolving/dnsresolver,
nameresolving/mockresolver,
wire]
import ./helpers
#
#Cloudflare
const fallbackDnsServers = @[
initTAddress("1.1.1.1:53"),
initTAddress("1.0.0.1:53"),
initTAddress("[2606:4700:4700::1111]:53")
]
const unixPlatform = defined(linux) or defined(solaris) or
defined(macosx) or defined(freebsd) or
defined(netbsd) or defined(openbsd) or
defined(dragonfly)
proc guessOsNameServers(): seq[TransportAddress] =
when unixPlatform:
var resultSeq = newSeqOfCap[TransportAddress](3)
try:
for l in lines("/etc/resolv.conf"):
let lineParsed = l.strip().split(seps = Whitespace + {'%'}, maxsplit = 2)
if lineParsed.len < 2: continue
if lineParsed[0].startsWith('#'): continue
if lineParsed[0] == "nameserver":
resultSeq.add(initTAddress(lineParsed[1], Port(53)))
if resultSeq.len > 2: break #3 nameserver max on linux
except Exception as e:
echo "Failed to get unix nameservers ", e.msg
finally:
if resultSeq.len > 0:
return resultSeq
return fallbackDnsServers
elif defined(windows):
#TODO
return fallbackDnsServers
else:
return fallbackDnsServers
suite "Name resolving":
suite "Generic Resolving":
var resolver {.threadvar.}: MockResolver
proc testOne(input: string, output: seq[Multiaddress]): bool =
let resolved = waitFor resolver.resolveMAddresses(@[Multiaddress.init(input).tryGet()])
if resolved != output:
echo "Expected ", output
echo "Got ", resolved
return false
return true
proc testOne(input: string, output: seq[string]): bool =
testOne(input, output.mapIt(Multiaddress.init(it).tryGet()))
proc testOne(input, output: string): bool =
testOne(input, @[Multiaddress.init(output).tryGet()])
asyncSetup:
resolver = MockResolver.new()
asyncTest "test multi address dns resolve":
resolver.ipResponses[("localhost", false)] = @["127.0.0.1"]
resolver.ipResponses[("localhost", true)] = @["::1"]
check testOne("/dns/localhost/udp/0", @["/ip4/127.0.0.1/udp/0", "/ip6/::1/udp/0"])
check testOne("/dns4/localhost/tcp/0", "/ip4/127.0.0.1/tcp/0")
check testOne("/dns6/localhost/tcp/0", "/ip6/::1/tcp/0")
check testOne("/dns6/localhost/tcp/4001/p2p/QmNnooDu7bfjPFoTZYxMNLWUQJyrVwtbZg5gBMjTezGAJN", "/ip6/::1/tcp/4001/p2p/QmNnooDu7bfjPFoTZYxMNLWUQJyrVwtbZg5gBMjTezGAJN")
asyncTest "test non dns resolve":
resolver.ipResponses[("localhost", false)] = @["127.0.0.1"]
resolver.ipResponses[("localhost", true)] = @["::1"]
check testOne("/ip6/::1/tcp/0", "/ip6/::1/tcp/0")
asyncTest "test multiple resolve":
resolver.ipResponses[("localhost", false)] = @["127.0.0.1"]
resolver.ipResponses[("localhost", true)] = @["::1"]
let resolved = waitFor resolver.resolveMAddresses(@[
Multiaddress.init("/dns/localhost/udp/0").tryGet(),
Multiaddress.init("/dns4/localhost/udp/0").tryGet(),
Multiaddress.init("/dns6/localhost/udp/0").tryGet(),
])
check resolved == @[Multiaddress.init("/ip4/127.0.0.1/udp/0").tryGet(), Multiaddress.init("/ip6/::1/udp/0").tryGet()]
asyncTest "dnsaddr recursive test":
resolver.txtResponses["_dnsaddr.bootstrap.libp2p.io"] = @[
"dnsaddr=/dnsaddr/sjc-1.bootstrap.libp2p.io/tcp/4001/p2p/QmNnooDu7bfjPFoTZYxMNLWUQJyrVwtbZg5gBMjTezGAJN",
"dnsaddr=/dnsaddr/ams-2.bootstrap.libp2p.io/tcp/4001/p2p/QmbLHAnMoJPWSCR5Zhtx6BHJX9KiKNN6tpvbUcqanj75Nb"
]
resolver.txtResponses["_dnsaddr.sjc-1.bootstrap.libp2p.io"] = @[
"dnsaddr=/ip6/2604:1380:1000:6000::1/tcp/4001/p2p/QmNnooDu7bfjPFoTZYxMNLWUQJyrVwtbZg5gBMjTezGAJN",
"dnsaddr=/ip4/147.75.69.143/tcp/4001/p2p/QmNnooDu7bfjPFoTZYxMNLWUQJyrVwtbZg5gBMjTezGAJN"
]
resolver.txtResponses["_dnsaddr.ams-2.bootstrap.libp2p.io"] = @[
"dnsaddr=/ip4/147.75.83.83/tcp/4001/p2p/QmbLHAnMoJPWSCR5Zhtx6BHJX9KiKNN6tpvbUcqanj75Nb",
"dnsaddr=/ip6/2604:1380:2000:7a00::1/tcp/4001/p2p/QmbLHAnMoJPWSCR5Zhtx6BHJX9KiKNN6tpvbUcqanj75Nb"
]
check testOne("/dnsaddr/bootstrap.libp2p.io/", @[
"/ip6/2604:1380:1000:6000::1/tcp/4001/p2p/QmNnooDu7bfjPFoTZYxMNLWUQJyrVwtbZg5gBMjTezGAJN",
"/ip4/147.75.69.143/tcp/4001/p2p/QmNnooDu7bfjPFoTZYxMNLWUQJyrVwtbZg5gBMjTezGAJN",
"/ip4/147.75.83.83/tcp/4001/p2p/QmbLHAnMoJPWSCR5Zhtx6BHJX9KiKNN6tpvbUcqanj75Nb",
"/ip6/2604:1380:2000:7a00::1/tcp/4001/p2p/QmbLHAnMoJPWSCR5Zhtx6BHJX9KiKNN6tpvbUcqanj75Nb",
])
asyncTest "dnsaddr suffix matching test":
resolver.txtResponses["_dnsaddr.bootstrap.libp2p.io"] = @[
"dnsaddr=/dnsaddr/ams-2.bootstrap.libp2p.io/tcp/4001/p2p/QmbLHAnMoJPWSCR5Zhtx6BHJX9KiKNN6tpvbUcqanj75Nb",
"dnsaddr=/dnsaddr/sjc-1.bootstrap.libp2p.io/tcp/4001/p2p/QmNnooDu7bfjPFoTZYxMNLWUQJyrVwtbZg5gBMjTezGAJN",
"dnsaddr=/dnsaddr/nrt-1.bootstrap.libp2p.io/tcp/4001/p2p/QmcZf59bWwK5XFi76CZX8cbJ4BhTzzA3gU1ZjYZcYW3dwt",
"dnsaddr=/dnsaddr/ewr-1.bootstrap.libp2p.io/tcp/4001/p2p/QmQCU2EcMqAqQPR2i9bChDtGNJchTbq5TbXJJ16u19uLTa",
]
resolver.txtResponses["_dnsaddr.sjc-1.bootstrap.libp2p.io"] = @[
"dnsaddr=/ip4/147.75.69.143/tcp/4001/p2p/QmNnooDu7bfjPFoTZYxMNLWUQJyrVwtbZg5gBMjTezGAJN",
"dnsaddr=/ip6/2604:1380:1000:6000::1/tcp/4001/p2p/QmNnooDu7bfjPFoTZYxMNLWUQJyrVwtbZg5gBMjTezGAJN",
]
resolver.txtResponses["_dnsaddr.ams-1.bootstrap.libp2p.io"] = @[
"dnsaddr=/ip4/147.75.69.143/tcp/4001/p2p/shouldbefiltered",
"dnsaddr=/ip6/2604:1380:1000:6000::1/tcp/4001/p2p/shouldbefiltered",
]
check testOne("/dnsaddr/bootstrap.libp2p.io/p2p/QmNnooDu7bfjPFoTZYxMNLWUQJyrVwtbZg5gBMjTezGAJN", @[
"/ip4/147.75.69.143/tcp/4001/p2p/QmNnooDu7bfjPFoTZYxMNLWUQJyrVwtbZg5gBMjTezGAJN",
"/ip6/2604:1380:1000:6000::1/tcp/4001/p2p/QmNnooDu7bfjPFoTZYxMNLWUQJyrVwtbZg5gBMjTezGAJN",
])
asyncTest "dnsaddr infinite recursion":
resolver.txtResponses["_dnsaddr.bootstrap.libp2p.io"] = @["dnsaddr=/dnsaddr/bootstrap.libp2p.io"]
check testOne("/dnsaddr/bootstrap.libp2p.io/", "/dnsaddr/bootstrap.libp2p.io/")
suite "DNS Resolving":
teardown:
checkTrackers()
asyncTest "test manual dns ip resolve":
## DNS mock server
proc clientMark1(transp: DatagramTransport,
raddr: TransportAddress): Future[void] {.async.} =
var msg = transp.getMessage()
let
resp = if msg[24] == 1: #AAAA or A
"\xae\xbf\x81\x80\x00\x01\x00\x03\x00\x00\x00\x00\x06\x73\x74\x61" &
"\x74\x75\x73\x02\x69\x6d\x00\x00\x01\x00\x01\xc0\x0c\x00\x01\x00" &
"\x01\x00\x00\x00\x4f\x00\x04\x68\x16\x18\xb5\xc0\x0c\x00\x01\x00" &
"\x01\x00\x00\x00\x4f\x00\x04\xac\x43\x0a\xa1\xc0\x0c\x00\x01\x00" &
"\x01\x00\x00\x00\x4f\x00\x04\x68\x16\x19\xb5"
else:
"\xe8\xc5\x81\x80\x00\x01\x00\x03\x00\x00\x00\x00\x06\x73\x74\x61" &
"\x74\x75\x73\x02\x69\x6d\x00\x00\x1c\x00\x01\xc0\x0c\x00\x1c\x00" &
"\x01\x00\x00\x00\x4f\x00\x10\x26\x06\x47\x00\x00\x10\x00\x00\x00" &
"\x00\x00\x00\x68\x16\x19\xb5\xc0\x0c\x00\x1c\x00\x01\x00\x00\x00" &
"\x4f\x00\x10\x26\x06\x47\x00\x00\x10\x00\x00\x00\x00\x00\x00\x68" &
"\x16\x18\xb5\xc0\x0c\x00\x1c\x00\x01\x00\x00\x00\x4f\x00\x10\x26" &
"\x06\x47\x00\x00\x10\x00\x00\x00\x00\x00\x00\xac\x43\x0a\xa1"
await transp.sendTo(raddr, resp)
let server = newDatagramTransport(clientMark1)
# The test
var dnsresolver = DnsResolver.new(@[server.localAddress])
check await(dnsresolver.resolveIp("status.im", 0.Port, Domain.AF_UNSPEC)) ==
mapIt(
@["104.22.24.181:0", "172.67.10.161:0", "104.22.25.181:0",
"[2606:4700:10::6816:19b5]:0", "[2606:4700:10::6816:18b5]:0", "[2606:4700:10::ac43:aa1]:0"
], initTAddress(it))
check await(dnsresolver.resolveIp("status.im", 0.Port, Domain.AF_INET)) ==
mapIt(@["104.22.24.181:0", "172.67.10.161:0", "104.22.25.181:0"], initTAddress(it))
check await(dnsresolver.resolveIp("status.im", 0.Port, Domain.AF_INET6)) ==
mapIt(@["[2606:4700:10::6816:19b5]:0", "[2606:4700:10::6816:18b5]:0", "[2606:4700:10::ac43:aa1]:0"], initTAddress(it))
await server.closeWait()
asyncTest "test unresponsive dns server":
var unresponsiveTentatives = 0
## DNS mock server
proc clientMark1(transp: DatagramTransport,
raddr: TransportAddress): Future[void] {.async.} =
unresponsiveTentatives.inc()
check unresponsiveTentatives == 1
proc clientMark2(transp: DatagramTransport,
raddr: TransportAddress): Future[void] {.async.} =
var msg = transp.getMessage()
let resp =
"\xae\xbf\x81\x80\x00\x01\x00\x03\x00\x00\x00\x00\x06\x73\x74\x61" &
"\x74\x75\x73\x02\x69\x6d\x00\x00\x01\x00\x01\xc0\x0c\x00\x01\x00" &
"\x01\x00\x00\x00\x4f\x00\x04\x68\x16\x18\xb5\xc0\x0c\x00\x01\x00" &
"\x01\x00\x00\x00\x4f\x00\x04\xac\x43\x0a\xa1\xc0\x0c\x00\x01\x00" &
"\x01\x00\x00\x00\x4f\x00\x04\x68\x16\x19\xb5"
await transp.sendTo(raddr, resp)
let
unresponsiveServer = newDatagramTransport(clientMark1)
server = newDatagramTransport(clientMark2)
# The test
var dnsresolver = DnsResolver.new(@[unresponsiveServer.localAddress, server.localAddress])
check await(dnsresolver.resolveIp("status.im", 0.Port, Domain.AF_INET)) ==
mapIt(@["104.22.24.181:0", "172.67.10.161:0", "104.22.25.181:0"], initTAddress(it))
check await(dnsresolver.resolveIp("status.im", 0.Port, Domain.AF_INET)) ==
mapIt(@["104.22.24.181:0", "172.67.10.161:0", "104.22.25.181:0"], initTAddress(it))
await server.closeWait()
await unresponsiveServer.closeWait()
asyncTest "inexisting domain resolving":
var dnsresolver = DnsResolver.new(guessOsNameServers())
let invalid = await dnsresolver.resolveIp("thisdomain.doesnot.exist", 0.Port)
check invalid.len == 0
asyncTest "wrong domain resolving":
var dnsresolver = DnsResolver.new(guessOsNameServers())
let invalid = await dnsresolver.resolveIp("", 0.Port)
check invalid.len == 0
asyncTest "unreachable dns server":
var dnsresolver = DnsResolver.new(@[initTAddress("172.67.10.161:53")])
let invalid = await dnsresolver.resolveIp("google.fr", 0.Port)
check invalid.len == 0

View File

@ -17,6 +17,7 @@ import testmultibase,
testpeerid
import testtcptransport,
testnameresolve,
testwstransport,
testmultistream,
testbufferstream,

View File

@ -18,6 +18,8 @@ import ../libp2p/[errors,
muxers/muxer,
muxers/mplex/lpchannel,
stream/lpstream,
nameresolving/nameresolver,
nameresolving/mockresolver,
stream/chronosstream,
transports/tcptransport]
import ./helpers