Adapt for new amphp/dns

This commit is contained in:
Daniil Gentili 2023-01-12 15:49:35 +01:00
parent 1f6935b973
commit b98f463294
15 changed files with 226 additions and 188 deletions

View File

@ -21,24 +21,34 @@ require __DIR__ . '/examples/_bootstrap.php';
use Amp\DoH;
use Amp\Dns;
use Amp\Loop;
use Amp\Dns\DnsRecord;
use function Amp\Future\awaitFirst;
// Set default resolver to DNS-over-HTTPS resolver
$DohConfig = new DoH\DoHConfig([new DoH\Nameserver('https://mozilla.cloudflare-dns.com/dns-query')]); // Defaults to DoH\NameserverType::RFC8484_POST
Dns\resolver(new DoH\Rfc8484StubResolver($DohConfig));
Dns\dnsResolver(new DoH\Rfc8484StubDohResolver($DohConfig));
$githubIpv4 = Dns\resolve("github.com", Dns\Record::A);
$githubIpv4 = Dns\resolve("github.com", DnsRecord::A);
pretty_print_records("github.com", $githubIpv4);
$googleIpv4 = \Amp\async(fn () => Amp\Dns\resolve("google.com", Dns\Record::A));
$googleIpv6 = \Amp\async(fn () => Amp\Dns\resolve("google.com", Dns\Record::AAAA));
$googleIpv4 = \Amp\async(fn () => Amp\Dns\resolve("google.com", DnsRecord::A));
$googleIpv6 = \Amp\async(fn () => Amp\Dns\resolve("google.com", DnsRecord::AAAA));
$firstGoogleResult = Amp\awaitAll([$googleIpv4, $googleIpv6]);
$firstGoogleResult = awaitFirst([$googleIpv4, $googleIpv6]);
pretty_print_records("google.com", $firstGoogleResult);
$combinedGoogleResult = Amp\Dns\resolve("google.com");
pretty_print_records("google.com", $combinedGoogleResult);
$googleMx = Amp\Dns\query("google.com", Amp\Dns\Record::MX);
$googleMx = Amp\Dns\query("google.com", DnsRecord::MX);
pretty_print_records("google.com", $googleMx);
$firstGoogleResult = awaitFirst([$googleIpv4, $googleIpv6]);
pretty_print_records("google.com", $firstGoogleResult);
$combinedGoogleResult = Amp\Dns\resolve("google.com");
pretty_print_records("google.com", $combinedGoogleResult);
$googleMx = Amp\Dns\query("google.com", DnsRecord::MX);
pretty_print_records("google.com", $googleMx);
```

View File

@ -40,24 +40,25 @@
"email": "aaron@trowski.com"
}
],
"minimum-stablity": "dev",
"require": {
"php": ">=8.1",
"amphp/cache": "^v2-dev",
"amphp/cache": "^2",
"amphp/parser": "^1",
"danog/libdns-json": "^0.2",
"daverandom/libdns": "^2.0.1",
"amphp/amp": "^v3-dev",
"amphp/http-client": "^v5-dev",
"amphp/dns": "^v2-dev",
"amphp/amp": "^3",
"amphp/http-client": "v5.x-dev",
"amphp/socket": "v2.x-dev",
"amphp/dns": "^2",
"ext-filter": "*",
"ext-json": "*"
},
"minimum-stability": "dev",
"prefer-stable": true,
"require-dev": {
"amphp/phpunit-util": "^3",
"phpunit/phpunit": "^9",
"amphp/php-cs-fixer-config": "^2-dev",
"amphp/php-cs-fixer-config": "^2",
"psalm/phar": "^5"
},
"autoload": {

View File

@ -39,7 +39,7 @@ $nameservers []= new DoH\Nameserver('https://google.com/resolve', DoH\Nameserver
$DohConfig = new DoH\DoHConfig($nameservers);
// Set default resolver for all AMPHP apps to DNS-over-HTTPS resolver
Dns\resolver(new DoH\Rfc8484StubResolver($DohConfig));
Dns\dnsResolver(new DoH\Rfc8484StubDohResolver($DohConfig));
```
In the last example, [domain fronting](https://en.wikipedia.org/wiki/Domain_fronting), useful to bypass censorship in non-free countries: from the outside, it looks like the DoH client is connecting to `https://google.com`, but by sending a custom Host HTTP header to the server after the TLS handshake is finished, the server that actually replies is `https://dns.google.com` (this is only possible if both servers are behind a common CDN that allows domain fronting, like google's CDN).
@ -47,7 +47,7 @@ In normal conditions, it is recommended that you use mozilla+cloudflare's DoH en
Other parameters that can be passed to the DoHConfig constructor are:
```php
public function __construct(array $nameservers, \Amp\Artax\Client $artax = null, \Amp\Dns\Resolver $resolver = null, \Amp\Dns\ConfigLoader $configLoader = null, \Amp\Cache\Cache $cache = null);
public function __construct(array $nameservers, \Amp\Artax\Client $artax = null, \Amp\Dns\dnsResolver $resolver = null, \Amp\Dns\ConfigLoader $configLoader = null, \Amp\Cache\Cache $cache = null);
```
You can provide a custom HTTP client to use for resolution, or use a custom subresolver (the subresolver is used to make the first and only plaintext DNS request to obtain the address of the DoH nameserver), or use a [custom configuration](https://amphp.org/dns/#configuration) for the DoH client (and the subresolver, too, if the configuration is provided but the resolver isn't).
@ -55,7 +55,7 @@ The last parameter can be a custom async caching object.
### Address Resolution
To resolve addresses using `dns-over-https` first set the global DNS resolver as explained in the [configuration section](#configuration), or use an instance of `Rfc8484StubResolver` instead of `Rfc1035StubResolver`.
To resolve addresses using `dns-over-https` first set the global DNS resolver as explained in the [configuration section](#configuration), or use an instance of `Rfc8484StubDohResolver` instead of `Rfc1035StubResolver`.
`Amp\Dns\resolve` provides hostname to IP address resolution. It returns an array of IPv4 and IPv6 addresses by default. The type of IP addresses returned can be restricted by passing a second argument with the respective type.
@ -76,7 +76,7 @@ $records = Amp\Dns\resolve("github.com", Amp\Dns\Record::A);
### Custom Queries
To resolve addresses using `dns-over-https` first set the global DNS resolver as explained in the [configuration section](#configuration), or use an instance of `Rfc8484StubResolver` instead of `Rfc1035StubResolver`.
To resolve addresses using `dns-over-https` first set the global DNS resolver as explained in the [configuration section](#configuration), or use an instance of `Rfc8484StubDohResolver` instead of `Rfc1035StubResolver`.
`Amp\Dns\query` supports the various other DNS record types such as `MX`, `PTR`, or `TXT`. It automatically rewrites passed IP addresses for `PTR` lookups.
@ -92,7 +92,7 @@ $records = Amp\Dns\query("8.8.8.8", Amp\Dns\Record::PTR);
### Caching
The `Rfc8484StubResolver` caches responses by default in an `Amp\Cache\LocalCache`. You can set any other `Amp\Cache\Cache` implementation by creating a custom instance of `Rfc8484StubResolver` and setting that via `Amp\Dns\resolver()`, but it's usually unnecessary. If you have a lot of very short running scripts, you might want to consider using a local DNS resolver with a cache instead of setting a custom cache implementation, such as `dnsmasq`.
The `Rfc8484StubDohResolver` caches responses by default in an `Amp\Cache\LocalCache`. You can set any other `Amp\Cache\Cache` implementation by creating a custom instance of `Rfc8484StubDohResolver` and setting that via `Amp\Dns\dnsResolver()`, but it's usually unnecessary. If you have a lot of very short running scripts, you might want to consider using a local DNS resolver with a cache instead of setting a custom cache implementation, such as `dnsmasq`.
### Reloading Configuration
@ -100,7 +100,7 @@ The subresolver (which is the resolver set in the `DoHConfig`, `Rfc1035StubResol
```php
Loop::repeat(60000, function () use ($resolver) {
Amp\Dns\resolver()->reloadConfig();
Amp\Dns\dnsResolver()->reloadConfig();
});
```

View File

@ -1,21 +1,24 @@
<?php declare(strict_types=1);
use Amp\Dns\Record;
use Amp\Dns\DnsRecord;
require __DIR__ . "/../vendor/autoload.php";
function pretty_print_records(string $queryName, array $records)
/**
* @param array<DnsRecord> $records
*/
function pretty_print_records(string $queryName, array $records): void
{
print "---------- " . $queryName . " " . str_repeat("-", 55 - strlen($queryName)) . " TTL --\r\n";
$format = "%-10s %-56s %-5d\r\n";
foreach ($records as $record) {
print sprintf($format, Record::getName($record->getType()), $record->getValue(), $record->getTtl());
print sprintf($format, DnsRecord::getName($record->getType()), $record->getValue(), $record->getTtl());
}
}
function pretty_print_error(string $queryName, \Throwable $error)
function pretty_print_error(string $queryName, \Throwable $error): void
{
print "-- " . $queryName . " " . str_repeat("-", 70 - strlen($queryName)) . "\r\n";
print get_class($error) . ": " . $error->getMessage() . "\r\n";

View File

@ -16,14 +16,14 @@ $domains = array_map(function ($line) {
array_shift($domains);
$DohConfig = new DoH\DoHConfig([new DoH\Nameserver('https://mozilla.cloudflare-dns.com/dns-query')]);
Dns\resolver(new DoH\Rfc8484StubResolver($DohConfig));
Dns\dnsResolver(new DoH\Rfc8484StubDohResolver($DohConfig));
print "Starting sequential queries...\r\n\r\n";
$timings = [];
for ($i = 0; $i < 10; $i++) {
$start = microtime(1);
$start = microtime(true);
$domain = $domains[random_int(0, count($domains) - 1)];
try {
@ -32,7 +32,7 @@ for ($i = 0; $i < 10; $i++) {
pretty_print_error($domain, $e);
}
$time = round(microtime(1) - $start, 2);
$time = round(microtime(true) - $start, 2);
$timings[] = $time;
printf("%'-74s\r\n\r\n", " in " . $time . " ms");

View File

@ -13,7 +13,7 @@ $customConfigLoader = new class implements Dns\DnsConfigLoader {
return new Dns\DnsConfig([
"8.8.8.8:53",
"[2001:4860:4860::8888]:53",
], $hosts, 5, 3);
], $hosts);
}
};
@ -28,7 +28,7 @@ $DohConfig = new DoH\DoHConfig(
null,
$customConfigLoader
);
Dns\resolver(new DoH\Rfc8484StubResolver($DohConfig));
Dns\dnsResolver(new DoH\Rfc8484StubDohResolver($DohConfig));
$hostname = $argv[1] ?? "amphp.org";

View File

@ -3,16 +3,17 @@
require __DIR__ . "/_bootstrap.php";
use Amp\Dns;
use Amp\Dns\DnsRecord;
use Amp\DoH;
// Set default resolver to DNS-over-HTTPS resolver
$DohConfig = new DoH\DoHConfig([new DoH\Nameserver('https://mozilla.cloudflare-dns.com/dns-query')]);
Dns\resolver(new DoH\Rfc8484StubResolver($DohConfig));
Dns\dnsResolver(new DoH\Rfc8484StubDohResolver($DohConfig));
$ip = $argv[1] ?? "8.8.8.8";
try {
pretty_print_records($ip, Dns\query($ip, Dns\Record::PTR));
pretty_print_records($ip, Dns\query($ip, DnsRecord::PTR));
} catch (Dns\DnsException $e) {
pretty_print_error($ip, $e);
}

View File

@ -4,9 +4,10 @@ namespace Amp\DoH;
use Amp\Cache\Cache;
use Amp\Cache\LocalCache;
use Amp\Dns\ConfigException;
use Amp\Dns\DnsConfigException;
use Amp\Dns\DnsConfigLoader;
use Amp\Dns\Rfc1035StubResolver;
use Amp\Dns\DnsResolver;
use Amp\Dns\Rfc1035StubDnsResolver;
use Amp\Dns\UnixDnsConfigLoader;
use Amp\Dns\WindowsDnsConfigLoader;
use Amp\Http\Client\DelegateHttpClient;
@ -19,24 +20,24 @@ final class DoHConfig
*/
private readonly array $nameservers;
private readonly DelegateHttpClient $httpClient;
private readonly Rfc1035StubResolver $subResolver;
private readonly DnsResolver $subResolver;
private readonly DnsConfigLoader $configLoader;
private readonly Cache $cache;
/**
* @param non-empty-array<Nameserver> $nameservers
*/
public function __construct(array $nameservers, ?DelegateHttpClient $httpClient = null, ?Rfc1035StubResolver $resolver = null, ?DnsConfigLoader $configLoader = null, ?Cache $cache = null)
public function __construct(array $nameservers, ?DelegateHttpClient $httpClient = null, ?DnsResolver $resolver = null, ?DnsConfigLoader $configLoader = null, ?Cache $cache = null)
{
/** @psalm-suppress TypeDoesNotContainType */
if (\count($nameservers) < 1) {
throw new ConfigException("At least one nameserver is required for a valid config");
throw new DnsConfigException("At least one nameserver is required for a valid config");
}
foreach ($nameservers as $nameserver) {
/** @psalm-suppress DocblockContradiction */
if (!($nameserver instanceof Nameserver)) {
throw new ConfigException("Invalid nameserver: {$nameserver}");
throw new DnsConfigException("Invalid nameserver: {$nameserver}");
}
}
@ -46,7 +47,7 @@ final class DoHConfig
$this->configLoader = $configLoader ?? (\stripos(PHP_OS, "win") === 0
? new WindowsDnsConfigLoader()
: new UnixDnsConfigLoader());
$this->subResolver = $resolver ?? new Rfc1035StubResolver(null, $this->configLoader);
$this->subResolver = $resolver ?? new Rfc1035StubDnsResolver(null, $this->configLoader);
}
/**
@ -80,7 +81,7 @@ final class DoHConfig
{
return $this->configLoader;
}
public function getSubResolver(): Rfc1035StubResolver
public function getSubResolver(): DnsResolver
{
return $this->subResolver;
}

View File

@ -2,7 +2,7 @@
namespace Amp\DoH;
use Amp\Dns\ConfigException;
use Amp\Dns\DnsConfigException;
final class Nameserver
{
@ -18,7 +18,7 @@ final class Nameserver
private readonly array $headers = []
) {
if (\parse_url($uri, PHP_URL_SCHEME) !== 'https') {
throw new ConfigException('Did not provide a valid HTTPS url!');
throw new DnsConfigException('Did not provide a valid HTTPS url!');
}
$this->host = \parse_url($uri, PHP_URL_HOST);
}

View File

@ -5,15 +5,15 @@ namespace Amp\DoH;
use Amp\Cache\Cache;
use Amp\Cancellation;
use Amp\CompositeException;
use Amp\Dns\ConfigException;
use Amp\Dns\DnsConfig;
use Amp\Dns\DnsConfigException;
use Amp\Dns\DnsConfigLoader;
use Amp\Dns\DnsException;
use Amp\Dns\NoRecordException;
use Amp\Dns\Record;
use Amp\Dns\Resolver;
use Amp\Dns\Rfc1035StubResolver;
use Amp\Dns\TimeoutException;
use Amp\Dns\DnsRecord;
use Amp\Dns\DnsResolver;
use Amp\Dns\DnsTimeoutException;
use Amp\Dns\MissingDnsRecordException;
use Amp\Dns\Rfc1035StubDnsResolver;
use Amp\Future;
use Amp\Http\Client\DelegateHttpClient;
use Amp\Http\Client\Request;
@ -35,7 +35,7 @@ use LibDNS\Records\QuestionFactory;
use function Amp\async;
use function Amp\Dns\normalizeName;
final class Rfc8484StubResolver implements Resolver
final class Rfc8484StubDohResolver implements DnsResolver
{
const CACHE_PREFIX = "amphp.doh.";
@ -50,7 +50,7 @@ final class Rfc8484StubResolver implements Resolver
/** @var Future[] */
private array $pendingQueries = [];
private Rfc1035StubResolver $subResolver;
private DnsResolver $subResolver;
private Encoder $encoder;
private Decoder $decoder;
private QueryEncoder $encoderJson;
@ -75,31 +75,31 @@ final class Rfc8484StubResolver implements Resolver
/** @inheritdoc */
public function resolve(string $name, int $typeRestriction = null, ?Cancellation $cancellation = null): array
{
if ($typeRestriction !== null && $typeRestriction !== Record::A && $typeRestriction !== Record::AAAA) {
throw new \Error("Invalid value for parameter 2: null|Record::A|Record::AAAA expected");
if ($typeRestriction !== null && $typeRestriction !== DnsRecord::A && $typeRestriction !== DnsRecord::AAAA) {
throw new \Error("Invalid value for parameter 2: null|DnsRecord::A|DnsRecord::AAAA expected");
}
if (!$this->config) {
try {
$this->reloadConfig();
} catch (ConfigException $e) {
} catch (DnsConfigException $e) {
$this->config = new DnsConfig(['0.0.0.0'], []);
}
}
switch ($typeRestriction) {
case Record::A:
case DnsRecord::A:
if (\filter_var($name, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) {
return [new Record($name, Record::A, null)];
return [new DnsRecord($name, DnsRecord::A, null)];
}
if (\filter_var($name, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) {
throw new DnsException("Got an IPv6 address, but type is restricted to IPv4");
}
break;
case Record::AAAA:
case DnsRecord::AAAA:
if (\filter_var($name, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) {
return [new Record($name, Record::AAAA, null)];
return [new DnsRecord($name, DnsRecord::AAAA, null)];
}
if (\filter_var($name, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) {
@ -108,11 +108,11 @@ final class Rfc8484StubResolver implements Resolver
break;
default:
if (\filter_var($name, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) {
return [new Record($name, Record::A, null)];
return [new DnsRecord($name, DnsRecord::A, null)];
}
if (\filter_var($name, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) {
return [new Record($name, Record::AAAA, null)];
return [new DnsRecord($name, DnsRecord::AAAA, null)];
}
break;
}
@ -128,9 +128,9 @@ final class Rfc8484StubResolver implements Resolver
// Follow RFC 6761 and never send queries for localhost to the caching DNS server
// Usually, these queries are already resolved via queryHosts()
if ($name === 'localhost') {
return $typeRestriction === Record::AAAA
? [new Record('::1', Record::AAAA, null)]
: [new Record('127.0.0.1', Record::A, null)];
return $typeRestriction === DnsRecord::AAAA
? [new DnsRecord('::1', DnsRecord::AAAA, null)]
: [new DnsRecord('127.0.0.1', DnsRecord::A, null)];
}
if ($this->dohConfig->isNameserver($name)) {
@ -157,15 +157,15 @@ final class Rfc8484StubResolver implements Resolver
}
[$exceptions, $records] = Future\awaitAll([
async(fn () => $this->query($searchName, Record::A, $cancellation)),
async(fn () => $this->query($searchName, Record::AAAA, $cancellation)),
async(fn () => $this->query($searchName, DnsRecord::A, $cancellation)),
async(fn () => $this->query($searchName, DnsRecord::AAAA, $cancellation)),
]);
if (\count($exceptions) === 2) {
$errors = [];
foreach ($exceptions as $reason) {
if ($reason instanceof NoRecordException) {
if ($reason instanceof MissingDnsRecordException) {
throw $reason;
}
@ -184,13 +184,13 @@ final class Rfc8484StubResolver implements Resolver
}
return \array_merge(...$records);
} catch (NoRecordException) {
} catch (MissingDnsRecordException) {
try {
$cnameRecords = $this->query($searchName, Record::CNAME, $cancellation);
$cnameRecords = $this->query($searchName, DnsRecord::CNAME, $cancellation);
$name = $cnameRecords[0]->getValue();
continue;
} catch (NoRecordException) {
$dnameRecords = $this->query($searchName, Record::DNAME, $cancellation);
} catch (MissingDnsRecordException) {
$dnameRecords = $this->query($searchName, DnsRecord::DNAME, $cancellation);
$name = $dnameRecords[0]->getValue();
continue;
}
@ -219,7 +219,9 @@ final class Rfc8484StubResolver implements Resolver
if (!$this->pendingConfig) {
$promise = async(function () {
try {
$this->subResolver->reloadConfig();
if ($this->subResolver instanceof Rfc1035StubDnsResolver) {
$this->subResolver->reloadConfig();
}
$this->config = $this->configLoader->loadConfig();
} finally {
$this->pendingConfig = null;
@ -237,15 +239,15 @@ final class Rfc8484StubResolver implements Resolver
$hosts = $this->config->getKnownHosts();
$records = [];
$returnIPv4 = $typeRestriction === null || $typeRestriction === Record::A;
$returnIPv6 = $typeRestriction === null || $typeRestriction === Record::AAAA;
$returnIPv4 = $typeRestriction === null || $typeRestriction === DnsRecord::A;
$returnIPv6 = $typeRestriction === null || $typeRestriction === DnsRecord::AAAA;
if ($returnIPv4 && isset($hosts[Record::A][$name])) {
$records[] = new Record($hosts[Record::A][$name], Record::A, null);
if ($returnIPv4 && isset($hosts[DnsRecord::A][$name])) {
$records[] = new DnsRecord($hosts[DnsRecord::A][$name], DnsRecord::A, null);
}
if ($returnIPv6 && isset($hosts[Record::AAAA][$name])) {
$records[] = new Record($hosts[Record::AAAA][$name], Record::AAAA, null);
if ($returnIPv6 && isset($hosts[DnsRecord::AAAA][$name])) {
$records[] = new DnsRecord($hosts[DnsRecord::AAAA][$name], DnsRecord::AAAA, null);
}
return $records;
@ -266,7 +268,7 @@ final class Rfc8484StubResolver implements Resolver
if (!$this->config) {
try {
$this->reloadConfig();
} catch (ConfigException $e) {
} catch (DnsConfigException $e) {
$this->config = new DnsConfig(['0.0.0.0'], []);
}
}
@ -296,7 +298,7 @@ final class Rfc8484StubResolver implements Resolver
$this->assertAcceptableResponse($response);
if ($response->isTruncated()) {
throw new DnsException("Server returned a truncated response for '{$name}' (".Record::getName($type).")");
throw new DnsException("Server returned a truncated response for '{$name}' (".DnsRecord::getName($type).")");
}
$answers = $response->getAnswerRecords();
@ -319,22 +321,22 @@ final class Rfc8484StubResolver implements Resolver
if (!isset($result[$type])) {
// "it MUST NOT cache it for longer than five (5) minutes" per RFC 2308 section 7.1
$this->cache->set($this->getCacheKey($name, $type), \json_encode([]), 300);
throw new NoRecordException("No records returned for '{$name}' (".Record::getName($type).")");
throw new MissingDnsRecordException("No records returned for '{$name}' (".DnsRecord::getName($type).")");
}
return \array_map(function ($data) use ($type, $ttls) {
return new Record($data, $type, $ttls[$type]);
return new DnsRecord($data, $type, $ttls[$type]);
}, $result[$type]);
} catch (TimeoutException) {
} catch (DnsTimeoutException) {
$i = ++$attempt % \count($nameservers);
$nameserver = $nameservers[$i];
}
}
throw new TimeoutException(\sprintf(
throw new DnsTimeoutException(\sprintf(
"No response for '%s' (%s) from any nameserver within %d ms after %d attempts, tried %s",
$name,
Record::getName($type),
DnsRecord::getName($type),
$this->config->getTimeout(),
$attempts,
\implode(", ", $attemptDescription)
@ -395,7 +397,7 @@ final class Rfc8484StubResolver implements Resolver
private function normalizeName(string $name, int $type): string
{
if ($type === Record::PTR) {
if ($type === DnsRecord::PTR) {
if (($packedIp = @\inet_pton($name)) !== false) {
if (isset($packedIp[4])) { // IPv6
$name = \wordwrap(\strrev(\bin2hex($packedIp)), 1, ".", true).".ip6.arpa";
@ -403,7 +405,7 @@ final class Rfc8484StubResolver implements Resolver
$name = \inet_ntop(\strrev($packedIp)).".in-addr.arpa";
}
}
} elseif (\in_array($type, [Record::A, Record::AAAA])) {
} elseif (\in_array($type, [DnsRecord::A, DnsRecord::AAAA])) {
$name = normalizeName($name);
}
@ -438,20 +440,20 @@ final class Rfc8484StubResolver implements Resolver
}
/**
* @return list<Record>
* @return list<DnsRecord>
*/
private function decodeCachedResult(string $name, int $type, string $encoded): array
{
$decoded = \json_decode($encoded, true);
if (!$decoded) {
throw new NoRecordException("No records returned for {$name} (cached result)");
throw new MissingDnsRecordException("No records returned for {$name} (cached result)");
}
$result = [];
foreach ($decoded as $data) {
$result[] = new Record($data, $type);
$result[] = new DnsRecord($data, $type);
}
return $result;

View File

@ -8,6 +8,8 @@
>
<projectFiles>
<directory name="lib" />
<directory name="test" />
<directory name="examples" />
<ignoreFiles>
<directory name="vendor" />
</ignoreFiles>

View File

@ -2,30 +2,38 @@
namespace Amp\DoH\Test;
use Amp\Cache\Cache;
use Amp\Cache\LocalCache;
use Amp\Dns\ConfigException;
use Amp\Dns\Rfc1035StubResolver;
use Amp\Dns\DnsConfigException;
use Amp\Dns\DnsConfigLoader;
use Amp\Dns\DnsResolver;
use Amp\Dns\Rfc1035StubDnsResolver;
use Amp\Dns\UnixDnsConfigLoader;
use Amp\Dns\WindowsDnsConfigLoader;
use Amp\DoH\DoHConfig;
use Amp\DoH\Nameserver;
use Amp\DoH\NameserverType;
use Amp\Http\Client\HttpClient;
use Amp\Http\Client\HttpClientBuilder;
use Amp\PHPUnit\AsyncTestCase;
/** @psalm-suppress PropertyNotSetInConstructor */
class DoHConfigTest extends AsyncTestCase
{
/**
* @param string[] $nameservers Valid server array.
* @param non-empty-list<NameServer> $nameservers Valid server array.
*
* @dataProvider provideValidServers
*/
public function testAcceptsValidServers(array $nameservers)
public function testAcceptsValidServers(array $nameservers): void
{
$this->assertInstanceOf(DoHConfig::class, new DoHConfig($nameservers));
}
public function provideValidServers()
/**
* @return list<non-empty-list<list{0: Nameserver, 1?: NameserverType}>>
*/
public function provideValidServers(): array
{
return [
[[new Nameserver('https://cloudflare-dns.com/dns-query')]],
@ -38,16 +46,19 @@ class DoHConfigTest extends AsyncTestCase
}
/**
* @param string[] $nameservers Invalid server array.
* @param list<mixed> $nameservers Invalid server array.
*
* @dataProvider provideInvalidServers
*/
public function testRejectsInvalidServers(array $nameservers)
public function testRejectsInvalidServers(array $nameservers): void
{
$this->expectException(ConfigException::class);
$this->expectException(DnsConfigException::class);
new DoHConfig($nameservers);
}
/**
* @return list<list{mixed}>
*/
public function provideInvalidServers()
{
return [
@ -75,47 +86,50 @@ class DoHConfigTest extends AsyncTestCase
}
/**
* @param \Amp\Http\Client\DelegateHttpClient $client Valid HttpClient instance
*
* @dataProvider provideValidHttpClient
*/
public function testAcceptsValidHttpClient($client)
public function testAcceptsValidHttpClient(HttpClient $client): void
{
$this->assertInstanceOf(DoHConfig::class, new DoHConfig([new Nameserver('https://cloudflare-dns.com/dns-query')], $client));
}
public function provideValidHttpClient()
/**
* @return list<list{HttpClient}>
*/
public function provideValidHttpClient(): array
{
return [
[HttpClientBuilder::buildDefault()],
];
}
/**
* @param \Amp\Dns\Resolver $resolver Valid resolver instance
*
* @dataProvider provideValidResolver
*/
public function testAcceptsValidResolver($resolver)
public function testAcceptsValidResolver(DnsResolver $resolver): void
{
$this->assertInstanceOf(DoHConfig::class, new DoHConfig([new Nameserver('https://cloudflare-dns.com/dns-query')], null, $resolver));
}
/**
* @return list<list{DnsResolver}>
*/
public function provideValidResolver()
{
return [
[new Rfc1035StubResolver()],
[new Rfc1035StubDnsResolver()],
];
}
/**
* @param $configLoader \Amp\Dns\ConfigLoader Valid ConfigLoader instance
*
* @dataProvider provideValidConfigLoader
*/
public function testAcceptsValidConfigLoader($configLoader)
public function testAcceptsValidConfigLoader(DnsConfigLoader $configLoader): void
{
$this->assertInstanceOf(DoHConfig::class, new DoHConfig([new Nameserver('https://cloudflare-dns.com/dns-query')], null, null, $configLoader));
}
/**
* @return list<list{DnsConfigLoader}>
*/
public function provideValidConfigLoader()
{
return [
@ -125,15 +139,16 @@ class DoHConfigTest extends AsyncTestCase
];
}
/**
* @param \Amp\Cache\Cache Valid cache instance
*
* @dataProvider provideValidCache
*/
public function testAcceptsValidCache($cache)
public function testAcceptsValidCache(Cache $cache): void
{
$this->assertInstanceOf(DoHConfig::class, new DoHConfig([new Nameserver('https://cloudflare-dns.com/dns-query')], null, null, null, $cache));
}
/**
* @return list<list{Cache}>
*/
public function provideValidCache()
{
return [

View File

@ -3,31 +3,31 @@
namespace Amp\DoH\Test;
use Amp\Dns;
use Amp\Dns\Record;
use Amp\Dns\DnsRecord;
use Amp\DoH;
use Amp\DoH\Nameserver;
use Amp\DoH\NameserverType;
use Amp\PHPUnit\AsyncTestCase;
use function Amp\delay;
use function Amp\Dns\dnsResolver;
/** @psalm-suppress PropertyNotSetInConstructor */
class IntegrationTest extends AsyncTestCase
{
/**
* @param string $hostname
* @param non-empty-list<list{0: string, 1?: NameserverType}> $nameservers
* @group internet
* @dataProvider provideServersAndHostnames
*/
public function testResolve($hostname, $nameservers)
public function testResolve(string $hostname, array $nameservers): void
{
foreach ($nameservers as &$nameserver) {
$nameserver = new Nameserver(...$nameserver);
}
$nameservers = \array_map(fn (array $v) => new Nameserver(...$v), $nameservers);
$DohConfig = new DoH\DoHConfig($nameservers);
Dns\resolver(new DoH\Rfc8484StubResolver($DohConfig));
dnsResolver(new DoH\Rfc8484StubDohResolver($DohConfig));
$result = Dns\resolve($hostname);
/** @var Record $record */
/** @var DnsRecord $record */
$record = $result[0];
$inAddr = @\inet_pton($record->getValue());
$this->assertNotFalse(
@ -38,42 +38,40 @@ class IntegrationTest extends AsyncTestCase
}
/**
* @param non-empty-list<list{0: string, 1?: NameserverType}> $nameservers
* @group internet
* @dataProvider provideServers
*/
public function testWorksAfterConfigReload($nameservers)
public function testWorksAfterConfigReload($nameservers): void
{
foreach ($nameservers as &$nameserver) {
$nameserver = new Nameserver(...$nameserver);
}
$nameservers = \array_map(fn (array $v) => new Nameserver(...$v), $nameservers);
$DohConfig = new DoH\DoHConfig($nameservers);
Dns\resolver(new DoH\Rfc8484StubResolver($DohConfig));
dnsResolver(new DoH\Rfc8484StubDohResolver($DohConfig));
Dns\resolve('google.com');
$this->assertNull(Dns\resolver()->reloadConfig());
/** @psalm-suppress UndefinedInterfaceMethod */
$this->assertNull(dnsResolver()->reloadConfig());
delay(0.5);
$result = \is_array(Dns\resolve('google.com'));
$this->assertTrue($result);
$this->assertIsArray(Dns\resolve('google.com'));
\usleep(500*1000);
}
/**
* @param non-empty-list<list{0: string, 1?: NameserverType}> $nameservers
* @group internet
* @dataProvider provideServers
*/
public function testResolveIPv4only($nameservers)
public function testResolveIPv4only($nameservers): void
{
foreach ($nameservers as &$nameserver) {
$nameserver = new Nameserver(...$nameserver);
}
$nameservers = \array_map(fn (array $v) => new Nameserver(...$v), $nameservers);
$DohConfig = new DoH\DoHConfig($nameservers);
Dns\resolver(new DoH\Rfc8484StubResolver($DohConfig));
dnsResolver(new DoH\Rfc8484StubDohResolver($DohConfig));
$records = Dns\resolve("google.com", Record::A);
$records = Dns\resolve("google.com", DnsRecord::A);
/** @var Record $record */
/** @var DnsRecord $record */
foreach ($records as $record) {
$this->assertSame(Record::A, $record->getType());
$this->assertSame(DnsRecord::A, $record->getType());
$inAddr = @\inet_pton($record->getValue());
$this->assertNotFalse(
$inAddr,
@ -84,22 +82,21 @@ class IntegrationTest extends AsyncTestCase
}
/**
* @param non-empty-list<list{0: string, 1?: NameserverType}> $nameservers
* @group internet
* @dataProvider provideServers
*/
public function testResolveIPv6only($nameservers)
public function testResolveIPv6only($nameservers): void
{
foreach ($nameservers as &$nameserver) {
$nameserver = new Nameserver(...$nameserver);
}
$nameservers = \array_map(fn (array $v) => new Nameserver(...$v), $nameservers);
$DohConfig = new DoH\DoHConfig($nameservers);
Dns\resolver(new DoH\Rfc8484StubResolver($DohConfig));
dnsResolver(new DoH\Rfc8484StubDohResolver($DohConfig));
$records = Dns\resolve("google.com", Record::AAAA);
$records = Dns\resolve("google.com", DnsRecord::AAAA);
/** @var Record $record */
/** @var DnsRecord $record */
foreach ($records as $record) {
$this->assertSame(Record::AAAA, $record->getType());
$this->assertSame(DnsRecord::AAAA, $record->getType());
$inAddr = @\inet_pton($record->getValue());
$this->assertNotFalse(
$inAddr,
@ -110,41 +107,41 @@ class IntegrationTest extends AsyncTestCase
}
/**
* @param non-empty-list<list{0: string, 1?: NameserverType}> $nameservers
* @group internet
* @dataProvider provideServers
*/
public function testPtrLookup($nameservers)
public function testPtrLookup($nameservers): void
{
foreach ($nameservers as &$nameserver) {
$nameserver = new Nameserver(...$nameserver);
}
$nameservers = \array_map(fn (array $v) => new Nameserver(...$v), $nameservers);
$DohConfig = new DoH\DoHConfig($nameservers);
Dns\resolver(new DoH\Rfc8484StubResolver($DohConfig));
dnsResolver(new DoH\Rfc8484StubDohResolver($DohConfig));
$result = Dns\query("8.8.4.4", Record::PTR);
$result = Dns\query("8.8.4.4", DnsRecord::PTR);
/** @var Record $record */
/** @var DnsRecord $record */
$record = $result[0];
$this->assertSame("dns.google", $record->getValue());
$this->assertNotNull($record->getTtl());
$this->assertSame(Record::PTR, $record->getType());
$this->assertSame(DnsRecord::PTR, $record->getType());
\usleep(500*1000);
}
/**
* @return iterable<list{string, list<list{0: string, 1?: NameserverType}>}>
*/
public function provideServersAndHostnames()
{
$hostnames = $this->provideHostnames();
$servers = $this->provideServers();
$result = [];
foreach ($hostnames as $args) {
$hostname = $args[0];
foreach ($servers as $args) {
$nameserver = $args[0];
$result[] = [$hostname, $nameserver];
foreach ($this->provideHostnames() as [$hostname]) {
foreach ($this->provideServers() as [$nameserver]) {
yield [$hostname, $nameserver];
}
}
return $result;
}
/**
* @return list<list{string}>
*/
public function provideHostnames()
{
return [
@ -160,6 +157,9 @@ class IntegrationTest extends AsyncTestCase
];
}
/**
* @return iterable<int, list{list<list{0: string, 1?: NameserverType}>}>
*/
public function provideServers()
{
$nameservers = [
@ -169,7 +169,6 @@ class IntegrationTest extends AsyncTestCase
['https://mozilla.cloudflare-dns.com/dns-query', NameserverType::GOOGLE_JSON],
['https://dns.google/resolve', NameserverType::GOOGLE_JSON],
];
$result = [];
for ($start = 0; $start < \count($nameservers); $start++) {
$temp = [];
for ($i = 0; $i < \count($nameservers); $i++) {
@ -177,9 +176,7 @@ class IntegrationTest extends AsyncTestCase
$temp[] = $nameservers[$i];
}
$result[] = [$temp];
yield [$temp];
}
return $result;
}
}

View File

@ -2,22 +2,25 @@
namespace Amp\DoH\Test;
use Amp\Dns\ConfigException;
use Amp\Dns\DnsConfigException;
use Amp\DoH\Nameserver;
use Amp\DoH\NameserverType;
use Amp\PHPUnit\AsyncTestCase;
/** @psalm-suppress PropertyNotSetInConstructor */
class NameserverTest extends AsyncTestCase
{
/**
*
* @dataProvider provideValidServers
*/
public function testAcceptsValidServers($nameserver, $type = NameserverType::RFC8484_POST, $headers = [])
public function testAcceptsValidServers(string $nameserver, NameserverType $type = NameserverType::RFC8484_POST, array $headers = []): void
{
$this->assertInstanceOf(Nameserver::class, new Nameserver($nameserver, $type, $headers));
}
/**
* @return list<list{0: string, 1?: NameserverType::RFC8484_POST}>
*/
public function provideValidServers()
{
return [
@ -30,16 +33,18 @@ class NameserverTest extends AsyncTestCase
}
/**
*
* @dataProvider provideInvalidServers
*/
public function testRejectsInvalidServers($nameserver, $type = NameserverType::RFC8484_POST, $headers = [])
public function testRejectsInvalidServers(string $nameserver, NameserverType $type = NameserverType::RFC8484_POST, array $headers = []): void
{
$this->expectException(ConfigException::class);
$this->expectException(DnsConfigException::class);
new Nameserver($nameserver, $type, $headers);
}
public function provideInvalidServers()
/**
* @return list<list{0: string, 1?: NameserverType::RFC8484_POST}>
*/
public function provideInvalidServers(): array
{
return [
[''],

View File

@ -3,50 +3,51 @@
namespace Amp\DoH\Test;
use Amp\Dns\DnsException;
use Amp\Dns\DnsRecord;
use Amp\Dns\InvalidNameException;
use Amp\Dns\Record;
use Amp\Dns\Rfc1035StubResolver;
use Amp\Dns\Rfc1035StubDnsResolver;
use Amp\DoH;
use Amp\DoH\Rfc8484StubResolver;
use Amp\DoH\Rfc8484StubDohResolver;
use Amp\PHPUnit\AsyncTestCase;
class Rfc8484StubResolverTest extends AsyncTestCase
/** @psalm-suppress PropertyNotSetInConstructor */
class Rfc8484StubDohResolverTest extends AsyncTestCase
{
public function getResolver()
public function getResolver(): Rfc8484StubDohResolver
{
$DohConfig = new DoH\DoHConfig([new DoH\Nameserver('https://mozilla.cloudflare-dns.com/dns-query')]);
return new Rfc8484StubResolver($DohConfig);
return new Rfc8484StubDohResolver($DohConfig);
}
public function testResolveSecondParameterAcceptedValues()
public function testResolveSecondParameterAcceptedValues(): void
{
$this->expectException(\Error::class);
$this->getResolver()->resolve("abc.de", Record::TXT);
$this->getResolver()->resolve("abc.de", DnsRecord::TXT);
}
public function testIpAsArgumentWithIPv4Restriction()
public function testIpAsArgumentWithIPv4Restriction(): void
{
$this->expectException(DnsException::class);
$this->getResolver()->resolve("::1", Record::A);
$this->getResolver()->resolve("::1", DnsRecord::A);
}
public function testIpAsArgumentWithIPv6Restriction()
public function testIpAsArgumentWithIPv6Restriction(): void
{
$this->expectException(DnsException::class);
$this->getResolver()->resolve("127.0.0.1", Record::AAAA);
$this->getResolver()->resolve("127.0.0.1", DnsRecord::AAAA);
}
public function testInvalidName()
public function testInvalidName(): void
{
$this->expectException(InvalidNameException::class);
$this->getResolver()->resolve("go@gle.com", Record::A);
$this->getResolver()->resolve("go@gle.com", DnsRecord::A);
}
public function testValidSubResolver()
public function testValidSubResolver(): void
{
$DohConfig = new DoH\DoHConfig([new DoH\Nameserver('https://mozilla.cloudflare-dns.com/dns-query')], null, new Rfc1035StubResolver());
$this->assertInstanceOf(Rfc8484StubResolver::class, new Rfc8484StubResolver($DohConfig));
$DohConfig = new DoH\DoHConfig([new DoH\Nameserver('https://mozilla.cloudflare-dns.com/dns-query')], null, new Rfc1035StubDnsResolver());
$this->assertInstanceOf(Rfc8484StubDohResolver::class, new Rfc8484StubDohResolver($DohConfig));
}
public function testInvalidNameserverFallback()
public function testInvalidNameserverFallback(): void
{
$DohConfig = new DoH\DoHConfig(
[
@ -56,8 +57,8 @@ class Rfc8484StubResolverTest extends AsyncTestCase
new DoH\Nameserver('https://mozilla.cloudflare-dns.com/dns-query'),
]
);
$resolver = new Rfc8484StubResolver($DohConfig);
$this->assertInstanceOf(Rfc8484StubResolver::class, $resolver);
$resolver = new Rfc8484StubDohResolver($DohConfig);
$this->assertInstanceOf(Rfc8484StubDohResolver::class, $resolver);
$resolver->resolve('google.com');
}
}