1
0
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:
Chris Wright 2018-02-01 13:25:36 +00:00
parent 64b8e3af6b
commit dcb6ebb2bb
3 changed files with 90 additions and 16 deletions

View File

@ -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 = [];

View File

@ -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;
}
}

View File

@ -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;
}
}