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:
parent
e58658d822
commit
f274bfe19d
|
@ -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",
|
||||
|
|
|
@ -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():
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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)
|
|
@ -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()
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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 =
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
|
@ -17,6 +17,7 @@ import testmultibase,
|
|||
testpeerid
|
||||
|
||||
import testtcptransport,
|
||||
testnameresolve,
|
||||
testwstransport,
|
||||
testmultistream,
|
||||
testbufferstream,
|
||||
|
|
|
@ -18,6 +18,8 @@ import ../libp2p/[errors,
|
|||
muxers/muxer,
|
||||
muxers/mplex/lpchannel,
|
||||
stream/lpstream,
|
||||
nameresolving/nameresolver,
|
||||
nameresolving/mockresolver,
|
||||
stream/chronosstream,
|
||||
transports/tcptransport]
|
||||
import ./helpers
|
||||
|
|
Loading…
Reference in New Issue