mirror of
https://github.com/danog/dns-over-https.git
synced 2024-12-03 09:47:45 +01:00
Adapt for new amphp/dns
This commit is contained in:
parent
1f6935b973
commit
b98f463294
24
README.md
24
README.md
@ -21,24 +21,34 @@ require __DIR__ . '/examples/_bootstrap.php';
|
|||||||
|
|
||||||
use Amp\DoH;
|
use Amp\DoH;
|
||||||
use Amp\Dns;
|
use Amp\Dns;
|
||||||
use Amp\Loop;
|
use Amp\Dns\DnsRecord;
|
||||||
|
|
||||||
|
use function Amp\Future\awaitFirst;
|
||||||
|
|
||||||
// Set default resolver to DNS-over-HTTPS resolver
|
// 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
|
$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);
|
pretty_print_records("github.com", $githubIpv4);
|
||||||
|
|
||||||
$googleIpv4 = \Amp\async(fn () => Amp\Dns\resolve("google.com", Dns\Record::A));
|
$googleIpv4 = \Amp\async(fn () => Amp\Dns\resolve("google.com", DnsRecord::A));
|
||||||
$googleIpv6 = \Amp\async(fn () => Amp\Dns\resolve("google.com", Dns\Record::AAAA));
|
$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);
|
pretty_print_records("google.com", $firstGoogleResult);
|
||||||
|
|
||||||
$combinedGoogleResult = Amp\Dns\resolve("google.com");
|
$combinedGoogleResult = Amp\Dns\resolve("google.com");
|
||||||
pretty_print_records("google.com", $combinedGoogleResult);
|
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);
|
pretty_print_records("google.com", $googleMx);
|
||||||
```
|
```
|
||||||
|
@ -40,24 +40,25 @@
|
|||||||
"email": "aaron@trowski.com"
|
"email": "aaron@trowski.com"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
"minimum-stablity": "dev",
|
||||||
"require": {
|
"require": {
|
||||||
"php": ">=8.1",
|
"php": ">=8.1",
|
||||||
"amphp/cache": "^v2-dev",
|
"amphp/cache": "^2",
|
||||||
"amphp/parser": "^1",
|
"amphp/parser": "^1",
|
||||||
"danog/libdns-json": "^0.2",
|
"danog/libdns-json": "^0.2",
|
||||||
"daverandom/libdns": "^2.0.1",
|
"daverandom/libdns": "^2.0.1",
|
||||||
"amphp/amp": "^v3-dev",
|
"amphp/amp": "^3",
|
||||||
"amphp/http-client": "^v5-dev",
|
"amphp/http-client": "v5.x-dev",
|
||||||
"amphp/dns": "^v2-dev",
|
"amphp/socket": "v2.x-dev",
|
||||||
|
"amphp/dns": "^2",
|
||||||
"ext-filter": "*",
|
"ext-filter": "*",
|
||||||
"ext-json": "*"
|
"ext-json": "*"
|
||||||
},
|
},
|
||||||
"minimum-stability": "dev",
|
|
||||||
"prefer-stable": true,
|
"prefer-stable": true,
|
||||||
"require-dev": {
|
"require-dev": {
|
||||||
"amphp/phpunit-util": "^3",
|
"amphp/phpunit-util": "^3",
|
||||||
"phpunit/phpunit": "^9",
|
"phpunit/phpunit": "^9",
|
||||||
"amphp/php-cs-fixer-config": "^2-dev",
|
"amphp/php-cs-fixer-config": "^2",
|
||||||
"psalm/phar": "^5"
|
"psalm/phar": "^5"
|
||||||
},
|
},
|
||||||
"autoload": {
|
"autoload": {
|
||||||
|
@ -39,7 +39,7 @@ $nameservers []= new DoH\Nameserver('https://google.com/resolve', DoH\Nameserver
|
|||||||
$DohConfig = new DoH\DoHConfig($nameservers);
|
$DohConfig = new DoH\DoHConfig($nameservers);
|
||||||
|
|
||||||
// Set default resolver for all AMPHP apps to DNS-over-HTTPS resolver
|
// 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).
|
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:
|
Other parameters that can be passed to the DoHConfig constructor are:
|
||||||
```php
|
```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).
|
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
|
### 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.
|
`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
|
### 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.
|
`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
|
### 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
|
### Reloading Configuration
|
||||||
|
|
||||||
@ -100,7 +100,7 @@ The subresolver (which is the resolver set in the `DoHConfig`, `Rfc1035StubResol
|
|||||||
|
|
||||||
```php
|
```php
|
||||||
Loop::repeat(60000, function () use ($resolver) {
|
Loop::repeat(60000, function () use ($resolver) {
|
||||||
Amp\Dns\resolver()->reloadConfig();
|
Amp\Dns\dnsResolver()->reloadConfig();
|
||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -1,21 +1,24 @@
|
|||||||
<?php declare(strict_types=1);
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
use Amp\Dns\Record;
|
use Amp\Dns\DnsRecord;
|
||||||
|
|
||||||
require __DIR__ . "/../vendor/autoload.php";
|
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";
|
print "---------- " . $queryName . " " . str_repeat("-", 55 - strlen($queryName)) . " TTL --\r\n";
|
||||||
|
|
||||||
$format = "%-10s %-56s %-5d\r\n";
|
$format = "%-10s %-56s %-5d\r\n";
|
||||||
|
|
||||||
foreach ($records as $record) {
|
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 "-- " . $queryName . " " . str_repeat("-", 70 - strlen($queryName)) . "\r\n";
|
||||||
print get_class($error) . ": " . $error->getMessage() . "\r\n";
|
print get_class($error) . ": " . $error->getMessage() . "\r\n";
|
||||||
|
@ -16,14 +16,14 @@ $domains = array_map(function ($line) {
|
|||||||
array_shift($domains);
|
array_shift($domains);
|
||||||
|
|
||||||
$DohConfig = new DoH\DoHConfig([new DoH\Nameserver('https://mozilla.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));
|
Dns\dnsResolver(new DoH\Rfc8484StubDohResolver($DohConfig));
|
||||||
|
|
||||||
print "Starting sequential queries...\r\n\r\n";
|
print "Starting sequential queries...\r\n\r\n";
|
||||||
|
|
||||||
$timings = [];
|
$timings = [];
|
||||||
|
|
||||||
for ($i = 0; $i < 10; $i++) {
|
for ($i = 0; $i < 10; $i++) {
|
||||||
$start = microtime(1);
|
$start = microtime(true);
|
||||||
$domain = $domains[random_int(0, count($domains) - 1)];
|
$domain = $domains[random_int(0, count($domains) - 1)];
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@ -32,7 +32,7 @@ for ($i = 0; $i < 10; $i++) {
|
|||||||
pretty_print_error($domain, $e);
|
pretty_print_error($domain, $e);
|
||||||
}
|
}
|
||||||
|
|
||||||
$time = round(microtime(1) - $start, 2);
|
$time = round(microtime(true) - $start, 2);
|
||||||
$timings[] = $time;
|
$timings[] = $time;
|
||||||
|
|
||||||
printf("%'-74s\r\n\r\n", " in " . $time . " ms");
|
printf("%'-74s\r\n\r\n", " in " . $time . " ms");
|
||||||
|
@ -13,7 +13,7 @@ $customConfigLoader = new class implements Dns\DnsConfigLoader {
|
|||||||
return new Dns\DnsConfig([
|
return new Dns\DnsConfig([
|
||||||
"8.8.8.8:53",
|
"8.8.8.8:53",
|
||||||
"[2001:4860:4860::8888]:53",
|
"[2001:4860:4860::8888]:53",
|
||||||
], $hosts, 5, 3);
|
], $hosts);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -28,7 +28,7 @@ $DohConfig = new DoH\DoHConfig(
|
|||||||
null,
|
null,
|
||||||
$customConfigLoader
|
$customConfigLoader
|
||||||
);
|
);
|
||||||
Dns\resolver(new DoH\Rfc8484StubResolver($DohConfig));
|
Dns\dnsResolver(new DoH\Rfc8484StubDohResolver($DohConfig));
|
||||||
|
|
||||||
$hostname = $argv[1] ?? "amphp.org";
|
$hostname = $argv[1] ?? "amphp.org";
|
||||||
|
|
||||||
|
@ -3,16 +3,17 @@
|
|||||||
require __DIR__ . "/_bootstrap.php";
|
require __DIR__ . "/_bootstrap.php";
|
||||||
|
|
||||||
use Amp\Dns;
|
use Amp\Dns;
|
||||||
|
use Amp\Dns\DnsRecord;
|
||||||
use Amp\DoH;
|
use Amp\DoH;
|
||||||
|
|
||||||
// Set default resolver to DNS-over-HTTPS resolver
|
// Set default resolver to DNS-over-HTTPS resolver
|
||||||
$DohConfig = new DoH\DoHConfig([new DoH\Nameserver('https://mozilla.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));
|
Dns\dnsResolver(new DoH\Rfc8484StubDohResolver($DohConfig));
|
||||||
|
|
||||||
$ip = $argv[1] ?? "8.8.8.8";
|
$ip = $argv[1] ?? "8.8.8.8";
|
||||||
|
|
||||||
try {
|
try {
|
||||||
pretty_print_records($ip, Dns\query($ip, Dns\Record::PTR));
|
pretty_print_records($ip, Dns\query($ip, DnsRecord::PTR));
|
||||||
} catch (Dns\DnsException $e) {
|
} catch (Dns\DnsException $e) {
|
||||||
pretty_print_error($ip, $e);
|
pretty_print_error($ip, $e);
|
||||||
}
|
}
|
||||||
|
@ -4,9 +4,10 @@ namespace Amp\DoH;
|
|||||||
|
|
||||||
use Amp\Cache\Cache;
|
use Amp\Cache\Cache;
|
||||||
use Amp\Cache\LocalCache;
|
use Amp\Cache\LocalCache;
|
||||||
use Amp\Dns\ConfigException;
|
use Amp\Dns\DnsConfigException;
|
||||||
use Amp\Dns\DnsConfigLoader;
|
use Amp\Dns\DnsConfigLoader;
|
||||||
use Amp\Dns\Rfc1035StubResolver;
|
use Amp\Dns\DnsResolver;
|
||||||
|
use Amp\Dns\Rfc1035StubDnsResolver;
|
||||||
use Amp\Dns\UnixDnsConfigLoader;
|
use Amp\Dns\UnixDnsConfigLoader;
|
||||||
use Amp\Dns\WindowsDnsConfigLoader;
|
use Amp\Dns\WindowsDnsConfigLoader;
|
||||||
use Amp\Http\Client\DelegateHttpClient;
|
use Amp\Http\Client\DelegateHttpClient;
|
||||||
@ -19,24 +20,24 @@ final class DoHConfig
|
|||||||
*/
|
*/
|
||||||
private readonly array $nameservers;
|
private readonly array $nameservers;
|
||||||
private readonly DelegateHttpClient $httpClient;
|
private readonly DelegateHttpClient $httpClient;
|
||||||
private readonly Rfc1035StubResolver $subResolver;
|
private readonly DnsResolver $subResolver;
|
||||||
private readonly DnsConfigLoader $configLoader;
|
private readonly DnsConfigLoader $configLoader;
|
||||||
private readonly Cache $cache;
|
private readonly Cache $cache;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param non-empty-array<Nameserver> $nameservers
|
* @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 */
|
/** @psalm-suppress TypeDoesNotContainType */
|
||||||
if (\count($nameservers) < 1) {
|
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) {
|
foreach ($nameservers as $nameserver) {
|
||||||
/** @psalm-suppress DocblockContradiction */
|
/** @psalm-suppress DocblockContradiction */
|
||||||
if (!($nameserver instanceof Nameserver)) {
|
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
|
$this->configLoader = $configLoader ?? (\stripos(PHP_OS, "win") === 0
|
||||||
? new WindowsDnsConfigLoader()
|
? new WindowsDnsConfigLoader()
|
||||||
: new UnixDnsConfigLoader());
|
: 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;
|
return $this->configLoader;
|
||||||
}
|
}
|
||||||
public function getSubResolver(): Rfc1035StubResolver
|
public function getSubResolver(): DnsResolver
|
||||||
{
|
{
|
||||||
return $this->subResolver;
|
return $this->subResolver;
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
namespace Amp\DoH;
|
namespace Amp\DoH;
|
||||||
|
|
||||||
use Amp\Dns\ConfigException;
|
use Amp\Dns\DnsConfigException;
|
||||||
|
|
||||||
final class Nameserver
|
final class Nameserver
|
||||||
{
|
{
|
||||||
@ -18,7 +18,7 @@ final class Nameserver
|
|||||||
private readonly array $headers = []
|
private readonly array $headers = []
|
||||||
) {
|
) {
|
||||||
if (\parse_url($uri, PHP_URL_SCHEME) !== 'https') {
|
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);
|
$this->host = \parse_url($uri, PHP_URL_HOST);
|
||||||
}
|
}
|
||||||
|
@ -5,15 +5,15 @@ namespace Amp\DoH;
|
|||||||
use Amp\Cache\Cache;
|
use Amp\Cache\Cache;
|
||||||
use Amp\Cancellation;
|
use Amp\Cancellation;
|
||||||
use Amp\CompositeException;
|
use Amp\CompositeException;
|
||||||
use Amp\Dns\ConfigException;
|
|
||||||
use Amp\Dns\DnsConfig;
|
use Amp\Dns\DnsConfig;
|
||||||
|
use Amp\Dns\DnsConfigException;
|
||||||
use Amp\Dns\DnsConfigLoader;
|
use Amp\Dns\DnsConfigLoader;
|
||||||
use Amp\Dns\DnsException;
|
use Amp\Dns\DnsException;
|
||||||
use Amp\Dns\NoRecordException;
|
use Amp\Dns\DnsRecord;
|
||||||
use Amp\Dns\Record;
|
use Amp\Dns\DnsResolver;
|
||||||
use Amp\Dns\Resolver;
|
use Amp\Dns\DnsTimeoutException;
|
||||||
use Amp\Dns\Rfc1035StubResolver;
|
use Amp\Dns\MissingDnsRecordException;
|
||||||
use Amp\Dns\TimeoutException;
|
use Amp\Dns\Rfc1035StubDnsResolver;
|
||||||
use Amp\Future;
|
use Amp\Future;
|
||||||
use Amp\Http\Client\DelegateHttpClient;
|
use Amp\Http\Client\DelegateHttpClient;
|
||||||
use Amp\Http\Client\Request;
|
use Amp\Http\Client\Request;
|
||||||
@ -35,7 +35,7 @@ use LibDNS\Records\QuestionFactory;
|
|||||||
use function Amp\async;
|
use function Amp\async;
|
||||||
use function Amp\Dns\normalizeName;
|
use function Amp\Dns\normalizeName;
|
||||||
|
|
||||||
final class Rfc8484StubResolver implements Resolver
|
final class Rfc8484StubDohResolver implements DnsResolver
|
||||||
{
|
{
|
||||||
const CACHE_PREFIX = "amphp.doh.";
|
const CACHE_PREFIX = "amphp.doh.";
|
||||||
|
|
||||||
@ -50,7 +50,7 @@ final class Rfc8484StubResolver implements Resolver
|
|||||||
/** @var Future[] */
|
/** @var Future[] */
|
||||||
private array $pendingQueries = [];
|
private array $pendingQueries = [];
|
||||||
|
|
||||||
private Rfc1035StubResolver $subResolver;
|
private DnsResolver $subResolver;
|
||||||
private Encoder $encoder;
|
private Encoder $encoder;
|
||||||
private Decoder $decoder;
|
private Decoder $decoder;
|
||||||
private QueryEncoder $encoderJson;
|
private QueryEncoder $encoderJson;
|
||||||
@ -75,31 +75,31 @@ final class Rfc8484StubResolver implements Resolver
|
|||||||
/** @inheritdoc */
|
/** @inheritdoc */
|
||||||
public function resolve(string $name, int $typeRestriction = null, ?Cancellation $cancellation = null): array
|
public function resolve(string $name, int $typeRestriction = null, ?Cancellation $cancellation = null): array
|
||||||
{
|
{
|
||||||
if ($typeRestriction !== null && $typeRestriction !== Record::A && $typeRestriction !== Record::AAAA) {
|
if ($typeRestriction !== null && $typeRestriction !== DnsRecord::A && $typeRestriction !== DnsRecord::AAAA) {
|
||||||
throw new \Error("Invalid value for parameter 2: null|Record::A|Record::AAAA expected");
|
throw new \Error("Invalid value for parameter 2: null|DnsRecord::A|DnsRecord::AAAA expected");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!$this->config) {
|
if (!$this->config) {
|
||||||
try {
|
try {
|
||||||
$this->reloadConfig();
|
$this->reloadConfig();
|
||||||
} catch (ConfigException $e) {
|
} catch (DnsConfigException $e) {
|
||||||
$this->config = new DnsConfig(['0.0.0.0'], []);
|
$this->config = new DnsConfig(['0.0.0.0'], []);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
switch ($typeRestriction) {
|
switch ($typeRestriction) {
|
||||||
case Record::A:
|
case DnsRecord::A:
|
||||||
if (\filter_var($name, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) {
|
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)) {
|
if (\filter_var($name, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) {
|
||||||
throw new DnsException("Got an IPv6 address, but type is restricted to IPv4");
|
throw new DnsException("Got an IPv6 address, but type is restricted to IPv4");
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case Record::AAAA:
|
case DnsRecord::AAAA:
|
||||||
if (\filter_var($name, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) {
|
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)) {
|
if (\filter_var($name, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) {
|
||||||
@ -108,11 +108,11 @@ final class Rfc8484StubResolver implements Resolver
|
|||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
if (\filter_var($name, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) {
|
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)) {
|
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;
|
break;
|
||||||
}
|
}
|
||||||
@ -128,9 +128,9 @@ final class Rfc8484StubResolver implements Resolver
|
|||||||
// Follow RFC 6761 and never send queries for localhost to the caching DNS server
|
// Follow RFC 6761 and never send queries for localhost to the caching DNS server
|
||||||
// Usually, these queries are already resolved via queryHosts()
|
// Usually, these queries are already resolved via queryHosts()
|
||||||
if ($name === 'localhost') {
|
if ($name === 'localhost') {
|
||||||
return $typeRestriction === Record::AAAA
|
return $typeRestriction === DnsRecord::AAAA
|
||||||
? [new Record('::1', Record::AAAA, null)]
|
? [new DnsRecord('::1', DnsRecord::AAAA, null)]
|
||||||
: [new Record('127.0.0.1', Record::A, null)];
|
: [new DnsRecord('127.0.0.1', DnsRecord::A, null)];
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($this->dohConfig->isNameserver($name)) {
|
if ($this->dohConfig->isNameserver($name)) {
|
||||||
@ -157,15 +157,15 @@ final class Rfc8484StubResolver implements Resolver
|
|||||||
}
|
}
|
||||||
|
|
||||||
[$exceptions, $records] = Future\awaitAll([
|
[$exceptions, $records] = Future\awaitAll([
|
||||||
async(fn () => $this->query($searchName, Record::A, $cancellation)),
|
async(fn () => $this->query($searchName, DnsRecord::A, $cancellation)),
|
||||||
async(fn () => $this->query($searchName, Record::AAAA, $cancellation)),
|
async(fn () => $this->query($searchName, DnsRecord::AAAA, $cancellation)),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
if (\count($exceptions) === 2) {
|
if (\count($exceptions) === 2) {
|
||||||
$errors = [];
|
$errors = [];
|
||||||
|
|
||||||
foreach ($exceptions as $reason) {
|
foreach ($exceptions as $reason) {
|
||||||
if ($reason instanceof NoRecordException) {
|
if ($reason instanceof MissingDnsRecordException) {
|
||||||
throw $reason;
|
throw $reason;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -184,13 +184,13 @@ final class Rfc8484StubResolver implements Resolver
|
|||||||
}
|
}
|
||||||
|
|
||||||
return \array_merge(...$records);
|
return \array_merge(...$records);
|
||||||
} catch (NoRecordException) {
|
} catch (MissingDnsRecordException) {
|
||||||
try {
|
try {
|
||||||
$cnameRecords = $this->query($searchName, Record::CNAME, $cancellation);
|
$cnameRecords = $this->query($searchName, DnsRecord::CNAME, $cancellation);
|
||||||
$name = $cnameRecords[0]->getValue();
|
$name = $cnameRecords[0]->getValue();
|
||||||
continue;
|
continue;
|
||||||
} catch (NoRecordException) {
|
} catch (MissingDnsRecordException) {
|
||||||
$dnameRecords = $this->query($searchName, Record::DNAME, $cancellation);
|
$dnameRecords = $this->query($searchName, DnsRecord::DNAME, $cancellation);
|
||||||
$name = $dnameRecords[0]->getValue();
|
$name = $dnameRecords[0]->getValue();
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@ -219,7 +219,9 @@ final class Rfc8484StubResolver implements Resolver
|
|||||||
if (!$this->pendingConfig) {
|
if (!$this->pendingConfig) {
|
||||||
$promise = async(function () {
|
$promise = async(function () {
|
||||||
try {
|
try {
|
||||||
$this->subResolver->reloadConfig();
|
if ($this->subResolver instanceof Rfc1035StubDnsResolver) {
|
||||||
|
$this->subResolver->reloadConfig();
|
||||||
|
}
|
||||||
$this->config = $this->configLoader->loadConfig();
|
$this->config = $this->configLoader->loadConfig();
|
||||||
} finally {
|
} finally {
|
||||||
$this->pendingConfig = null;
|
$this->pendingConfig = null;
|
||||||
@ -237,15 +239,15 @@ final class Rfc8484StubResolver implements Resolver
|
|||||||
$hosts = $this->config->getKnownHosts();
|
$hosts = $this->config->getKnownHosts();
|
||||||
$records = [];
|
$records = [];
|
||||||
|
|
||||||
$returnIPv4 = $typeRestriction === null || $typeRestriction === Record::A;
|
$returnIPv4 = $typeRestriction === null || $typeRestriction === DnsRecord::A;
|
||||||
$returnIPv6 = $typeRestriction === null || $typeRestriction === Record::AAAA;
|
$returnIPv6 = $typeRestriction === null || $typeRestriction === DnsRecord::AAAA;
|
||||||
|
|
||||||
if ($returnIPv4 && isset($hosts[Record::A][$name])) {
|
if ($returnIPv4 && isset($hosts[DnsRecord::A][$name])) {
|
||||||
$records[] = new Record($hosts[Record::A][$name], Record::A, null);
|
$records[] = new DnsRecord($hosts[DnsRecord::A][$name], DnsRecord::A, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($returnIPv6 && isset($hosts[Record::AAAA][$name])) {
|
if ($returnIPv6 && isset($hosts[DnsRecord::AAAA][$name])) {
|
||||||
$records[] = new Record($hosts[Record::AAAA][$name], Record::AAAA, null);
|
$records[] = new DnsRecord($hosts[DnsRecord::AAAA][$name], DnsRecord::AAAA, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
return $records;
|
return $records;
|
||||||
@ -266,7 +268,7 @@ final class Rfc8484StubResolver implements Resolver
|
|||||||
if (!$this->config) {
|
if (!$this->config) {
|
||||||
try {
|
try {
|
||||||
$this->reloadConfig();
|
$this->reloadConfig();
|
||||||
} catch (ConfigException $e) {
|
} catch (DnsConfigException $e) {
|
||||||
$this->config = new DnsConfig(['0.0.0.0'], []);
|
$this->config = new DnsConfig(['0.0.0.0'], []);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -296,7 +298,7 @@ final class Rfc8484StubResolver implements Resolver
|
|||||||
$this->assertAcceptableResponse($response);
|
$this->assertAcceptableResponse($response);
|
||||||
|
|
||||||
if ($response->isTruncated()) {
|
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();
|
$answers = $response->getAnswerRecords();
|
||||||
@ -319,22 +321,22 @@ final class Rfc8484StubResolver implements Resolver
|
|||||||
if (!isset($result[$type])) {
|
if (!isset($result[$type])) {
|
||||||
// "it MUST NOT cache it for longer than five (5) minutes" per RFC 2308 section 7.1
|
// "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);
|
$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 \array_map(function ($data) use ($type, $ttls) {
|
||||||
return new Record($data, $type, $ttls[$type]);
|
return new DnsRecord($data, $type, $ttls[$type]);
|
||||||
}, $result[$type]);
|
}, $result[$type]);
|
||||||
} catch (TimeoutException) {
|
} catch (DnsTimeoutException) {
|
||||||
$i = ++$attempt % \count($nameservers);
|
$i = ++$attempt % \count($nameservers);
|
||||||
$nameserver = $nameservers[$i];
|
$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",
|
"No response for '%s' (%s) from any nameserver within %d ms after %d attempts, tried %s",
|
||||||
$name,
|
$name,
|
||||||
Record::getName($type),
|
DnsRecord::getName($type),
|
||||||
$this->config->getTimeout(),
|
$this->config->getTimeout(),
|
||||||
$attempts,
|
$attempts,
|
||||||
\implode(", ", $attemptDescription)
|
\implode(", ", $attemptDescription)
|
||||||
@ -395,7 +397,7 @@ final class Rfc8484StubResolver implements Resolver
|
|||||||
|
|
||||||
private function normalizeName(string $name, int $type): string
|
private function normalizeName(string $name, int $type): string
|
||||||
{
|
{
|
||||||
if ($type === Record::PTR) {
|
if ($type === DnsRecord::PTR) {
|
||||||
if (($packedIp = @\inet_pton($name)) !== false) {
|
if (($packedIp = @\inet_pton($name)) !== false) {
|
||||||
if (isset($packedIp[4])) { // IPv6
|
if (isset($packedIp[4])) { // IPv6
|
||||||
$name = \wordwrap(\strrev(\bin2hex($packedIp)), 1, ".", true).".ip6.arpa";
|
$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";
|
$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);
|
$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
|
private function decodeCachedResult(string $name, int $type, string $encoded): array
|
||||||
{
|
{
|
||||||
$decoded = \json_decode($encoded, true);
|
$decoded = \json_decode($encoded, true);
|
||||||
|
|
||||||
if (!$decoded) {
|
if (!$decoded) {
|
||||||
throw new NoRecordException("No records returned for {$name} (cached result)");
|
throw new MissingDnsRecordException("No records returned for {$name} (cached result)");
|
||||||
}
|
}
|
||||||
|
|
||||||
$result = [];
|
$result = [];
|
||||||
|
|
||||||
foreach ($decoded as $data) {
|
foreach ($decoded as $data) {
|
||||||
$result[] = new Record($data, $type);
|
$result[] = new DnsRecord($data, $type);
|
||||||
}
|
}
|
||||||
|
|
||||||
return $result;
|
return $result;
|
@ -8,6 +8,8 @@
|
|||||||
>
|
>
|
||||||
<projectFiles>
|
<projectFiles>
|
||||||
<directory name="lib" />
|
<directory name="lib" />
|
||||||
|
<directory name="test" />
|
||||||
|
<directory name="examples" />
|
||||||
<ignoreFiles>
|
<ignoreFiles>
|
||||||
<directory name="vendor" />
|
<directory name="vendor" />
|
||||||
</ignoreFiles>
|
</ignoreFiles>
|
||||||
|
@ -2,30 +2,38 @@
|
|||||||
|
|
||||||
namespace Amp\DoH\Test;
|
namespace Amp\DoH\Test;
|
||||||
|
|
||||||
|
use Amp\Cache\Cache;
|
||||||
use Amp\Cache\LocalCache;
|
use Amp\Cache\LocalCache;
|
||||||
use Amp\Dns\ConfigException;
|
use Amp\Dns\DnsConfigException;
|
||||||
use Amp\Dns\Rfc1035StubResolver;
|
use Amp\Dns\DnsConfigLoader;
|
||||||
|
use Amp\Dns\DnsResolver;
|
||||||
|
use Amp\Dns\Rfc1035StubDnsResolver;
|
||||||
use Amp\Dns\UnixDnsConfigLoader;
|
use Amp\Dns\UnixDnsConfigLoader;
|
||||||
use Amp\Dns\WindowsDnsConfigLoader;
|
use Amp\Dns\WindowsDnsConfigLoader;
|
||||||
use Amp\DoH\DoHConfig;
|
use Amp\DoH\DoHConfig;
|
||||||
use Amp\DoH\Nameserver;
|
use Amp\DoH\Nameserver;
|
||||||
use Amp\DoH\NameserverType;
|
use Amp\DoH\NameserverType;
|
||||||
|
use Amp\Http\Client\HttpClient;
|
||||||
use Amp\Http\Client\HttpClientBuilder;
|
use Amp\Http\Client\HttpClientBuilder;
|
||||||
use Amp\PHPUnit\AsyncTestCase;
|
use Amp\PHPUnit\AsyncTestCase;
|
||||||
|
|
||||||
|
/** @psalm-suppress PropertyNotSetInConstructor */
|
||||||
class DoHConfigTest extends AsyncTestCase
|
class DoHConfigTest extends AsyncTestCase
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* @param string[] $nameservers Valid server array.
|
* @param non-empty-list<NameServer> $nameservers Valid server array.
|
||||||
*
|
*
|
||||||
* @dataProvider provideValidServers
|
* @dataProvider provideValidServers
|
||||||
*/
|
*/
|
||||||
public function testAcceptsValidServers(array $nameservers)
|
public function testAcceptsValidServers(array $nameservers): void
|
||||||
{
|
{
|
||||||
$this->assertInstanceOf(DoHConfig::class, new DoHConfig($nameservers));
|
$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 [
|
return [
|
||||||
[[new Nameserver('https://cloudflare-dns.com/dns-query')]],
|
[[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
|
* @dataProvider provideInvalidServers
|
||||||
*/
|
*/
|
||||||
public function testRejectsInvalidServers(array $nameservers)
|
public function testRejectsInvalidServers(array $nameservers): void
|
||||||
{
|
{
|
||||||
$this->expectException(ConfigException::class);
|
$this->expectException(DnsConfigException::class);
|
||||||
new DoHConfig($nameservers);
|
new DoHConfig($nameservers);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return list<list{mixed}>
|
||||||
|
*/
|
||||||
public function provideInvalidServers()
|
public function provideInvalidServers()
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
@ -75,47 +86,50 @@ class DoHConfigTest extends AsyncTestCase
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param \Amp\Http\Client\DelegateHttpClient $client Valid HttpClient instance
|
|
||||||
*
|
|
||||||
* @dataProvider provideValidHttpClient
|
* @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));
|
$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 [
|
return [
|
||||||
[HttpClientBuilder::buildDefault()],
|
[HttpClientBuilder::buildDefault()],
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* @param \Amp\Dns\Resolver $resolver Valid resolver instance
|
|
||||||
*
|
|
||||||
* @dataProvider provideValidResolver
|
* @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));
|
$this->assertInstanceOf(DoHConfig::class, new DoHConfig([new Nameserver('https://cloudflare-dns.com/dns-query')], null, $resolver));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return list<list{DnsResolver}>
|
||||||
|
*/
|
||||||
public function provideValidResolver()
|
public function provideValidResolver()
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
[new Rfc1035StubResolver()],
|
[new Rfc1035StubDnsResolver()],
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* @param $configLoader \Amp\Dns\ConfigLoader Valid ConfigLoader instance
|
|
||||||
*
|
|
||||||
* @dataProvider provideValidConfigLoader
|
* @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));
|
$this->assertInstanceOf(DoHConfig::class, new DoHConfig([new Nameserver('https://cloudflare-dns.com/dns-query')], null, null, $configLoader));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return list<list{DnsConfigLoader}>
|
||||||
|
*/
|
||||||
public function provideValidConfigLoader()
|
public function provideValidConfigLoader()
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
@ -125,15 +139,16 @@ class DoHConfigTest extends AsyncTestCase
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* @param \Amp\Cache\Cache Valid cache instance
|
|
||||||
*
|
|
||||||
* @dataProvider provideValidCache
|
* @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));
|
$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()
|
public function provideValidCache()
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
|
@ -3,31 +3,31 @@
|
|||||||
namespace Amp\DoH\Test;
|
namespace Amp\DoH\Test;
|
||||||
|
|
||||||
use Amp\Dns;
|
use Amp\Dns;
|
||||||
use Amp\Dns\Record;
|
use Amp\Dns\DnsRecord;
|
||||||
use Amp\DoH;
|
use Amp\DoH;
|
||||||
use Amp\DoH\Nameserver;
|
use Amp\DoH\Nameserver;
|
||||||
use Amp\DoH\NameserverType;
|
use Amp\DoH\NameserverType;
|
||||||
use Amp\PHPUnit\AsyncTestCase;
|
use Amp\PHPUnit\AsyncTestCase;
|
||||||
|
|
||||||
use function Amp\delay;
|
use function Amp\delay;
|
||||||
|
use function Amp\Dns\dnsResolver;
|
||||||
|
|
||||||
|
/** @psalm-suppress PropertyNotSetInConstructor */
|
||||||
class IntegrationTest extends AsyncTestCase
|
class IntegrationTest extends AsyncTestCase
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* @param string $hostname
|
* @param non-empty-list<list{0: string, 1?: NameserverType}> $nameservers
|
||||||
* @group internet
|
* @group internet
|
||||||
* @dataProvider provideServersAndHostnames
|
* @dataProvider provideServersAndHostnames
|
||||||
*/
|
*/
|
||||||
public function testResolve($hostname, $nameservers)
|
public function testResolve(string $hostname, array $nameservers): void
|
||||||
{
|
{
|
||||||
foreach ($nameservers as &$nameserver) {
|
$nameservers = \array_map(fn (array $v) => new Nameserver(...$v), $nameservers);
|
||||||
$nameserver = new Nameserver(...$nameserver);
|
|
||||||
}
|
|
||||||
$DohConfig = new DoH\DoHConfig($nameservers);
|
$DohConfig = new DoH\DoHConfig($nameservers);
|
||||||
Dns\resolver(new DoH\Rfc8484StubResolver($DohConfig));
|
dnsResolver(new DoH\Rfc8484StubDohResolver($DohConfig));
|
||||||
$result = Dns\resolve($hostname);
|
$result = Dns\resolve($hostname);
|
||||||
|
|
||||||
/** @var Record $record */
|
/** @var DnsRecord $record */
|
||||||
$record = $result[0];
|
$record = $result[0];
|
||||||
$inAddr = @\inet_pton($record->getValue());
|
$inAddr = @\inet_pton($record->getValue());
|
||||||
$this->assertNotFalse(
|
$this->assertNotFalse(
|
||||||
@ -38,42 +38,40 @@ class IntegrationTest extends AsyncTestCase
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* @param non-empty-list<list{0: string, 1?: NameserverType}> $nameservers
|
||||||
* @group internet
|
* @group internet
|
||||||
* @dataProvider provideServers
|
* @dataProvider provideServers
|
||||||
*/
|
*/
|
||||||
public function testWorksAfterConfigReload($nameservers)
|
public function testWorksAfterConfigReload($nameservers): void
|
||||||
{
|
{
|
||||||
foreach ($nameservers as &$nameserver) {
|
$nameservers = \array_map(fn (array $v) => new Nameserver(...$v), $nameservers);
|
||||||
$nameserver = new Nameserver(...$nameserver);
|
|
||||||
}
|
|
||||||
$DohConfig = new DoH\DoHConfig($nameservers);
|
$DohConfig = new DoH\DoHConfig($nameservers);
|
||||||
Dns\resolver(new DoH\Rfc8484StubResolver($DohConfig));
|
dnsResolver(new DoH\Rfc8484StubDohResolver($DohConfig));
|
||||||
|
|
||||||
Dns\resolve('google.com');
|
Dns\resolve('google.com');
|
||||||
$this->assertNull(Dns\resolver()->reloadConfig());
|
/** @psalm-suppress UndefinedInterfaceMethod */
|
||||||
|
$this->assertNull(dnsResolver()->reloadConfig());
|
||||||
delay(0.5);
|
delay(0.5);
|
||||||
$result = \is_array(Dns\resolve('google.com'));
|
$this->assertIsArray(Dns\resolve('google.com'));
|
||||||
$this->assertTrue($result);
|
|
||||||
\usleep(500*1000);
|
\usleep(500*1000);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* @param non-empty-list<list{0: string, 1?: NameserverType}> $nameservers
|
||||||
* @group internet
|
* @group internet
|
||||||
* @dataProvider provideServers
|
* @dataProvider provideServers
|
||||||
*/
|
*/
|
||||||
public function testResolveIPv4only($nameservers)
|
public function testResolveIPv4only($nameservers): void
|
||||||
{
|
{
|
||||||
foreach ($nameservers as &$nameserver) {
|
$nameservers = \array_map(fn (array $v) => new Nameserver(...$v), $nameservers);
|
||||||
$nameserver = new Nameserver(...$nameserver);
|
|
||||||
}
|
|
||||||
$DohConfig = new DoH\DoHConfig($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) {
|
foreach ($records as $record) {
|
||||||
$this->assertSame(Record::A, $record->getType());
|
$this->assertSame(DnsRecord::A, $record->getType());
|
||||||
$inAddr = @\inet_pton($record->getValue());
|
$inAddr = @\inet_pton($record->getValue());
|
||||||
$this->assertNotFalse(
|
$this->assertNotFalse(
|
||||||
$inAddr,
|
$inAddr,
|
||||||
@ -84,22 +82,21 @@ class IntegrationTest extends AsyncTestCase
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* @param non-empty-list<list{0: string, 1?: NameserverType}> $nameservers
|
||||||
* @group internet
|
* @group internet
|
||||||
* @dataProvider provideServers
|
* @dataProvider provideServers
|
||||||
*/
|
*/
|
||||||
public function testResolveIPv6only($nameservers)
|
public function testResolveIPv6only($nameservers): void
|
||||||
{
|
{
|
||||||
foreach ($nameservers as &$nameserver) {
|
$nameservers = \array_map(fn (array $v) => new Nameserver(...$v), $nameservers);
|
||||||
$nameserver = new Nameserver(...$nameserver);
|
|
||||||
}
|
|
||||||
$DohConfig = new DoH\DoHConfig($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) {
|
foreach ($records as $record) {
|
||||||
$this->assertSame(Record::AAAA, $record->getType());
|
$this->assertSame(DnsRecord::AAAA, $record->getType());
|
||||||
$inAddr = @\inet_pton($record->getValue());
|
$inAddr = @\inet_pton($record->getValue());
|
||||||
$this->assertNotFalse(
|
$this->assertNotFalse(
|
||||||
$inAddr,
|
$inAddr,
|
||||||
@ -110,41 +107,41 @@ class IntegrationTest extends AsyncTestCase
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* @param non-empty-list<list{0: string, 1?: NameserverType}> $nameservers
|
||||||
* @group internet
|
* @group internet
|
||||||
* @dataProvider provideServers
|
* @dataProvider provideServers
|
||||||
*/
|
*/
|
||||||
public function testPtrLookup($nameservers)
|
public function testPtrLookup($nameservers): void
|
||||||
{
|
{
|
||||||
foreach ($nameservers as &$nameserver) {
|
$nameservers = \array_map(fn (array $v) => new Nameserver(...$v), $nameservers);
|
||||||
$nameserver = new Nameserver(...$nameserver);
|
|
||||||
}
|
|
||||||
$DohConfig = new DoH\DoHConfig($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];
|
$record = $result[0];
|
||||||
$this->assertSame("dns.google", $record->getValue());
|
$this->assertSame("dns.google", $record->getValue());
|
||||||
$this->assertNotNull($record->getTtl());
|
$this->assertNotNull($record->getTtl());
|
||||||
$this->assertSame(Record::PTR, $record->getType());
|
$this->assertSame(DnsRecord::PTR, $record->getType());
|
||||||
\usleep(500*1000);
|
\usleep(500*1000);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return iterable<list{string, list<list{0: string, 1?: NameserverType}>}>
|
||||||
|
*/
|
||||||
public function provideServersAndHostnames()
|
public function provideServersAndHostnames()
|
||||||
{
|
{
|
||||||
$hostnames = $this->provideHostnames();
|
foreach ($this->provideHostnames() as [$hostname]) {
|
||||||
$servers = $this->provideServers();
|
foreach ($this->provideServers() as [$nameserver]) {
|
||||||
$result = [];
|
yield [$hostname, $nameserver];
|
||||||
foreach ($hostnames as $args) {
|
|
||||||
$hostname = $args[0];
|
|
||||||
foreach ($servers as $args) {
|
|
||||||
$nameserver = $args[0];
|
|
||||||
$result[] = [$hostname, $nameserver];
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return $result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return list<list{string}>
|
||||||
|
*/
|
||||||
public function provideHostnames()
|
public function provideHostnames()
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
@ -160,6 +157,9 @@ class IntegrationTest extends AsyncTestCase
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return iterable<int, list{list<list{0: string, 1?: NameserverType}>}>
|
||||||
|
*/
|
||||||
public function provideServers()
|
public function provideServers()
|
||||||
{
|
{
|
||||||
$nameservers = [
|
$nameservers = [
|
||||||
@ -169,7 +169,6 @@ class IntegrationTest extends AsyncTestCase
|
|||||||
['https://mozilla.cloudflare-dns.com/dns-query', NameserverType::GOOGLE_JSON],
|
['https://mozilla.cloudflare-dns.com/dns-query', NameserverType::GOOGLE_JSON],
|
||||||
['https://dns.google/resolve', NameserverType::GOOGLE_JSON],
|
['https://dns.google/resolve', NameserverType::GOOGLE_JSON],
|
||||||
];
|
];
|
||||||
$result = [];
|
|
||||||
for ($start = 0; $start < \count($nameservers); $start++) {
|
for ($start = 0; $start < \count($nameservers); $start++) {
|
||||||
$temp = [];
|
$temp = [];
|
||||||
for ($i = 0; $i < \count($nameservers); $i++) {
|
for ($i = 0; $i < \count($nameservers); $i++) {
|
||||||
@ -177,9 +176,7 @@ class IntegrationTest extends AsyncTestCase
|
|||||||
|
|
||||||
$temp[] = $nameservers[$i];
|
$temp[] = $nameservers[$i];
|
||||||
}
|
}
|
||||||
$result[] = [$temp];
|
yield [$temp];
|
||||||
}
|
}
|
||||||
|
|
||||||
return $result;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,22 +2,25 @@
|
|||||||
|
|
||||||
namespace Amp\DoH\Test;
|
namespace Amp\DoH\Test;
|
||||||
|
|
||||||
use Amp\Dns\ConfigException;
|
use Amp\Dns\DnsConfigException;
|
||||||
use Amp\DoH\Nameserver;
|
use Amp\DoH\Nameserver;
|
||||||
use Amp\DoH\NameserverType;
|
use Amp\DoH\NameserverType;
|
||||||
use Amp\PHPUnit\AsyncTestCase;
|
use Amp\PHPUnit\AsyncTestCase;
|
||||||
|
|
||||||
|
/** @psalm-suppress PropertyNotSetInConstructor */
|
||||||
class NameserverTest extends AsyncTestCase
|
class NameserverTest extends AsyncTestCase
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
*
|
|
||||||
* @dataProvider provideValidServers
|
* @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));
|
$this->assertInstanceOf(Nameserver::class, new Nameserver($nameserver, $type, $headers));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return list<list{0: string, 1?: NameserverType::RFC8484_POST}>
|
||||||
|
*/
|
||||||
public function provideValidServers()
|
public function provideValidServers()
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
@ -30,16 +33,18 @@ class NameserverTest extends AsyncTestCase
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
|
||||||
* @dataProvider provideInvalidServers
|
* @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);
|
new Nameserver($nameserver, $type, $headers);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function provideInvalidServers()
|
/**
|
||||||
|
* @return list<list{0: string, 1?: NameserverType::RFC8484_POST}>
|
||||||
|
*/
|
||||||
|
public function provideInvalidServers(): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
[''],
|
[''],
|
||||||
|
@ -3,50 +3,51 @@
|
|||||||
namespace Amp\DoH\Test;
|
namespace Amp\DoH\Test;
|
||||||
|
|
||||||
use Amp\Dns\DnsException;
|
use Amp\Dns\DnsException;
|
||||||
|
use Amp\Dns\DnsRecord;
|
||||||
use Amp\Dns\InvalidNameException;
|
use Amp\Dns\InvalidNameException;
|
||||||
use Amp\Dns\Record;
|
use Amp\Dns\Rfc1035StubDnsResolver;
|
||||||
use Amp\Dns\Rfc1035StubResolver;
|
|
||||||
use Amp\DoH;
|
use Amp\DoH;
|
||||||
use Amp\DoH\Rfc8484StubResolver;
|
use Amp\DoH\Rfc8484StubDohResolver;
|
||||||
use Amp\PHPUnit\AsyncTestCase;
|
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')]);
|
$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->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->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->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->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());
|
$DohConfig = new DoH\DoHConfig([new DoH\Nameserver('https://mozilla.cloudflare-dns.com/dns-query')], null, new Rfc1035StubDnsResolver());
|
||||||
$this->assertInstanceOf(Rfc8484StubResolver::class, new Rfc8484StubResolver($DohConfig));
|
$this->assertInstanceOf(Rfc8484StubDohResolver::class, new Rfc8484StubDohResolver($DohConfig));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testInvalidNameserverFallback()
|
public function testInvalidNameserverFallback(): void
|
||||||
{
|
{
|
||||||
$DohConfig = new DoH\DoHConfig(
|
$DohConfig = new DoH\DoHConfig(
|
||||||
[
|
[
|
||||||
@ -56,8 +57,8 @@ class Rfc8484StubResolverTest extends AsyncTestCase
|
|||||||
new DoH\Nameserver('https://mozilla.cloudflare-dns.com/dns-query'),
|
new DoH\Nameserver('https://mozilla.cloudflare-dns.com/dns-query'),
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
$resolver = new Rfc8484StubResolver($DohConfig);
|
$resolver = new Rfc8484StubDohResolver($DohConfig);
|
||||||
$this->assertInstanceOf(Rfc8484StubResolver::class, $resolver);
|
$this->assertInstanceOf(Rfc8484StubDohResolver::class, $resolver);
|
||||||
$resolver->resolve('google.com');
|
$resolver->resolve('google.com');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user