mirror of
https://github.com/danog/dns.git
synced 2024-11-26 20:14:51 +01:00
Improve alias resolution for non-recursive servers
This commit is contained in:
parent
64b8e3af6b
commit
dcb6ebb2bb
@ -148,7 +148,7 @@ final class BasicResolver implements Resolver {
|
||||
return $records;
|
||||
}
|
||||
|
||||
for ($redirects = 0; $redirects < 5; $redirects++) {
|
||||
for ($redirects = 0; $redirects < $this->config->getRecursionDepth(); $redirects++) {
|
||||
try {
|
||||
if ($typeRestriction) {
|
||||
$records = yield $this->query($name, $typeRestriction);
|
||||
@ -173,19 +173,13 @@ final class BasicResolver implements Resolver {
|
||||
}
|
||||
}
|
||||
} catch (NoRecordException $e) {
|
||||
try {
|
||||
/** @var Record[] $cnameRecords */
|
||||
$cnameRecords = yield $this->query($name, Record::CNAME);
|
||||
/** @var ResourceData\CNAME $cname */
|
||||
$cname = $cnameRecords[0]->getValue();
|
||||
$name = $cname->getCanonicalName();
|
||||
continue;
|
||||
} catch (NoRecordException $e) {
|
||||
/** @var Record[] $dnameRecords */
|
||||
$dnameRecords = yield $this->query($name, Record::DNAME);
|
||||
$name = $dnameRecords[0]->getValue();
|
||||
continue;
|
||||
// If the query was recursive on the server side, there's no point in trying to recurse ourselves
|
||||
if ($e->wasRecursiveQuery()) {
|
||||
break;
|
||||
}
|
||||
|
||||
$name = yield from $this->getCanonicalName($name);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
@ -193,6 +187,64 @@ final class BasicResolver implements Resolver {
|
||||
});
|
||||
}
|
||||
|
||||
private function resolveCname(DomainName $name) {
|
||||
/** @var Record[] $records */
|
||||
$records = yield $this->query($name, Record::CNAME);
|
||||
|
||||
/** @var ResourceData\CNAME $cname */
|
||||
$cname = $records[0]->getValue();
|
||||
|
||||
return $cname->getCanonicalName();
|
||||
}
|
||||
|
||||
private function resolveDname(DomainName $name) {
|
||||
$labels = $name->getLabels();
|
||||
$queries = [];
|
||||
$prefixes = [];
|
||||
$prefix = [];
|
||||
|
||||
// Generate a set of DNAME queries for each portion of the name up to (but not including) root
|
||||
do {
|
||||
$key = \implode('.', $labels);
|
||||
|
||||
$queries[$key] = $this->query(new DomainName($labels, false), Record::DNAME);
|
||||
$prefixes[$key] = $prefix;
|
||||
|
||||
$prefix[] = \array_shift($labels);
|
||||
} while (!empty($labels));
|
||||
|
||||
try {
|
||||
/** @var Record[] $records */
|
||||
list(, $records) = yield Promise\some($queries);
|
||||
|
||||
\reset($records);
|
||||
$key = \key($records);
|
||||
|
||||
/** @var ResourceData\DNAME $dname */
|
||||
$dname = $records[0]->getValue();
|
||||
$dnameLabels = $dname->getCanonicalName()->getLabels();
|
||||
|
||||
// Prepend the labels that are not included in the DNAME
|
||||
return new DomainName(\array_merge($prefixes[$key], $dnameLabels), false);
|
||||
} catch (MultiReasonException $e) {
|
||||
foreach ($e->getReasons() as $reason) {
|
||||
if ($reason instanceof NoRecordException) {
|
||||
throw $reason;
|
||||
}
|
||||
}
|
||||
|
||||
throw new ResolutionException("All query attempts failed", 0, $e);
|
||||
}
|
||||
}
|
||||
|
||||
private function getCanonicalName(DomainName $name) {
|
||||
try {
|
||||
return yield from $this->resolveCname($name);
|
||||
} catch (NoRecordException $e) {
|
||||
return yield from $this->resolveDname($name);
|
||||
}
|
||||
}
|
||||
|
||||
private function queryHosts(DomainName $name, int $typeRestriction = null): array {
|
||||
$hosts = $this->config->getKnownHosts();
|
||||
$records = [];
|
||||
@ -299,7 +351,7 @@ final class BasicResolver 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}");
|
||||
throw new NoRecordException("No records returned for {$name}", $response->isRecursionAvailable());
|
||||
}
|
||||
|
||||
return \array_map(function ($data) use ($type, $ttls) {
|
||||
@ -378,7 +430,7 @@ final class BasicResolver implements Resolver {
|
||||
$decoded = \unserialize($encoded, ['allowed_classes' => self::CACHE_UNSERIALIZE_ALLOWED_CLASSES]);
|
||||
|
||||
if (!$decoded) {
|
||||
throw new NoRecordException("No records returned for {$name} (cached result)");
|
||||
throw new NoRecordException("No records returned for {$name} (cached result)", false);
|
||||
}
|
||||
|
||||
$result = [];
|
||||
|
@ -9,8 +9,9 @@ final class Config {
|
||||
private $knownHosts;
|
||||
private $timeout;
|
||||
private $attempts;
|
||||
private $recursionDepth;
|
||||
|
||||
public function __construct(array $nameservers, HostsFile $knownHosts = null, int $timeout = 3000, int $attempts = 2) {
|
||||
public function __construct(array $nameservers, HostsFile $knownHosts = null, int $timeout = 3000, int $attempts = 2, int $recursionDepth = 5) {
|
||||
if (\count($nameservers) < 1) {
|
||||
throw new ConfigException("At least one nameserver is required for a valid config");
|
||||
}
|
||||
@ -27,10 +28,15 @@ final class Config {
|
||||
throw new ConfigException("Invalid attempt count ({$attempts}), must be 1 or greater");
|
||||
}
|
||||
|
||||
if ($recursionDepth < 1) {
|
||||
throw new ConfigException("Invalid recursion depth ({$recursionDepth}), must be 1 or greater");
|
||||
}
|
||||
|
||||
$this->nameservers = $nameservers;
|
||||
$this->knownHosts = $knownHosts ?? new HostsFile([]);
|
||||
$this->timeout = $timeout;
|
||||
$this->attempts = $attempts;
|
||||
$this->recursionDepth = $recursionDepth;
|
||||
}
|
||||
|
||||
private function validateNameserver($nameserver) {
|
||||
@ -85,4 +91,8 @@ final class Config {
|
||||
public function getAttempts(): int {
|
||||
return $this->attempts;
|
||||
}
|
||||
|
||||
public function getRecursionDepth(): int {
|
||||
return $this->recursionDepth;
|
||||
}
|
||||
}
|
||||
|
@ -2,5 +2,17 @@
|
||||
|
||||
namespace Amp\Dns;
|
||||
|
||||
use Throwable;
|
||||
|
||||
class NoRecordException extends ResolutionException {
|
||||
private $recursive;
|
||||
|
||||
public function __construct(string $message, bool $recursive, Throwable $previous = null) {
|
||||
parent::__construct($message, 0, $previous);
|
||||
$this->recursive = $recursive;
|
||||
}
|
||||
|
||||
public function wasRecursiveQuery() {
|
||||
return $this->recursive;
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user