Complete async DNS that uses the name servers top to botom
This commit is contained in:
parent
d623593bc2
commit
2dad7a4c08
30 changed files with 1375 additions and 414 deletions
|
|
@ -0,0 +1,199 @@
|
|||
/*
|
||||
* Copyright (C) 2018 Lightbend Inc. <https://www.lightbend.com>
|
||||
*/
|
||||
|
||||
package akka.io.dns
|
||||
|
||||
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.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
|
||||
|
||||
*/
|
||||
class AsyncDnsResolverIntegrationSpec extends AkkaSpec(
|
||||
"""
|
||||
akka.loglevel = DEBUG
|
||||
akka.io.dns.resolver = async-dns
|
||||
akka.io.dns.async-dns.nameservers = [localhost]
|
||||
// akka.io.dns.async-dns.nameservers = default
|
||||
""") {
|
||||
val duration = 10.seconds
|
||||
implicit val timeout = Timeout(duration)
|
||||
|
||||
"Resolver" must {
|
||||
pending
|
||||
"resolve single A record" in {
|
||||
val name = "a-single.akka.test"
|
||||
val answer = resolve(name, DnsProtocol.Ip(ipv6 = false))
|
||||
withClue(answer) {
|
||||
answer.name shouldEqual name
|
||||
answer.results.size shouldEqual 1
|
||||
answer.results.head.name shouldEqual name
|
||||
answer.results.head.asInstanceOf[ARecord].ip shouldEqual InetAddress.getByName("192.168.1.20")
|
||||
}
|
||||
}
|
||||
|
||||
"resolve double A records" in {
|
||||
val name = "a-double.akka.test"
|
||||
val answer = resolve(name)
|
||||
answer.name shouldEqual name
|
||||
answer.results.map(_.asInstanceOf[ARecord].ip).toSet shouldEqual Set(
|
||||
InetAddress.getByName("192.168.1.21"),
|
||||
InetAddress.getByName("192.168.1.22")
|
||||
)
|
||||
}
|
||||
|
||||
"resolve single AAAA record" in {
|
||||
val name = "aaaa-single.akka.test"
|
||||
val answer = resolve(name)
|
||||
answer.name shouldEqual name
|
||||
answer.results.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 answer = resolve(name)
|
||||
answer.name shouldEqual name
|
||||
answer.results.map(_.asInstanceOf[AAAARecord].ip).toSet shouldEqual Set(
|
||||
InetAddress.getByName("fd4d:36b2:3eca:a2d8:0:0:0:2"),
|
||||
InetAddress.getByName("fd4d:36b2:3eca:a2d8:0:0:0:3")
|
||||
)
|
||||
}
|
||||
|
||||
"resolve mixed A/AAAA records" in {
|
||||
val name = "a-aaaa.akka.test"
|
||||
val answer = resolve(name)
|
||||
answer.name shouldEqual name
|
||||
|
||||
answer.results.collect { case r: ARecord ⇒ r.ip }.toSet shouldEqual Set(
|
||||
InetAddress.getByName("192.168.1.23"),
|
||||
InetAddress.getByName("192.168.1.24")
|
||||
)
|
||||
|
||||
answer.results.collect { case r: AAAARecord ⇒ r.ip }.toSet shouldEqual Set(
|
||||
InetAddress.getByName("fd4d:36b2:3eca:a2d8:0:0:0:4"),
|
||||
InetAddress.getByName("fd4d:36b2:3eca:a2d8:0:0:0:5")
|
||||
)
|
||||
}
|
||||
|
||||
"resolve external CNAME record" in {
|
||||
val name = "cname-ext.akka.test"
|
||||
val answer = (IO(Dns) ? DnsProtocol.Resolve(name)).mapTo[DnsProtocol.Resolved].futureValue
|
||||
answer.name shouldEqual name
|
||||
answer.results.collect { case r: CNameRecord ⇒ r.canonicalName }.toSet shouldEqual Set(
|
||||
"a-single.akka.test2"
|
||||
)
|
||||
answer.results.collect { case r: ARecord ⇒ r.ip }.toSet shouldEqual Set(
|
||||
InetAddress.getByName("192.168.2.20")
|
||||
)
|
||||
}
|
||||
|
||||
"resolve internal CNAME record" in {
|
||||
val name = "cname-in.akka.test"
|
||||
val answer = resolve(name)
|
||||
answer.name shouldEqual name
|
||||
answer.results.collect { case r: CNameRecord ⇒ r.canonicalName }.toSet shouldEqual Set(
|
||||
"a-double.akka.test"
|
||||
)
|
||||
answer.results.collect { case r: ARecord ⇒ r.ip }.toSet shouldEqual Set(
|
||||
InetAddress.getByName("192.168.1.21"),
|
||||
InetAddress.getByName("192.168.1.22")
|
||||
)
|
||||
}
|
||||
|
||||
"resolve SRV record" in {
|
||||
val name = "service.tcp.akka.test"
|
||||
val answer = resolve("service.tcp.akka.test", Srv)
|
||||
|
||||
answer.name shouldEqual name
|
||||
answer.results.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")
|
||||
)
|
||||
}
|
||||
|
||||
"resolve same address twice" in {
|
||||
resolve("a-single.akka.test").results.map(_.asInstanceOf[ARecord].ip) shouldEqual Seq(InetAddress.getByName("192.168.1.20"))
|
||||
resolve("a-single.akka.test").results.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
|
||||
answer.results shouldEqual List.empty
|
||||
}
|
||||
|
||||
def resolve(name: String, requestType: RequestType = Ip()) = {
|
||||
(IO(Dns) ? DnsProtocol.Resolve(name, requestType)).mapTo[DnsProtocol.Resolved].futureValue
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
/*
|
||||
* Copyright (C) 2018 Lightbend Inc. <https://www.lightbend.com>
|
||||
*/
|
||||
|
||||
package akka.io.dns
|
||||
|
||||
import java.net.InetSocketAddress
|
||||
|
||||
import org.scalatest.{ Matchers, WordSpec }
|
||||
|
||||
class NameserverAddressParserSpec extends WordSpec with Matchers {
|
||||
"Parser" should {
|
||||
"handle explicit port in IPv4 address" in {
|
||||
DnsSettings.parseNameserverAddress("8.8.8.8:153") shouldEqual new InetSocketAddress("8.8.8.8", 153)
|
||||
}
|
||||
"handle explicit port in IPv6 address" in {
|
||||
DnsSettings.parseNameserverAddress("[2001:4860:4860::8888]:153") shouldEqual new InetSocketAddress("2001:4860:4860::8888", 153)
|
||||
}
|
||||
"handle default port in IPv4 address" in {
|
||||
DnsSettings.parseNameserverAddress("8.8.8.8") shouldEqual new InetSocketAddress("8.8.8.8", 53)
|
||||
}
|
||||
"handle default port in IPv6 address" in {
|
||||
DnsSettings.parseNameserverAddress("[2001:4860:4860::8888]") shouldEqual new InetSocketAddress("2001:4860:4860::8888", 53)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,108 @@
|
|||
/*
|
||||
* Copyright (C) 2018 Lightbend Inc. <https://www.lightbend.com>
|
||||
*/
|
||||
|
||||
package akka.io.dns.internal
|
||||
|
||||
import java.net.{ Inet6Address, InetAddress }
|
||||
|
||||
import akka.actor.Status.Failure
|
||||
import akka.actor.{ ActorRef, ExtendedActorSystem, Props }
|
||||
import akka.io.dns.{ AAAARecord, ARecord, DnsSettings }
|
||||
import akka.testkit.{ AkkaSpec, ImplicitSender, TestProbe }
|
||||
import com.typesafe.config.ConfigFactory
|
||||
import akka.io.dns.DnsProtocol._
|
||||
import akka.io.dns.internal.AsyncDnsResolver.ResolveFailedException
|
||||
import akka.io.dns.internal.DnsClient.{ Answer, Question4, Question6, SrvQuestion }
|
||||
|
||||
import scala.collection.immutable
|
||||
|
||||
class AsyncDnsResolverSpec extends AkkaSpec(
|
||||
"""
|
||||
akka.loglevel = INFO
|
||||
""") with ImplicitSender {
|
||||
|
||||
"Async DNS Resolver" must {
|
||||
|
||||
"use dns clients in order" in {
|
||||
val dnsClient1 = TestProbe()
|
||||
val dnsClient2 = TestProbe()
|
||||
val r = resolver(List(dnsClient1.ref, dnsClient2.ref))
|
||||
r ! Resolve("cats.com", Ip(ipv4 = true, ipv6 = false))
|
||||
dnsClient1.expectMsg(Question4(1, "cats.com"))
|
||||
dnsClient1.reply(Answer(1, immutable.Seq()))
|
||||
dnsClient2.expectNoMessage()
|
||||
expectMsg(Resolved("cats.com", immutable.Seq()))
|
||||
}
|
||||
|
||||
"move to next client if first fails" in {
|
||||
val dnsClient1 = TestProbe()
|
||||
val dnsClient2 = TestProbe()
|
||||
val r = resolver(List(dnsClient1.ref, dnsClient2.ref))
|
||||
r ! Resolve("cats.com", Ip(ipv4 = true, ipv6 = false))
|
||||
// first will get ask timeout
|
||||
dnsClient1.expectMsg(Question4(1, "cats.com"))
|
||||
dnsClient1.reply(Failure(new RuntimeException("Nope")))
|
||||
dnsClient2.expectMsg(Question4(2, "cats.com"))
|
||||
dnsClient2.reply(Answer(2, immutable.Seq()))
|
||||
expectMsg(Resolved("cats.com", immutable.Seq()))
|
||||
}
|
||||
|
||||
"move to next client if first times out" in {
|
||||
val dnsClient1 = TestProbe()
|
||||
val dnsClient2 = TestProbe()
|
||||
val r = resolver(List(dnsClient1.ref, dnsClient2.ref))
|
||||
r ! Resolve("cats.com", Ip(ipv4 = true, ipv6 = false))
|
||||
// first will get ask timeout
|
||||
dnsClient1.expectMsg(Question4(1, "cats.com"))
|
||||
dnsClient2.expectMsg(Question4(2, "cats.com"))
|
||||
dnsClient2.reply(Answer(2, immutable.Seq()))
|
||||
expectMsg(Resolved("cats.com", immutable.Seq()))
|
||||
}
|
||||
|
||||
"gets both A and AAAA records if requested" in {
|
||||
val dnsClient1 = TestProbe()
|
||||
val r = resolver(List(dnsClient1.ref))
|
||||
r ! Resolve("cats.com", Ip(ipv4 = true, ipv6 = true))
|
||||
dnsClient1.expectMsg(Question4(1, "cats.com"))
|
||||
val ipv4Record = ARecord("cats.com", 100, InetAddress.getByName("127.0.0.1"))
|
||||
dnsClient1.reply(Answer(1, immutable.Seq(ipv4Record)))
|
||||
dnsClient1.expectMsg(Question6(2, "cats.com"))
|
||||
val ipv6Record = AAAARecord("cats.com", 100, InetAddress.getByName("::1").asInstanceOf[Inet6Address])
|
||||
dnsClient1.reply(Answer(2, immutable.Seq(ipv6Record)))
|
||||
expectMsg(Resolved("cats.com", immutable.Seq(ipv4Record, ipv6Record)))
|
||||
}
|
||||
|
||||
"fails if all dns clients timeout" in {
|
||||
val dnsClient1 = TestProbe()
|
||||
val dnsClient2 = TestProbe()
|
||||
val r = resolver(List(dnsClient1.ref, dnsClient2.ref))
|
||||
r ! Resolve("cats.com", Ip(ipv4 = true, ipv6 = false))
|
||||
expectMsgPF(remainingOrDefault) {
|
||||
case Failure(ResolveFailedException(_)) ⇒
|
||||
}
|
||||
}
|
||||
|
||||
"gets SRV records if requested" in {
|
||||
val dnsClient1 = TestProbe()
|
||||
val dnsClient2 = TestProbe()
|
||||
val r = resolver(List(dnsClient1.ref, dnsClient2.ref))
|
||||
r ! Resolve("cats.com", Srv)
|
||||
dnsClient1.expectMsg(SrvQuestion(1, "cats.com"))
|
||||
dnsClient1.reply(Answer(1, immutable.Seq()))
|
||||
dnsClient2.expectNoMessage()
|
||||
expectMsg(Resolved("cats.com", immutable.Seq()))
|
||||
}
|
||||
}
|
||||
|
||||
def resolver(clients: List[ActorRef]): ActorRef = {
|
||||
val settings = new DnsSettings(system.asInstanceOf[ExtendedActorSystem], ConfigFactory.parseString(
|
||||
"""
|
||||
nameservers = ["one","two"]
|
||||
resolve-timeout = 25ms
|
||||
"""))
|
||||
system.actorOf(Props(new AsyncDnsResolver(settings, new AsyncDnsCache(), (arf, l) ⇒ {
|
||||
clients
|
||||
})))
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue