mirror of
https://github.com/danog/dns.git
synced 2024-11-26 20:14:51 +01:00
Implement caching and name normalization in BasicResolver
This commit is contained in:
parent
f0a67f5619
commit
48e37f4014
@ -2,12 +2,17 @@
|
||||
|
||||
namespace Amp\Dns;
|
||||
|
||||
use Amp\Cache\ArrayCache;
|
||||
use Amp\Cache\Cache;
|
||||
use Amp\Coroutine;
|
||||
use Amp\Promise;
|
||||
use LibDNS\Messages\MessageTypes;
|
||||
use LibDNS\Records\Question;
|
||||
use LibDNS\Records\QuestionFactory;
|
||||
|
||||
class BasicResolver implements Resolver {
|
||||
const CACHE_PREFIX = "amphp.dns.";
|
||||
|
||||
/** @var \Amp\Dns\ConfigLoader */
|
||||
private $configLoader;
|
||||
|
||||
@ -17,7 +22,11 @@ class BasicResolver implements Resolver {
|
||||
/** @var \Amp\Dns\Config|null */
|
||||
private $config;
|
||||
|
||||
public function __construct(ConfigLoader $configLoader = null) {
|
||||
/** @var Cache */
|
||||
private $cache;
|
||||
|
||||
public function __construct(Cache $cache = null, ConfigLoader $configLoader = null) {
|
||||
$this->cache = $cache ?? new ArrayCache;
|
||||
$this->configLoader = $configLoader ?? \stripos(PHP_OS, "win") === 0
|
||||
? new WindowsConfigLoader
|
||||
: new UnixConfigLoader;
|
||||
@ -25,14 +34,13 @@ class BasicResolver implements Resolver {
|
||||
$this->questionFactory = new QuestionFactory;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see \Amp\Dns\resolve
|
||||
*/
|
||||
/** @inheritdoc */
|
||||
public function resolve(string $name): Promise {
|
||||
// TODO: Implement resolve() method.
|
||||
}
|
||||
|
||||
public function query(string $name, $type): Promise {
|
||||
/** @inheritdoc */
|
||||
public function query(string $name, int $type): Promise {
|
||||
return new Coroutine($this->doQuery($name, $type));
|
||||
}
|
||||
|
||||
@ -41,8 +49,13 @@ class BasicResolver implements Resolver {
|
||||
$this->config = yield $this->configLoader->loadConfig();
|
||||
}
|
||||
|
||||
$name = $this->normalizeName($name, $type);
|
||||
$question = $this->createQuestion($name, $type);
|
||||
|
||||
if (null !== $cachedValue = yield $this->cache->get($this->getCacheKey($name, $type))) {
|
||||
return $this->decodeCachedResult($name, $type, $cachedValue);
|
||||
}
|
||||
|
||||
$nameservers = $this->config->getNameservers();
|
||||
$attempts = $this->config->getAttempts();
|
||||
|
||||
@ -50,8 +63,6 @@ class BasicResolver implements Resolver {
|
||||
$i = $attempt % \count($nameservers);
|
||||
$uri = "udp://" . $nameservers[$i];
|
||||
|
||||
var_dump($uri);
|
||||
|
||||
/** @var \Amp\Dns\Server $server */
|
||||
$server = yield UdpServer::connect($uri);
|
||||
|
||||
@ -59,34 +70,99 @@ class BasicResolver implements Resolver {
|
||||
$response = yield $server->ask($question);
|
||||
|
||||
if ($response->getResponseCode() !== 0) {
|
||||
throw new ResolutionException(\sprintf("Got a response code of %d", $response->getResponseCode()));
|
||||
throw new ResolutionException(\sprintf("Server returned error code: %d", $response->getResponseCode()));
|
||||
}
|
||||
|
||||
if ($response->getType() !== MessageTypes::RESPONSE) {
|
||||
throw new ResolutionException("Invalid server reply; expected RESPONSE but received QUERY");
|
||||
}
|
||||
|
||||
if ($response->isTruncated()) {
|
||||
// TODO: Retry via TCP
|
||||
}
|
||||
|
||||
$answers = $response->getAnswerRecords();
|
||||
|
||||
$result = [];
|
||||
$ttls = [];
|
||||
|
||||
/** @var \LibDNS\Records\Resource $record */
|
||||
foreach ($answers as $record) {
|
||||
$result[] = $record;
|
||||
$recordType = $record->getType();
|
||||
|
||||
$result[$recordType][] = $record->getData();
|
||||
$ttls[$recordType] = \min($ttls[$recordType] ?? \PHP_INT_MAX, $record->getTTL());
|
||||
}
|
||||
return $result;
|
||||
|
||||
foreach ($result as $recordType => $records) {
|
||||
// We don't care here whether storing in the cache fails
|
||||
$this->cache->set($this->getCacheKey($name, $recordType), \json_encode($records), $ttls[$recordType]);
|
||||
}
|
||||
|
||||
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}");
|
||||
}
|
||||
|
||||
return array_map(function ($data) use ($type, $ttls) {
|
||||
return new Record($data, $type, $ttls[$type]);
|
||||
}, $result[$type]);
|
||||
}
|
||||
|
||||
throw new ResolutionException("No response from any nameserver");
|
||||
throw new ResolutionException("No response from any nameserver after {$attempts} attempts");
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $name
|
||||
* @param int $type
|
||||
* @param int $type
|
||||
*
|
||||
* @return \LibDNS\Records\Question
|
||||
*/
|
||||
private function createQuestion(string $name, int $type): Question {
|
||||
if (0 > $type || 0xffff < $type) {
|
||||
throw new \Error(\sprintf('%d does not correspond to a valid record type (must be between 0 and 65535).', $type));
|
||||
$message = \sprintf('%d does not correspond to a valid record type (must be between 0 and 65535).', $type);
|
||||
throw new \Error($message);
|
||||
}
|
||||
|
||||
$question = $this->questionFactory->create($type);
|
||||
$question->setName($name);
|
||||
|
||||
return $question;
|
||||
}
|
||||
}
|
||||
|
||||
private function getCacheKey(string $name, int $type): string {
|
||||
return self::CACHE_PREFIX . $name . "#" . $type;
|
||||
}
|
||||
|
||||
private function decodeCachedResult(string $name, string $type, string $encoded) {
|
||||
$decoded = \json_decode($encoded, true);
|
||||
|
||||
if (!$decoded) {
|
||||
throw new NoRecordException("No records returned for {$name} (cached result)");
|
||||
}
|
||||
|
||||
$result = [];
|
||||
|
||||
foreach ($decoded as $data) {
|
||||
$result[] = new Record($data, $type);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
private function normalizeName(string $name, int $type) {
|
||||
if ($type === Record::PTR) {
|
||||
if (($packedIp = @inet_pton($name)) !== false) {
|
||||
if (isset($packedIp[4])) { // IPv6
|
||||
$name = wordwrap(strrev(bin2hex($packedIp)), 1, ".", true) . ".ip6.arpa";
|
||||
} else { // IPv4
|
||||
$name = inet_ntop(strrev($packedIp)) . ".in-addr.arpa";
|
||||
}
|
||||
}
|
||||
} else if (\in_array($type, [Record::A, Record::AAAA])) {
|
||||
$name = normalizeDnsName($name);
|
||||
}
|
||||
|
||||
return $name;
|
||||
}
|
||||
}
|
||||
|
@ -13,5 +13,5 @@ interface Resolver {
|
||||
/**
|
||||
* @see \Amp\Dns\query
|
||||
*/
|
||||
public function query(string $name, $type): Promise;
|
||||
public function query(string $name, int $type): Promise;
|
||||
}
|
||||
|
@ -10,8 +10,10 @@ use LibDNS\Messages\Message;
|
||||
use function Amp\call;
|
||||
|
||||
class UdpServer extends Server {
|
||||
/** @var \LibDNS\Encoder\Encoder */
|
||||
private $encoder;
|
||||
|
||||
/** @var \LibDNS\Decoder\Decoder */
|
||||
private $decoder;
|
||||
|
||||
public static function connect(string $uri): Promise {
|
||||
@ -27,7 +29,6 @@ class UdpServer extends Server {
|
||||
return new Success(new self($socket));
|
||||
}
|
||||
|
||||
|
||||
protected function __construct($socket) {
|
||||
parent::__construct($socket);
|
||||
|
||||
@ -51,4 +52,4 @@ class UdpServer extends Server {
|
||||
return $this->decoder->decode($data);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user