Async DNS over TCP (#25690)

This commit is contained in:
Arnout Engelen 2018-10-16 15:35:55 +02:00 committed by GitHub
parent d71ba251ed
commit 23b7f86a06
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
22 changed files with 848 additions and 108 deletions

View file

@ -0,0 +1,50 @@
# The bind.keys file is used to override the built-in DNSSEC trust anchors
# which are included as part of BIND 9. The only trust anchors it contains
# are for the DNS root zone ("."). Trust anchors for any other zones MUST
# be configured elsewhere; if they are configured here, they will not be
# recognized or used by named.
#
# The built-in trust anchors are provided for convenience of configuration.
# They are not activated within named.conf unless specifically switched on.
# To use the built-in key, use "dnssec-validation auto;" in the
# named.conf options. Without this option being set, the keys in this
# file are ignored.
#
# This file is NOT expected to be user-configured.
#
# These keys are current as of October 2017. If any key fails to
# initialize correctly, it may have expired. In that event you should
# replace this file with a current version. The latest version of
# bind.keys can always be obtained from ISC at https://www.isc.org/bind-keys.
#
# See https://data.iana.org/root-anchors/root-anchors.xml
# for current trust anchor information for the root zone.
managed-keys {
# This key (19036) is to be phased out starting in 2017. It will
# remain in the root zone for some time after its successor key
# has been added. It will remain this file until it is removed from
# the root zone.
. initial-key 257 3 8 "AwEAAagAIKlVZrpC6Ia7gEzahOR+9W29euxhJhVVLOyQbSEW0O8gcCjF
FVQUTf6v58fLjwBd0YI0EzrAcQqBGCzh/RStIoO8g0NfnfL2MTJRkxoX
bfDaUeVPQuYEhg37NZWAJQ9VnMVDxP/VHL496M/QZxkjf5/Efucp2gaD
X6RS6CXpoY68LsvPVjR0ZSwzz1apAzvN9dlzEheX7ICJBBtuA6G3LQpz
W5hOA2hzCTMjJPJ8LbqF6dsV6DoBQzgul0sGIcGOYl7OyQdXfZ57relS
Qageu+ipAdTTJ25AsRTAoub8ONGcLmqrAmRLKBP1dfwhYB4N7knNnulq
QxA+Uk1ihz0=";
# This key (20326) was published in the root zone in 2017.
# Servers which were already using the old key (19036) should
# roll seamlessly to this new one via RFC 5011 rollover. Servers
# being set up for the first time can use the contents of this
# file as initializing keys; thereafter, the keys in the
# managed key database will be trusted and maintained
# automatically.
. initial-key 257 3 8 "AwEAAaz/tAm8yTn4Mfeh5eyI96WSVexTBAvkMgJzkKTOiW1vkIbzxeF3
+/4RgWOq7HrxRixHlFlExOLAJr5emLvN7SWXgnLh4+B5xQlNVz8Og8kv
ArMtNROxVQuCaSnIDdD5LKyWbRd2n9WGe2R8PzgCmr3EgVLrjyBxWezF
0jLHwVN8efS3rCj/EWgvIWgb9tarpVUDK/b58Da+sqqls3eNbuv7pr+e
oZG+SrDK6nWeL3c6H5Apxz7LjVc1uTIdsIXxuOLYA4/ilBmSVIzuDWfd
RUfhHdY6+cn8HFRm+2hM8AnXGXws9555KrUB5qihylGa8subX2Nn6UwN
R1AkUTV74bU=";
};

View file

@ -0,0 +1,12 @@
;
; BIND reverse data file for broadcast zone
;
$TTL 604800
@ IN SOA localhost. root.localhost. (
1 ; Serial
604800 ; Refresh
86400 ; Retry
2419200 ; Expire
604800 ) ; Negative Cache TTL
;
@ IN NS localhost.

View file

@ -0,0 +1,13 @@
;
; BIND reverse data file for local loopback interface
;
$TTL 604800
@ IN SOA localhost. root.localhost. (
1 ; Serial
604800 ; Refresh
86400 ; Retry
2419200 ; Expire
604800 ) ; Negative Cache TTL
;
@ IN NS localhost.
1.0.0 IN PTR localhost.

View file

@ -0,0 +1,12 @@
;
; BIND reverse data file for broadcast zone
;
$TTL 604800
@ IN SOA localhost. root.localhost. (
1 ; Serial
604800 ; Refresh
86400 ; Retry
2419200 ; Expire
604800 ) ; Negative Cache TTL
;
@ IN NS localhost.

View file

@ -0,0 +1,13 @@
$TTL 86400
@ IN SOA bar.example root.bar.example (
2017010302
3600
900
604800
86400
)
@ IN NS example
example IN A 192.168.2.19
a-single IN A 192.168.2.20

View file

@ -0,0 +1,14 @@
; BIND reverse data file for empty rfc1918 zone
;
; DO NOT EDIT THIS FILE - it is used for multiple zones.
; Instead, copy it, edit named.conf, and use that copy.
;
$TTL 86400
@ IN SOA localhost. root.localhost. (
1 ; Serial
604800 ; Refresh
86400 ; Retry
2419200 ; Expire
86400 ) ; Negative Cache TTL
;
@ IN NS localhost.

View file

@ -0,0 +1,80 @@
$TTL 86400
@ IN SOA foo.test root.foo.test (
2017010302
3600
900
604800
86400
)
@ IN NS test
test IN A 192.168.1.19
a-single IN A 192.168.1.20
a-double IN A 192.168.1.21
a-double IN A 192.168.1.22
aaaa-single IN AAAA fd4d:36b2:3eca:a2d8:0:0:0:1
aaaa-double IN AAAA fd4d:36b2:3eca:a2d8:0:0:0:2
aaaa-double IN AAAA fd4d:36b2:3eca:a2d8:0:0:0:3
a-aaaa IN AAAA fd4d:36b2:3eca:a2d8:0:0:0:4
a-aaaa IN AAAA fd4d:36b2:3eca:a2d8:0:0:0:5
a-aaaa IN A 192.168.1.23
a-aaaa IN A 192.168.1.24
many in AAAA 2001:985:965:1:ba27:ebff:fe5f:9d00
many in AAAA 2001:985:965:1:ba27:ebff:fe5f:9d01
many in AAAA 2001:985:965:1:ba27:ebff:fe5f:9d02
many in AAAA 2001:985:965:1:ba27:ebff:fe5f:9d03
many in AAAA 2001:985:965:1:ba27:ebff:fe5f:9d04
many in AAAA 2001:985:965:1:ba27:ebff:fe5f:9d05
many in AAAA 2001:985:965:1:ba27:ebff:fe5f:9d06
many in AAAA 2001:985:965:1:ba27:ebff:fe5f:9d07
many in AAAA 2001:985:965:1:ba27:ebff:fe5f:9d08
many in AAAA 2001:985:965:1:ba27:ebff:fe5f:9d09
many in AAAA 2001:985:965:1:ba27:ebff:fe5f:9d0a
many in AAAA 2001:985:965:1:ba27:ebff:fe5f:9d0b
many in AAAA 2001:985:965:1:ba27:ebff:fe5f:9d0c
many in AAAA 2001:985:965:1:ba27:ebff:fe5f:9d0d
many in AAAA 2001:985:965:1:ba27:ebff:fe5f:9d0e
many in AAAA 2001:985:965:1:ba27:ebff:fe5f:9d0f
many in AAAA 2001:985:965:1:ba27:ebff:fe5f:9d10
many in AAAA 2001:985:965:1:ba27:ebff:fe5f:9d11
many in AAAA 2001:985:965:1:ba27:ebff:fe5f:9d12
many in AAAA 2001:985:965:1:ba27:ebff:fe5f:9d13
many in AAAA 2001:985:965:1:ba27:ebff:fe5f:9d14
many in AAAA 2001:985:965:1:ba27:ebff:fe5f:9d15
many in AAAA 2001:985:965:1:ba27:ebff:fe5f:9d16
many in AAAA 2001:985:965:1:ba27:ebff:fe5f:9d17
many in AAAA 2001:985:965:1:ba27:ebff:fe5f:9d18
many in AAAA 2001:985:965:1:ba27:ebff:fe5f:9d19
many in AAAA 2001:985:965:1:ba27:ebff:fe5f:9d1a
many in AAAA 2001:985:965:1:ba27:ebff:fe5f:9d1b
many in AAAA 2001:985:965:1:ba27:ebff:fe5f:9d1c
many in AAAA 2001:985:965:1:ba27:ebff:fe5f:9d1d
many in AAAA 2001:985:965:1:ba27:ebff:fe5f:9d1e
many in AAAA 2001:985:965:1:ba27:ebff:fe5f:9d1f
many in AAAA 2001:985:965:1:ba27:ebff:fe5f:9d20
many in AAAA 2001:985:965:1:ba27:ebff:fe5f:9d21
many in AAAA 2001:985:965:1:ba27:ebff:fe5f:9d22
many in AAAA 2001:985:965:1:ba27:ebff:fe5f:9d23
many in AAAA 2001:985:965:1:ba27:ebff:fe5f:9d24
many in AAAA 2001:985:965:1:ba27:ebff:fe5f:9d25
many in AAAA 2001:985:965:1:ba27:ebff:fe5f:9d26
many in AAAA 2001:985:965:1:ba27:ebff:fe5f:9d27
many in AAAA 2001:985:965:1:ba27:ebff:fe5f:9d28
many in AAAA 2001:985:965:1:ba27:ebff:fe5f:9d29
many in AAAA 2001:985:965:1:ba27:ebff:fe5f:9d2a
many in AAAA 2001:985:965:1:ba27:ebff:fe5f:9d2b
many in AAAA 2001:985:965:1:ba27:ebff:fe5f:9d2c
many in AAAA 2001:985:965:1:ba27:ebff:fe5f:9d2d
many in AAAA 2001:985:965:1:ba27:ebff:fe5f:9d2e
many in AAAA 2001:985:965:1:ba27:ebff:fe5f:9d2f
service.tcp 86400 IN SRV 10 60 5060 a-single
service.tcp 86400 IN SRV 10 40 5070 a-double
cname-in IN CNAME a-double
cname-ext IN CNAME a-single.bar.example.

View file

@ -0,0 +1,14 @@
;
; BIND data file for local loopback interface
;
$TTL 604800
@ IN SOA localhost. root.localhost. (
2 ; Serial
604800 ; Refresh
86400 ; Retry
2419200 ; Expire
604800 ) ; Negative Cache TTL
;
@ IN NS localhost.
@ IN A 127.0.0.1
@ IN AAAA ::1

View file

@ -0,0 +1,90 @@
; This file holds the information on root name servers needed to
; initialize cache of Internet domain name servers
; (e.g. reference this file in the "cache . <file>"
; configuration file of BIND domain name servers).
;
; This file is made available by InterNIC
; under anonymous FTP as
; file /domain/named.cache
; on server FTP.INTERNIC.NET
; -OR- RS.INTERNIC.NET
;
; last update: February 17, 2016
; related version of root zone: 2016021701
;
; formerly NS.INTERNIC.NET
;
. 3600000 NS A.ROOT-SERVERS.NET.
A.ROOT-SERVERS.NET. 3600000 A 198.41.0.4
A.ROOT-SERVERS.NET. 3600000 AAAA 2001:503:ba3e::2:30
;
; FORMERLY NS1.ISI.EDU
;
. 3600000 NS B.ROOT-SERVERS.NET.
B.ROOT-SERVERS.NET. 3600000 A 192.228.79.201
B.ROOT-SERVERS.NET. 3600000 AAAA 2001:500:84::b
;
; FORMERLY C.PSI.NET
;
. 3600000 NS C.ROOT-SERVERS.NET.
C.ROOT-SERVERS.NET. 3600000 A 192.33.4.12
C.ROOT-SERVERS.NET. 3600000 AAAA 2001:500:2::c
;
; FORMERLY TERP.UMD.EDU
;
. 3600000 NS D.ROOT-SERVERS.NET.
D.ROOT-SERVERS.NET. 3600000 A 199.7.91.13
D.ROOT-SERVERS.NET. 3600000 AAAA 2001:500:2d::d
;
; FORMERLY NS.NASA.GOV
;
. 3600000 NS E.ROOT-SERVERS.NET.
E.ROOT-SERVERS.NET. 3600000 A 192.203.230.10
;
; FORMERLY NS.ISC.ORG
;
. 3600000 NS F.ROOT-SERVERS.NET.
F.ROOT-SERVERS.NET. 3600000 A 192.5.5.241
F.ROOT-SERVERS.NET. 3600000 AAAA 2001:500:2f::f
;
; FORMERLY NS.NIC.DDN.MIL
;
. 3600000 NS G.ROOT-SERVERS.NET.
G.ROOT-SERVERS.NET. 3600000 A 192.112.36.4
;
; FORMERLY AOS.ARL.ARMY.MIL
;
. 3600000 NS H.ROOT-SERVERS.NET.
H.ROOT-SERVERS.NET. 3600000 A 198.97.190.53
H.ROOT-SERVERS.NET. 3600000 AAAA 2001:500:1::53
;
; FORMERLY NIC.NORDU.NET
;
. 3600000 NS I.ROOT-SERVERS.NET.
I.ROOT-SERVERS.NET. 3600000 A 192.36.148.17
I.ROOT-SERVERS.NET. 3600000 AAAA 2001:7fe::53
;
; OPERATED BY VERISIGN, INC.
;
. 3600000 NS J.ROOT-SERVERS.NET.
J.ROOT-SERVERS.NET. 3600000 A 192.58.128.30
J.ROOT-SERVERS.NET. 3600000 AAAA 2001:503:c27::2:30
;
; OPERATED BY RIPE NCC
;
. 3600000 NS K.ROOT-SERVERS.NET.
K.ROOT-SERVERS.NET. 3600000 A 193.0.14.129
K.ROOT-SERVERS.NET. 3600000 AAAA 2001:7fd::1
;
; OPERATED BY ICANN
;
. 3600000 NS L.ROOT-SERVERS.NET.
L.ROOT-SERVERS.NET. 3600000 A 199.7.83.42
L.ROOT-SERVERS.NET. 3600000 AAAA 2001:500:3::42
;
; OPERATED BY WIDE
;
. 3600000 NS M.ROOT-SERVERS.NET.
M.ROOT-SERVERS.NET. 3600000 A 202.12.27.33
M.ROOT-SERVERS.NET. 3600000 AAAA 2001:dc3::35
; End of file

View file

@ -0,0 +1,11 @@
// This is the primary configuration file for the BIND DNS server named.
//
// Please read /usr/share/doc/bind9/README.Debian.gz for information on the
// structure of BIND configuration files in Debian, *BEFORE* you customize
// this configuration file.
//
// If you are just adding zones, please do that in /etc/bind/named.conf.local
include "/etc/bind/named.conf.options";
include "/etc/bind/named.conf.local";
include "/etc/bind/named.conf.default-zones";

View file

@ -0,0 +1,30 @@
// prime the server with knowledge of the root servers
zone "." {
type hint;
file "/etc/bind/db.root";
};
// be authoritative for the localhost forward and reverse zones, and for
// broadcast zones as per RFC 1912
zone "localhost" {
type master;
file "/etc/bind/db.local";
};
zone "127.in-addr.arpa" {
type master;
file "/etc/bind/db.127";
};
zone "0.in-addr.arpa" {
type master;
file "/etc/bind/db.0";
};
zone "255.in-addr.arpa" {
type master;
file "/etc/bind/db.255";
};

View file

@ -0,0 +1,17 @@
//
// Do any local configuration here
//
// Consider adding the 1918 zones here, if they are not used in your
// organization
include "/etc/bind/zones.rfc1918";
zone "bar.example" {
type master;
file "/etc/bind/db.bar.example";
};
zone "foo.test" {
type master;
file "/etc/bind/db.foo.test";
};

View file

@ -0,0 +1,26 @@
options {
directory "/var/cache/bind";
// If there is a firewall between you and nameservers you want
// to talk to, you may need to fix the firewall to allow multiple
// ports to talk. See http://www.kb.cert.org/vuls/id/800113
// If your ISP provided one or more IP addresses for stable
// nameservers, you probably want to use them as forwarders.
// Uncomment the following block, and insert the addresses replacing
// the all-0's placeholder.
// forwarders {
// 0.0.0.0;
// };
//========================================================================
// If BIND logs error messages about the root key being expired,
// you will need to update your keys. See https://www.isc.org/bind-keys
//========================================================================
dnssec-validation auto;
auth-nxdomain no; # conform to RFC1035
listen-on-v6 { any; };
};

View file

@ -0,0 +1,4 @@
key "rndc-key" {
algorithm hmac-md5;
secret "WNiF81LrIxYbbPwt/twgUA==";
};

View file

@ -0,0 +1,20 @@
zone "10.in-addr.arpa" { type master; file "/etc/bind/db.empty"; };
zone "16.172.in-addr.arpa" { type master; file "/etc/bind/db.empty"; };
zone "17.172.in-addr.arpa" { type master; file "/etc/bind/db.empty"; };
zone "18.172.in-addr.arpa" { type master; file "/etc/bind/db.empty"; };
zone "19.172.in-addr.arpa" { type master; file "/etc/bind/db.empty"; };
zone "20.172.in-addr.arpa" { type master; file "/etc/bind/db.empty"; };
zone "21.172.in-addr.arpa" { type master; file "/etc/bind/db.empty"; };
zone "22.172.in-addr.arpa" { type master; file "/etc/bind/db.empty"; };
zone "23.172.in-addr.arpa" { type master; file "/etc/bind/db.empty"; };
zone "24.172.in-addr.arpa" { type master; file "/etc/bind/db.empty"; };
zone "25.172.in-addr.arpa" { type master; file "/etc/bind/db.empty"; };
zone "26.172.in-addr.arpa" { type master; file "/etc/bind/db.empty"; };
zone "27.172.in-addr.arpa" { type master; file "/etc/bind/db.empty"; };
zone "28.172.in-addr.arpa" { type master; file "/etc/bind/db.empty"; };
zone "29.172.in-addr.arpa" { type master; file "/etc/bind/db.empty"; };
zone "30.172.in-addr.arpa" { type master; file "/etc/bind/db.empty"; };
zone "31.172.in-addr.arpa" { type master; file "/etc/bind/db.empty"; };
zone "168.192.in-addr.arpa" { type master; file "/etc/bind/db.empty"; };

View file

@ -9,92 +9,36 @@ import java.net.InetAddress
import akka.io.dns.DnsProtocol.{ Ip, RequestType, Srv }
import akka.io.{ Dns, IO }
import akka.pattern.ask
import akka.testkit.AkkaSpec
import akka.testkit.{ AkkaSpec, SocketUtil }
import akka.util.Timeout
import scala.concurrent.duration._
/*
Relies on two zones setup, akka.test and akka.test2 e.g.
* Install bind
* Create the two zones in /var/named/akka.test.zone and /var/named/akka.test2.zone
* Add the following to /etc/named.conf:
zone "akka.test" IN {
type master;
file "akka.test.zone";
};
zone "akka.test2" IN {
type master;
file "akka.test2.zone";
};
/var/named/akka.test.zone:
$TTL 86400
@ IN SOA akka.test root.akka.test (
2017010302
3600
900
604800
86400
)
@ IN NS test
test IN A 192.168.1.19
a-single IN A 192.168.1.20
a-double IN A 192.168.1.21
a-double IN A 192.168.1.22
aaaa-single IN AAAA fd4d:36b2:3eca:a2d8:0:0:0:1
aaaa-double IN AAAA fd4d:36b2:3eca:a2d8:0:0:0:2
aaaa-double IN AAAA fd4d:36b2:3eca:a2d8:0:0:0:3
a-aaaa IN AAAA fd4d:36b2:3eca:a2d8:0:0:0:4
a-aaaa IN AAAA fd4d:36b2:3eca:a2d8:0:0:0:5
a-aaaa IN A 192.168.1.23
a-aaaa IN A 192.168.1.24
service.tcp 86400 IN SRV 10 60 5060 a-single
service.tcp 86400 IN SRV 10 40 5070 a-double
cname-in IN CNAME a-double
cname-ext IN CNAME a-single.akka.test2.
/var/named/akka.test2.zone:
$TTL 86400
@ IN SOA akka.test2 root.akka.test2 (
2017010302
3600
900
604800
86400
)
@ IN NS test2
test2 IN A 192.168.2.19
a-single IN A 192.168.2.20
These tests rely on a DNS server with 2 zones configured, foo.test and bar.example.
The configuration to start a bind DNS server in Docker with this configuration
is included, and the test will automatically start this container when the
test starts and tear it down when it finishes.
*/
class AsyncDnsResolverIntegrationSpec extends AkkaSpec(
"""
s"""
akka.loglevel = DEBUG
akka.io.dns.resolver = async-dns
akka.io.dns.async-dns.nameservers = [localhost]
akka.io.dns.async-dns.nameservers = ["localhost:${AsyncDnsResolverIntegrationSpec.dockerDnsServerPort}"]
// akka.io.dns.async-dns.nameservers = default
""") {
""") with DockerBindDnsService {
val duration = 10.seconds
implicit val timeout = Timeout(duration)
"Resolver" must {
val hostPort = AsyncDnsResolverIntegrationSpec.dockerDnsServerPort
pending // PENDING since needs `bind` server to be running to test end-to-end
"Resolver" must {
if (!dockerAvailable())
pending
"resolve single A record" in {
val name = "a-single.akka.test"
val name = "a-single.foo.test"
val answer = resolve(name, DnsProtocol.Ip(ipv6 = false))
withClue(answer) {
answer.name shouldEqual name
@ -105,7 +49,7 @@ class AsyncDnsResolverIntegrationSpec extends AkkaSpec(
}
"resolve double A records" in {
val name = "a-double.akka.test"
val name = "a-double.foo.test"
val answer = resolve(name)
answer.name shouldEqual name
answer.records.map(_.asInstanceOf[ARecord].ip).toSet shouldEqual Set(
@ -115,14 +59,14 @@ class AsyncDnsResolverIntegrationSpec extends AkkaSpec(
}
"resolve single AAAA record" in {
val name = "aaaa-single.akka.test"
val name = "aaaa-single.foo.test"
val answer = resolve(name)
answer.name shouldEqual name
answer.records.map(_.asInstanceOf[AAAARecord].ip) shouldEqual Seq(InetAddress.getByName("fd4d:36b2:3eca:a2d8:0:0:0:1"))
}
"resolve double AAAA records" in {
val name = "aaaa-double.akka.test"
val name = "aaaa-double.foo.test"
val answer = resolve(name)
answer.name shouldEqual name
answer.records.map(_.asInstanceOf[AAAARecord].ip).toSet shouldEqual Set(
@ -132,7 +76,7 @@ class AsyncDnsResolverIntegrationSpec extends AkkaSpec(
}
"resolve mixed A/AAAA records" in {
val name = "a-aaaa.akka.test"
val name = "a-aaaa.foo.test"
val answer = resolve(name)
answer.name shouldEqual name
@ -148,11 +92,11 @@ class AsyncDnsResolverIntegrationSpec extends AkkaSpec(
}
"resolve external CNAME record" in {
val name = "cname-ext.akka.test"
val name = "cname-ext.foo.test"
val answer = (IO(Dns) ? DnsProtocol.Resolve(name)).mapTo[DnsProtocol.Resolved].futureValue
answer.name shouldEqual name
answer.records.collect { case r: CNameRecord r.canonicalName }.toSet shouldEqual Set(
"a-single.akka.test2"
"a-single.bar.example"
)
answer.records.collect { case r: ARecord r.ip }.toSet shouldEqual Set(
InetAddress.getByName("192.168.2.20")
@ -160,11 +104,11 @@ class AsyncDnsResolverIntegrationSpec extends AkkaSpec(
}
"resolve internal CNAME record" in {
val name = "cname-in.akka.test"
val name = "cname-in.foo.test"
val answer = resolve(name)
answer.name shouldEqual name
answer.records.collect { case r: CNameRecord r.canonicalName }.toSet shouldEqual Set(
"a-double.akka.test"
"a-double.foo.test"
)
answer.records.collect { case r: ARecord r.ip }.toSet shouldEqual Set(
InetAddress.getByName("192.168.1.21"),
@ -173,29 +117,39 @@ class AsyncDnsResolverIntegrationSpec extends AkkaSpec(
}
"resolve SRV record" in {
val name = "service.tcp.akka.test"
val answer = resolve("service.tcp.akka.test", Srv)
val name = "service.tcp.foo.test"
val answer = resolve("service.tcp.foo.test", Srv)
answer.name shouldEqual name
answer.records.collect { case r: SRVRecord r }.toSet shouldEqual Set(
SRVRecord("service.tcp.akka.test", 86400, 10, 60, 5060, "a-single.akka.test"),
SRVRecord("service.tcp.akka.test", 86400, 10, 40, 5070, "a-double.akka.test")
SRVRecord("service.tcp.foo.test", 86400, 10, 60, 5060, "a-single.foo.test"),
SRVRecord("service.tcp.foo.test", 86400, 10, 40, 5070, "a-double.foo.test")
)
}
"resolve same address twice" in {
resolve("a-single.akka.test").records.map(_.asInstanceOf[ARecord].ip) shouldEqual Seq(InetAddress.getByName("192.168.1.20"))
resolve("a-single.akka.test").records.map(_.asInstanceOf[ARecord].ip) shouldEqual Seq(InetAddress.getByName("192.168.1.20"))
resolve("a-single.foo.test").records.map(_.asInstanceOf[ARecord].ip) shouldEqual Seq(InetAddress.getByName("192.168.1.20"))
resolve("a-single.foo.test").records.map(_.asInstanceOf[ARecord].ip) shouldEqual Seq(InetAddress.getByName("192.168.1.20"))
}
"handle nonexistent domains" in {
val answer = (IO(Dns) ? DnsProtocol.Resolve("nonexistent.akka.test")).mapTo[DnsProtocol.Resolved].futureValue
val answer = (IO(Dns) ? DnsProtocol.Resolve("nonexistent.foo.test")).mapTo[DnsProtocol.Resolved].futureValue
answer.records shouldEqual List.empty
}
"resolve queries that are too big for UDP" in {
val name = "many.foo.test"
val answer = resolve(name)
answer.name shouldEqual name
answer.records.length should be(48)
}
def resolve(name: String, requestType: RequestType = Ip()): DnsProtocol.Resolved = {
(IO(Dns) ? DnsProtocol.Resolve(name, requestType)).mapTo[DnsProtocol.Resolved].futureValue
}
}
}
object AsyncDnsResolverIntegrationSpec {
lazy val dockerDnsServerPort = SocketUtil.temporaryLocalPort()
}

View file

@ -0,0 +1,69 @@
/*
* Copyright (C) 2018 Lightbend Inc. <https://www.lightbend.com>
*/
package akka.io.dns
import collection.JavaConverters._
import akka.testkit.AkkaSpec
import com.spotify.docker.client.DefaultDockerClient
import com.spotify.docker.client.DockerClient.LogsParam
import com.spotify.docker.client.messages.{ ContainerConfig, HostConfig, PortBinding }
import org.scalatest.concurrent.Eventually
import scala.util.Try
import scala.util.control.NonFatal
trait DockerBindDnsService extends Eventually { self: AkkaSpec
val client = DefaultDockerClient.fromEnv().build()
val hostPort: Int
var id: Option[String] = None
def dockerAvailable() = Try(client.ping()).isSuccess
override def atStartup(): Unit = {
self.atStartup()
// https://github.com/sameersbn/docker-bind/pull/61
val image = "raboof/bind:9.11.3-20180713-nochown"
try {
client.pull(image)
} catch {
case NonFatal(_)
log.warning(s"Failed to pull docker image [$image], is docker running?")
return
}
val containerConfig = ContainerConfig.builder()
.image(image)
.env("NO_CHOWN=true")
.hostConfig(
HostConfig.builder()
.portBindings(Map(
"53/tcp" -> List(PortBinding.of("", hostPort)).asJava,
"53/udp" -> List(PortBinding.of("", hostPort)).asJava
).asJava)
.binds(HostConfig.Bind.from(new java.io.File("akka-actor-tests/src/test/bind/").getAbsolutePath).to("/data/bind").build())
.build()
)
.build()
val creation = client.createContainer(containerConfig, "akka-test-dns-" + getClass.getCanonicalName)
creation.warnings() should be(null)
id = Some(creation.id())
client.startContainer(creation.id())
eventually {
client.logs(creation.id(), LogsParam.stderr()).readFully() should include("all zones loaded")
}
}
override def afterTermination(): Unit = {
self.afterTermination()
id.foreach(client.killContainer)
id.foreach(client.removeContainer)
}
}

View file

@ -0,0 +1,77 @@
/*
* Copyright (C) 2018 Lightbend Inc. <https://www.lightbend.com>
*/
package akka.io.dns.internal
import java.net.InetSocketAddress
import java.util.concurrent.atomic.AtomicBoolean
import scala.collection.immutable.Seq
import akka.actor.Props
import akka.io.Udp
import akka.io.dns.{ RecordClass, RecordType }
import akka.io.dns.internal.DnsClient.{ Answer, Question4 }
import akka.testkit.{ AkkaSpec, ImplicitSender, TestProbe }
class DnsClientSpec extends AkkaSpec with ImplicitSender {
"The async DNS client" should {
val exampleRequest = Question4(42, "akka.io")
val exampleRequestMessage =
Message(42, MessageFlags(), questions = Seq(Question("akka.io", RecordType.A, RecordClass.IN)))
val exampleResponseMessage = Message(42, MessageFlags(answer = true))
val exampleResponse = Answer(42, Nil)
val dnsServerAddress = InetSocketAddress.createUnresolved("foo", 53)
val localAddress = InetSocketAddress.createUnresolved("localhost", 13441)
"not connect to the DNS server over TCP eagerly" in {
val udpExtensionProbe = TestProbe()
val tcpClientCreated = new AtomicBoolean(false)
val client = system.actorOf(Props(new DnsClient(dnsServerAddress) {
override val udp = udpExtensionProbe.ref
override def createTcpClient = {
tcpClientCreated.set(true)
TestProbe().ref
}
}))
client ! exampleRequest
udpExtensionProbe.expectMsgType[Udp.Bind]
udpExtensionProbe.lastSender ! Udp.Bound(InetSocketAddress.createUnresolved("localhost", 41325))
expectMsgType[Udp.Send]
client ! Udp.Received(exampleResponseMessage.write(), dnsServerAddress)
expectMsg(exampleResponse)
tcpClientCreated.get() should be(false)
}
"Fall back to TCP when the UDP response is truncated" in {
val udpExtensionProbe = TestProbe()
val tcpClientProbe = TestProbe()
val client = system.actorOf(Props(new DnsClient(dnsServerAddress) {
override val udp = udpExtensionProbe.ref
override def createTcpClient = tcpClientProbe.ref
}))
client ! exampleRequest
udpExtensionProbe.expectMsgType[Udp.Bind]
udpExtensionProbe.lastSender ! Udp.Bound(InetSocketAddress.createUnresolved("localhost", 41325))
expectMsgType[Udp.Send]
client ! Udp.Received(Message(exampleRequest.id, MessageFlags(truncated = true)).write(), dnsServerAddress)
tcpClientProbe.expectMsg(exampleRequestMessage)
tcpClientProbe.reply(exampleResponse)
expectMsg(exampleResponse)
}
}
}

View file

@ -0,0 +1,101 @@
/*
* Copyright (C) 2018 Lightbend Inc. <https://www.lightbend.com>
*/
package akka.io.dns.internal
import java.net.InetSocketAddress
import akka.actor.{ Props, Terminated }
import akka.io.Tcp
import akka.io.Tcp.{ CommandFailed, Connected, PeerClosed, Register }
import akka.io.dns.{ RecordClass, RecordType }
import akka.io.dns.internal.DnsClient.Answer
import akka.testkit.{ AkkaSpec, ImplicitSender, TestProbe }
import scala.collection.immutable.Seq
class TcpDnsClientSpec extends AkkaSpec with ImplicitSender {
import TcpDnsClient._
"The async TCP DNS client" should {
val exampleRequestMessage =
Message(42, MessageFlags(), questions = Seq(Question("akka.io", RecordType.A, RecordClass.IN)))
val exampleResponseMessage = Message(42, MessageFlags(answer = true))
val dnsServerAddress = InetSocketAddress.createUnresolved("foo", 53)
val localAddress = InetSocketAddress.createUnresolved("localhost", 13441)
"reconnect when the server closes the connection" in {
val tcpExtensionProbe = TestProbe()
val answerProbe = TestProbe()
val client = system.actorOf(Props(new TcpDnsClient(tcpExtensionProbe.ref, dnsServerAddress, answerProbe.ref)))
client ! exampleRequestMessage
tcpExtensionProbe.expectMsg(Tcp.Connect(dnsServerAddress))
tcpExtensionProbe.lastSender ! Connected(dnsServerAddress, localAddress)
expectMsgType[Register]
val registered = tcpExtensionProbe.lastSender
expectMsgType[Tcp.Write]
registered ! Tcp.Received(encodeLength(exampleResponseMessage.write().length) ++ exampleResponseMessage.write())
answerProbe.expectMsg(Answer(42, Nil))
// When a new request arrived after the connection is closed
registered ! PeerClosed
client ! exampleRequestMessage
// Expect a reconnect
tcpExtensionProbe.expectMsg(Tcp.Connect(dnsServerAddress))
}
"accept a fragmented TCP response" in {
val tcpExtensionProbe = TestProbe()
val answerProbe = TestProbe()
val client = system.actorOf(Props(new TcpDnsClient(tcpExtensionProbe.ref, dnsServerAddress, answerProbe.ref)))
client ! exampleRequestMessage
tcpExtensionProbe.expectMsg(Tcp.Connect(dnsServerAddress))
tcpExtensionProbe.lastSender ! Connected(dnsServerAddress, localAddress)
expectMsgType[Register]
val registered = tcpExtensionProbe.lastSender
expectMsgType[Tcp.Write]
val fullResponse = encodeLength(exampleResponseMessage.write().length) ++ exampleResponseMessage.write()
registered ! Tcp.Received(fullResponse.take(8))
registered ! Tcp.Received(fullResponse.drop(8))
answerProbe.expectMsg(Answer(42, Nil))
}
"accept merged TCP responses" in {
val tcpExtensionProbe = TestProbe()
val answerProbe = TestProbe()
val client = system.actorOf(Props(new TcpDnsClient(tcpExtensionProbe.ref, dnsServerAddress, answerProbe.ref)))
client ! exampleRequestMessage
client ! exampleRequestMessage.copy(id = 43)
tcpExtensionProbe.expectMsg(Tcp.Connect(dnsServerAddress))
tcpExtensionProbe.lastSender ! Connected(dnsServerAddress, localAddress)
expectMsgType[Register]
val registered = tcpExtensionProbe.lastSender
expectMsgType[Tcp.Write]
expectMsgType[Tcp.Write]
val fullResponse =
encodeLength(exampleResponseMessage.write().length) ++ exampleResponseMessage.write() ++
encodeLength(exampleResponseMessage.write().length) ++ exampleResponseMessage.copy(id = 43).write()
registered ! Tcp.Received(fullResponse.take(8))
registered ! Tcp.Received(fullResponse.drop(8))
answerProbe.expectMsg(Answer(42, Nil))
answerProbe.expectMsg(Answer(43, Nil))
}
}
}