diff --git a/examples/benchmark.php b/examples/benchmark.php index 2f40069..6289443 100644 --- a/examples/benchmark.php +++ b/examples/benchmark.php @@ -17,7 +17,7 @@ $domains = \array_map(function ($line) { \array_shift($domains); // Set default resolver to DNS-over-HTTPS resolver -$DohConfig = new DoH\DoHConfig([new DoH\Nameserver('https://cloudflare-dns.com/dns-query')]); +$DohConfig = new DoH\DoHConfig([new DoH\Nameserver('https://mozilla.cloudflare-dns.com/dns-query')]); Dns\resolver(new DoH\Rfc8484StubResolver($DohConfig)); Loop::run(function () use ($domains) { diff --git a/examples/custom-config.php b/examples/custom-config.php index 24bdd8f..689a9fd 100644 --- a/examples/custom-config.php +++ b/examples/custom-config.php @@ -24,7 +24,7 @@ $customConfigLoader = new class implements Dns\ConfigLoader { }; // Set default resolver to DNS-over-https resolver -$DohConfig = new DoH\DoHConfig([new DoH\Nameserver('https://cloudflare-dns.com/dns-query')], null, null, $customConfigLoader); +$DohConfig = new DoH\DoHConfig([new DoH\Nameserver('https://mozilla.cloudflare-dns.com/dns-query')], null, null, $customConfigLoader); Dns\resolver(new DoH\Rfc8484StubResolver($DohConfig)); Loop::run(function () { diff --git a/examples/domain-fronting.php b/examples/domain-fronting.php new file mode 100644 index 0000000..042751e --- /dev/null +++ b/examples/domain-fronting.php @@ -0,0 +1,23 @@ + "dns.google.com"])]); +Dns\resolver(new DoH\Rfc8484StubResolver($DohConfig)); + +Loop::run(function () { + $hostname = "amphp.org"; + + try { + pretty_print_records($hostname, yield Dns\resolve($hostname)); + } catch (Dns\DnsException $e) { + pretty_print_error($hostname, $e); + } +}); + diff --git a/examples/ptr-record.php b/examples/ptr-record.php index 673ad9b..764be39 100644 --- a/examples/ptr-record.php +++ b/examples/ptr-record.php @@ -7,7 +7,7 @@ use Amp\DoH; use Amp\Loop; // Set default resolver to DNS-over-HTTPS resolver -$DohConfig = new DoH\DoHConfig([new DoH\Nameserver('https://cloudflare-dns.com/dns-query')]); +$DohConfig = new DoH\DoHConfig([new DoH\Nameserver('https://mozilla.cloudflare-dns.com/dns-query')]); Dns\resolver(new DoH\Rfc8484StubResolver($DohConfig)); Loop::run(function () { diff --git a/lib/DoHConfig.php b/lib/DoHConfig.php index 0f785fa..051d784 100644 --- a/lib/DoHConfig.php +++ b/lib/DoHConfig.php @@ -11,6 +11,7 @@ use Amp\Dns\Resolver; use Amp\Dns\UnixConfigLoader; use Amp\Dns\WindowsConfigLoader; use Amp\Dns\Rfc1035StubResolver; +use Amp\Dns\ConfigException; final class DoHConfig { @@ -29,10 +30,12 @@ final class DoHConfig foreach ($nameservers as $nameserver) { $this->validateNameserver($nameserver); } + if ($resolver instanceof Rfc8484StubResolver) { + throw new ConfigException("Can't use Rfc8484StubResolver as subresolver for Rfc8484StubResolver"); + } $this->nameservers = $nameservers; $this->artax = $artax ?? new DefaultClient(); - $this->cache = $cache ?? new ArrayCache(5000/* default gc interval */, 256/* size */); $this->configLoader = $configLoader ?? (\stripos(PHP_OS, "win") === 0 ? new WindowsConfigLoader diff --git a/lib/Internal/HttpsSocket.php b/lib/Internal/HttpsSocket.php index 4ca6ded..13dc7c3 100644 --- a/lib/Internal/HttpsSocket.php +++ b/lib/Internal/HttpsSocket.php @@ -62,7 +62,7 @@ final class HttpsSocket extends Socket switch ($this->nameserver->getType()) { case Nameserver::RFC8484_GET: $data = $this->encoder->encode($message); - $request = (new Request($this->nameserver->getUri().'?'.http_build_query(['dns' => base64_encode($data), 'ct' => 'application/dns-message']), "GET")) + $request = (new Request($this->nameserver->getUri().'?'.\http_build_query(['dns' => \base64_encode($data), 'ct' => 'application/dns-message']), "GET")) ->withHeader('accept', 'application/dns-message') ->withHeaders($this->nameserver->getHeaders()); break; diff --git a/lib/JsonDecoder.php b/lib/JsonDecoder.php index c2c0d9d..5da66a6 100644 --- a/lib/JsonDecoder.php +++ b/lib/JsonDecoder.php @@ -23,6 +23,7 @@ use LibDNS\Records\Types\Type; use LibDNS\Records\Types\TypeBuilder; use LibDNS\Records\Types\Types; use LibDNS\Messages\MessageTypes; +use Amp\Dns\DnsException; /** * Decodes JSON DNS strings to Message objects @@ -314,7 +315,15 @@ class JsonDecoder */ public function decode(string $result, int $requestId): Message { - $result = json_decode($result, true); + $result = \json_decode($result, true); + if ($result === false) { + $error = \json_last_error_msg(); + throw new DnsException("Could not decode JSON DNS payload ($error)"); + } + if (!isset($result['Status'], $result['TC'], $result['RD'], $result['RA'])) { + throw new DnsException('Wrong reply from server, missing required fields'); + } + $message = $this->messageFactory->create(); $decodingContext = $this->decodingContextFactory->create($this->packetFactory->create()); diff --git a/lib/Nameserver.php b/lib/Nameserver.php index d948ccd..f8abdf6 100644 --- a/lib/Nameserver.php +++ b/lib/Nameserver.php @@ -2,6 +2,9 @@ namespace Amp\DoH; +use Amp\Uri\InvalidDnsNameException; +use Amp\Dns\ConfigException; + final class Nameserver { const RFC8484_GET = 0; @@ -15,6 +18,12 @@ final class Nameserver public function __construct(string $uri, int $type = self::RFC8484_POST, array $headers = []) { + if (parse_url($uri, PHP_URL_SCHEME) !== 'https') { + throw new ConfigException('Did not provide a valid HTTPS url!'); + } + if (!in_array($type, [self::RFC8484_GET, self::RFC8484_POST, self::GOOGLE_JSON], true)) { + throw new ConfigException('Invalid nameserver type provided!'); + } $this->uri = $uri; $this->type = $type; $this->headers = $headers; diff --git a/lib/QueryEncoder.php b/lib/QueryEncoder.php index 8eba12e..cf6a8d9 100644 --- a/lib/QueryEncoder.php +++ b/lib/QueryEncoder.php @@ -1,4 +1,4 @@ - 0, // Do not disable result validation - 'do' => 0, // Do not send me DNSSEC data - 'type' => $message->getQuestionRecords()->getRecordByIndex(0)->getType(), // Record type being requested - 'name' => implode('.', $message->getQuestionRecords()->getRecordByIndex(0)->getName()->getLabels()), // Record name being requested - 'ct' => 'application/dns-json', // Content-type of request - ]); + if ($message->getType() !== MessageTypes::QUERY) { + throw new DnsException('Invalid question: is not a question record'); + } + $questions = $message->getQuestionRecords(); + if ($questions->count() === 0) { + throw new DnsException('Invalid question: 0 question records provided'); + } + $question = $questions->getRecordByIndex(0); + return \http_build_query( + [ + 'cd' => 0, // Do not disable result validation + 'do' => 0, // Do not send me DNSSEC data + 'type' => $question->getType(), // Record type being requested + 'name' => implode('.', $question->getName()->getLabels()), // Record name being requested + 'ct' => 'application/dns-json', // Content-type of request + ] + ); } } diff --git a/test/DecodeTest.php b/test/DecodeTest.php deleted file mode 100644 index c5b4369..0000000 --- a/test/DecodeTest.php +++ /dev/null @@ -1,23 +0,0 @@ -create(); - $response = $decoder->decode($message); - - $this->assertInstanceOf(Message::class, $response); - } -} diff --git a/test/DoHConfigTest.php b/test/DoHConfigTest.php new file mode 100644 index 0000000..48e3e27 --- /dev/null +++ b/test/DoHConfigTest.php @@ -0,0 +1,164 @@ +assertInstanceOf(DoHConfig::class, new DoHConfig($nameservers)); + } + + public function provideValidServers() + { + return [ + [[new Nameserver('https://cloudflare-dns.com/dns-query')]], + [[new Nameserver('https://cloudflare-dns.com/dns-query', Nameserver::RFC8484_POST)]], + [[new Nameserver('https://cloudflare-dns.com/dns-query', Nameserver::RFC8484_GET)]], + [[new Nameserver('https://cloudflare-dns.com/dns-query', Nameserver::GOOGLE_JSON)]], + [[new Nameserver('https://google.com/resolve', Nameserver::GOOGLE_JSON, ["host" => "dns.google.com"])]], + [[new Nameserver('https://cloudflare-dns.com/dns-query', Nameserver::GOOGLE_JSON), new Nameserver('https://google.com/resolve', Nameserver::GOOGLE_JSON, ["host" => "dns.google.com"])]], + ]; + } + + /** + * @param string[] $nameservers Invalid server array. + * + * @dataProvider provideInvalidServers + */ + public function testRejectsInvalidServers(array $nameservers) + { + $this->expectException(ConfigException::class); + new DoHConfig($nameservers); + } + + public function provideInvalidServers() + { + return [ + [[]], + [[42]], + [[null]], + [[true]], + [["foobar"]], + [["foobar.com"]], + [["127.1.1:53"]], + [["127.1.1"]], + [["127.1.1.1.1"]], + [["126.0.0.5", "foobar"]], + [["42"]], + [["::1"]], + [["::1:53"]], + [["[::1]:53"]], + [["[::1]:"]], + [["[::1]:76235"]], + [["[::1]:0"]], + [["[::1]:-1"]], + [["[::1:51"]], + [["[::1]:abc"]], + ]; + } + + /** + * @param \Amp\Artax\Client $client Valid artax instance + * + * @dataProvider provideValidArtax + */ + public function testAcceptsValidArtax($client) + { + $this->assertInstanceOf(DoHConfig::class, new DoHConfig([new Nameserver('https://cloudflare-dns.com/dns-query')], $client)); + } + + public function provideValidArtax() + { + return [ + [new DefaultClient()] + [null] + ]; + } + /** + * @param \Amp\Dns\Resolver $resolver Valid resolver instance + * + * @dataProvider provideValidResolver + */ + public function testAcceptsValidResolver($resolver) + { + $this->assertInstanceOf(DoHConfig::class, new DoHConfig([new Nameserver('https://cloudflare-dns.com/dns-query')], null, $resolver)); + } + + public function provideValidResolver() + { + return [ + [new Rfc1035StubResolver()] + [null] + ]; + } + /** + * @param $resolver any invalid resolver instance + * + * @dataProvider provideInvalidResolver + */ + public function testRejectsInvalidResolver($resolver) + { + $this->expectException(ConfigException::class); + new DoHConfig([new Nameserver('https://cloudflare-dns.com/dns-query')], null, $resolver); + } + + public function provideInvalidResolver() + { + return [ + [new Rfc8484StubResolver(new DoHConfig([new Nameserver('https://mozilla.cloudflare-dns.com/dns-query')]))] + ]; + } + /** + * @param $configLoader Valid ConfigLoader instance + * + * @dataProvider provideValidConfigLoader + */ + public function testAcceptsValidConfigLoader($configLoader) + { + $this->assertInstanceOf(DoHConfig::class, new DoHConfig([new Nameserver('https://cloudflare-dns.com/dns-query')], null, null, $configLoader)); + } + + public function provideValidConfigLoader() + { + return [ + [new WindowsConfigLoader()] + [new UnixConfigLoader()] + [null] + ]; + } + /** + * @param \Amp\Cache\Cache Valid cache instance + * + * @dataProvider provideValidCache + */ + public function testAcceptsValidCache($cache) + { + $this->assertInstanceOf(DoHConfig::class, new DoHConfig([new Nameserver('https://cloudflare-dns.com/dns-query')], null, null, null, $cache)); + } + + public function provideValidCache() + { + return [ + [new ArrayCache(5000/* default gc interval */, 256/* size */)] + [null] + ]; + } +} diff --git a/test/HostLoaderTest.php b/test/HostLoaderTest.php deleted file mode 100644 index f273f4a..0000000 --- a/test/HostLoaderTest.php +++ /dev/null @@ -1,43 +0,0 @@ -assertSame([ - Record::A => [ - "localhost" => "127.0.0.1", - ], - ], yield $loader->loadHosts()); - }); - } - - public function testReturnsEmptyErrorOnFileNotFound() - { - Loop::run(function () { - $loader = new HostLoader(__DIR__ . "/data/hosts.not.found"); - $this->assertSame([], yield $loader->loadHosts()); - }); - } - - public function testIgnoresInvalidNames() - { - Loop::run(function () { - $loader = new HostLoader(__DIR__ . "/data/hosts.invalid.name"); - $this->assertSame([ - Record::A => [ - "localhost" => "127.0.0.1", - ], - ], yield $loader->loadHosts()); - }); - } -} diff --git a/test/JsonDecoderFactoryTest.php b/test/JsonDecoderFactoryTest.php new file mode 100644 index 0000000..0f2eb6c --- /dev/null +++ b/test/JsonDecoderFactoryTest.php @@ -0,0 +1,15 @@ +assertInstanceOf(JsonDecoder::class, (new JsonDecoderFactory)->create()); + } +} \ No newline at end of file diff --git a/test/JsonDecoderTest.php b/test/JsonDecoderTest.php new file mode 100644 index 0000000..fc2a71f --- /dev/null +++ b/test/JsonDecoderTest.php @@ -0,0 +1,201 @@ +create(); + $response = $decoder->decode($message, $requestId); + + $this->assertInstanceOf(Message::class, $response); + $this->assertEquals(MessageTypes::RESPONSE, $response->getType()); + } + + public function provideValidJsonPayloads() + { + return [ + [ + '{ + "Status": 0, + "TC": false, + "RD": true, + "RA": true, + "AD": false, + "CD": false, + "Question": + [ + { + "name": "apple.com.", + "type": 1 + } + ], + "Answer": + [ + { + "name": "apple.com.", + "type": 1, + "TTL": 3599, + "data": "17.178.96.59" + }, + { + "name": "apple.com.", + "type": 1, + "TTL": 3599, + "data": "17.172.224.47" + }, + { + "name": "apple.com.", + "type": 1, + "TTL": 3599, + "data": "17.142.160.59" + } + ], + "Additional": [ ], + "edns_client_subnet": "12.34.56.78/0" + }', + 2, + ], + [ + '{"Status": 0,"TC": false,"RD": true, "RA": true, "AD": true,"CD": false,"Question":[{"name": "example.com.", "type": 28}],"Answer":[{"name": "example.com.", "type": 28, "TTL": 7092, "data": "2606:2800:220:1:248:1893:25c8:1946"}]}', + 3, + ], + [ + '{"Status": 0,"TC": false,"RD": true, "RA": true, "AD": false,"CD": false,"Question":[{"name": "daniil.it.", "type": 1}],"Answer":[{"name": "daniil.it.", "type": 1, "TTL": 300, "data": "104.27.146.166"},{"name": "daniil.it.", "type": 1, "TTL": 300, "data": "104.27.147.166"}]}', + 3, + ], + [ + '{"Status": 0,"TC": false,"RD": true, "RA": true, "AD": false,"CD": false,"Question":[{"name": "amphp.org.", "type": 15}],"Answer":[{"name": "amphp.org.", "type": 15, "TTL": 86400, "data": "0 mail.negativeion.net."}]}', + 3, + ], + ]; + } + + + /** + * Test decoding of invalid JSON DNS payloads + * + * @param string $message + * @param int $requestId + * @return void + * + * @dataProvider provideInvalidJsonPayloads + */ + public function testDecodesInvalidJsonPayloads($message, $requestId) + { + $decoder = (new JsonDecoderFactory)->create(); + $this->expectException(DnsException::class); + $decoder->decode($message, $requestId); + } + + public function provideInvalidJsonPayloads() + { + return [ + [ + '{lmfao + "Status": 0, + "TC": false, + "RD": true, + "RA": true, + "AD": false, + "CD": false, + "Question": + [ + { + "name": "apple.com.", + "type": 1 + } + ], + "Answer": + [ + { + "name": "apple.com.", + "type": 1, + "TTL": 3599, + "data": "17.178.96.59" + }, + { + "name": "apple.com.", + "type": 1, + "TTL": 3599, + "data": "17.172.224.47" + }, + { + "name": "apple.com.", + "type": 1, + "TTL": 3599, + "data": "17.142.160.59" + } + ], + "Additional": [ ], + "edns_client_subnet": "12.34.56.78/0" + }', + 2, + ], + [ + 'xd{"Status": 0,"TC": false,"RD": true, "RA": true, "AD": true,"CD": false,"Question":[{"name": "example.com.", "type": 28}],"Answer":[{"name": "example.com.", "type": 28, "TTL": 7092, "data": "2606:2800:220:1:248:1893:25c8:1946"}]}', + 3, + ], + [ + 'whaaa{"Status": 0,"TC": false,"RD": true, "RA": true, "AD": false,"CD": false,"Question":[{"name": "daniil.it.", "type": 1}],"Answer":[{"name": "daniil.it.", "type": 1, "TTL": 300, "data": "104.27.146.166"},{"name": "daniil.it.", "type": 1, "TTL": 300, "data": "104.27.147.166"}]}', + 3, + ], + [ + 'xdxdxxxxx{"Status": 0,"TC": false,"RD": true, "RA": true, "AD": false,"CD": false,"Question":[{"name": "amphp.org.", "type": 15}],"Answer":[{"name": "amphp.org.", "type": 15, "TTL": 86400, "data": "0 mail.negativeion.net."}]}', + 3, + ], + [ + '{"TC": false,"RD": true, "RA": true, "AD": false,"CD": false,"Question":[{"name": "amphp.org.", "type": 15}],"Answer":[{"name": "amphp.org.", "type": 15, "TTL": 86400, "data": "0 mail.negativeion.net."}]}', + 3, + ], + [ + '{"Status": 0,"RD": true, "RA": true, "AD": false,"CD": false,"Question":[{"name": "amphp.org.", "type": 15}],"Answer":[{"name": "amphp.org.", "type": 15, "TTL": 86400, "data": "0 mail.negativeion.net."}]}', + 3, + ], + [ + '{"Status": 0,"TC": false,"RA": true, "AD": false,"CD": false,"Question":[{"name": "amphp.org.", "type": 15}],"Answer":[{"name": "amphp.org.", "type": 15, "TTL": 86400, "data": "0 mail.negativeion.net."}]}', + 3, + ], + [ + '{"Status": 0,"TC": false,"RD": true, "AD": false,"CD": false,"Question":[{"name": "amphp.org.", "type": 15}],"Answer":[{"name": "amphp.org.", "type": 15, "TTL": 86400, "data": "0 mail.negativeion.net."}]}', + 3, + ], + [ + 'xd', + 0 + ], + [ + null, + 0 + ], + [ + [], + 0 + ], + [ + 0, + 0 + ], + [ + new class {}, + 0 + ] + ]; + } +} diff --git a/test/NameserverTest.php b/test/NameserverTest.php new file mode 100644 index 0000000..ec1d7f3 --- /dev/null +++ b/test/NameserverTest.php @@ -0,0 +1,86 @@ +assertInstanceOf(Nameserver::class, new Nameserver($nameserver, $type, $headers)); + } + + public function provideValidServers() + { + return [ + ['https://mozilla.cloudflare-dns.com/dns-query'], + ['https://mozilla.cloudflare-dns.com/dns-query', Nameserver::RFC8484_POST], + ['https://mozilla.cloudflare-dns.com/dns-query', Nameserver::RFC8484_GET], + ['https://mozilla.cloudflare-dns.com/dns-query', Nameserver::GOOGLE_JSON], + ['https://google.com/resolve', Nameserver::GOOGLE_JSON, ["Host" => "dns.google.com"]], + ]; + } + + /** + * @param string[] $nameservers Invalid server array. + * + * @dataProvider provideInvalidServers + */ + public function testRejectsInvalidServers($nameserver, $type = Nameserver::RFC8484_POST, $headers = []) + { + $this->expectException(ConfigException::class); + new Nameserver($nameserver, $type, $headers); + } + + public function provideInvalidServers() + { + return [ + [''], + [42], + [null], + [true], + ["foobar"], + ["foobar.com"], + ["127.1.1"], + ["127.1.1.1.1"], + ["126.0.0.5"], + ["42"], + ["::1"], + ["::1:53"], + ["[::1]:"], + ["[::1]:76235"], + ["[::1]:0"], + ["[::1]:-1"], + ["[::1:51"], + ["[::1]:abc"], + ['http://mozilla.cloudflare-dns.com/dns-query'], + ['http://mozilla.cloudflare-dns.com/dns-query', Nameserver::RFC8484_POST], + ['http://mozilla.cloudflare-dns.com/dns-query', Nameserver::RFC8484_GET], + ['http://mozilla.cloudflare-dns.com/dns-query', Nameserver::GOOGLE_JSON], + ['http://google.com/resolve', Nameserver::GOOGLE_JSON, ["Host" => "dns.google.com"]], + + ['mozilla.cloudflare-dns.com/dns-query'], + ['mozilla.cloudflare-dns.com/dns-query', Nameserver::RFC8484_POST], + ['mozilla.cloudflare-dns.com/dns-query', Nameserver::RFC8484_GET], + ['mozilla.cloudflare-dns.com/dns-query', Nameserver::GOOGLE_JSON], + ['google.com/resolve', Nameserver::GOOGLE_JSON, ["Host" => "dns.google.com"]], + + ['https://mozilla.cloudflare-dns.com/dns-query'], + ['https://mozilla.cloudflare-dns.com/dns-query', 100], + ['https://mozilla.cloudflare-dns.com/dns-query', "2"], + ['https://mozilla.cloudflare-dns.com/dns-query', -1], + ['https://mozilla.cloudflare-dns.com/dns-query', null], + ['https://mozilla.cloudflare-dns.com/dns-query', "hi"], + ['https://mozilla.cloudflare-dns.com/dns-query', "foobar"], + + ]; + } + +} \ No newline at end of file diff --git a/test/QueryEncoderFactoryTest.php b/test/QueryEncoderFactoryTest.php new file mode 100644 index 0000000..902cf33 --- /dev/null +++ b/test/QueryEncoderFactoryTest.php @@ -0,0 +1,15 @@ +assertInstanceOf(QueryEncoder::class, (new QueryEncoderFactory)->create()); + } +} \ No newline at end of file diff --git a/test/QueryEncoderTest.php b/test/QueryEncoderTest.php new file mode 100644 index 0000000..0ec5f64 --- /dev/null +++ b/test/QueryEncoderTest.php @@ -0,0 +1,208 @@ +create(); + $response = $decoder->decode($message, 0); + $response->setType(MessageTypes::QUERY); + + $encoder = (new QueryEncoderFactory)->create(); + $request = $encoder->encode($response); + + $this->assertInternalType('string', $request, "Got a ".gettype($request)." instead of a string"); + parse_str($request, $output); + $this->assertNotEmpty($output); + $this->assertArrayHasKey('cd', $output); + $this->assertArrayHasKey('do', $output); + $this->assertArrayHasKey('ct', $output); + $this->assertArrayHasKey('type', $output); + $this->assertArrayHasKey('name', $output); + $this->assertEquals($output['cd'], 0); + $this->assertEquals($output['do'], 0); + $this->assertEquals($output['ct'], 'application/dns-json'); + $this->assertEquals($output['type'], $response->getQuestionRecords()->getRecordByIndex(0)->getType()); + $this->assertEquals($output['name'], implode('.', $message->getQuestionRecords()->getRecordByIndex(0)->getName()->getLabels())); + } + + public function provideValidJsonPayloads() + { + return [ + [ + '{ + "Status": 0, + "TC": false, + "RD": true, + "RA": true, + "AD": false, + "CD": false, + "Question": + [ + { + "name": "apple.com.", + "type": 1 + } + ], + "Answer": + [ + { + "name": "apple.com.", + "type": 1, + "TTL": 3599, + "data": "17.178.96.59" + }, + { + "name": "apple.com.", + "type": 1, + "TTL": 3599, + "data": "17.172.224.47" + }, + { + "name": "apple.com.", + "type": 1, + "TTL": 3599, + "data": "17.142.160.59" + } + ], + "Additional": [ ], + "edns_client_subnet": "12.34.56.78/0" + }', + 2, + ], + [ + '{"Status": 0,"TC": false,"RD": true, "RA": true, "AD": true,"CD": false,"Question":[{"name": "example.com.", "type": 28}],"Answer":[{"name": "example.com.", "type": 28, "TTL": 7092, "data": "2606:2800:220:1:248:1893:25c8:1946"}]}', + 3, + ], + [ + '{"Status": 0,"TC": false,"RD": true, "RA": true, "AD": false,"CD": false,"Question":[{"name": "daniil.it.", "type": 1}],"Answer":[{"name": "daniil.it.", "type": 1, "TTL": 300, "data": "104.27.146.166"},{"name": "daniil.it.", "type": 1, "TTL": 300, "data": "104.27.147.166"}]}', + 3, + ], + [ + '{"Status": 0,"TC": false,"RD": true, "RA": true, "AD": false,"CD": false,"Question":[{"name": "amphp.org.", "type": 15}],"Answer":[{"name": "amphp.org.", "type": 15, "TTL": 86400, "data": "0 mail.negativeion.net."}]}', + 3, + ], + ]; + } + + /** + * Test query encoding of invalid DNS payloads + * + * @param $request + * @return void + * + * @dataProvider provideInvalidQueryPayloads + */ + public function testEncodesInvalidQueryPayloads($request) + { + $encoder = (new QueryEncoderFactory)->create(); + $this->expectException(DnsException::class); + $encoder->encode($request); + } + + public function provideInvalidQueryPayloads() + { + $decoder = (new JsonDecoderFactory)->create(); + return [ + [ + $decoder->decode( + '{ + "Status": 0, + "TC": false, + "RD": true, + "RA": true, + "AD": false, + "CD": false, + "Question": + [ + { + "name": "apple.com.", + "type": 1 + } + ], + "Answer": + [ + { + "name": "apple.com.", + "type": 1, + "TTL": 3599, + "data": "17.178.96.59" + }, + { + "name": "apple.com.", + "type": 1, + "TTL": 3599, + "data": "17.172.224.47" + }, + { + "name": "apple.com.", + "type": 1, + "TTL": 3599, + "data": "17.142.160.59" + } + ], + "Additional": [ ], + "edns_client_subnet": "12.34.56.78/0" + }', + 2, + ), + ], + [ + $decoder->decode( + '{ + "Status": 0, + "TC": false, + "RD": true, + "RA": true, + "AD": false, + "CD": false, + "Question": + [ + ], + "Answer": + [ + { + "name": "apple.com.", + "type": 1, + "TTL": 3599, + "data": "17.178.96.59" + }, + { + "name": "apple.com.", + "type": 1, + "TTL": 3599, + "data": "17.172.224.47" + }, + { + "name": "apple.com.", + "type": 1, + "TTL": 3599, + "data": "17.142.160.59" + } + ], + "Additional": [ ], + "edns_client_subnet": "12.34.56.78/0" + }', + 2, + ), + ], + ]; + } +} diff --git a/test/RecordTest.php b/test/RecordTest.php deleted file mode 100644 index 69f8b1f..0000000 --- a/test/RecordTest.php +++ /dev/null @@ -1,27 +0,0 @@ -assertSame("A", Record::getName(Record::A)); - } - - public function testGetNameOnInvalidRecordType() - { - $this->expectException(\Error::class); - $this->expectExceptionMessage("65536 does not correspond to a valid record type (must be between 0 and 65535)."); - - Record::getName(65536); - } - - public function testGetNameOnUnknownRecordType() - { - $this->assertSame("unknown (1000)", Record::getName(1000)); - } -} diff --git a/test/Rfc1035StubResolverTest.php b/test/Rfc8484StubResolverTest.php similarity index 100% rename from test/Rfc1035StubResolverTest.php rename to test/Rfc8484StubResolverTest.php diff --git a/test/UnixConfigLoaderTest.php b/test/UnixConfigLoaderTest.php deleted file mode 100644 index 6bdeaac..0000000 --- a/test/UnixConfigLoaderTest.php +++ /dev/null @@ -1,34 +0,0 @@ -loadConfig()); - - $this->assertSame([ - "127.0.0.1:53", - "[2001:4860:4860::8888]:53", - ], $result->getNameservers()); - - $this->assertSame(5000, $result->getTimeout()); - $this->assertSame(3, $result->getAttempts()); - } - - public function testNoDefaultsOnConfNotFound() - { - $this->expectException(ConfigException::class); - wait((new UnixConfigLoader(__DIR__ . "/data/non-existent.conf"))->loadConfig()); - } -}