dns-over-https/lib/Rfc8484StubDoHResolver.php

477 lines
18 KiB
PHP
Raw Normal View History

2022-12-18 14:37:59 +01:00
<?php declare(strict_types=1);
2019-06-10 19:32:28 +02:00
namespace Amp\DoH;
use Amp\Cache\Cache;
2022-10-29 20:03:07 +02:00
use Amp\Cancellation;
use Amp\CompositeException;
2022-12-05 22:03:46 +01:00
use Amp\Dns\DnsConfig;
2023-01-12 15:49:35 +01:00
use Amp\Dns\DnsConfigException;
2022-12-05 22:03:46 +01:00
use Amp\Dns\DnsConfigLoader;
2019-06-10 19:32:28 +02:00
use Amp\Dns\DnsException;
2023-01-12 15:49:35 +01:00
use Amp\Dns\DnsRecord;
use Amp\Dns\DnsResolver;
use Amp\Dns\DnsTimeoutException;
use Amp\Dns\MissingDnsRecordException;
use Amp\Dns\Rfc1035StubDnsResolver;
2022-10-29 20:03:07 +02:00
use Amp\Future;
2023-02-15 18:28:06 +01:00
use Amp\Http\Client\HttpClient;
2022-10-29 20:03:07 +02:00
use Amp\Http\Client\Request;
use Amp\NullCancellation;
use danog\LibDNSJson\JsonDecoder;
use danog\LibDNSJson\JsonDecoderFactory;
2022-12-05 22:03:46 +01:00
use danog\LibDNSJson\QueryEncoder;
2022-10-29 20:03:07 +02:00
use danog\LibDNSJson\QueryEncoderFactory;
use LibDNS\Decoder\Decoder;
use LibDNS\Decoder\DecoderFactory;
use LibDNS\Encoder\Encoder;
use LibDNS\Encoder\EncoderFactory;
2019-06-10 19:32:28 +02:00
use LibDNS\Messages\Message;
2022-10-29 20:03:07 +02:00
use LibDNS\Messages\MessageFactory;
use LibDNS\Messages\MessageTypes;
2019-06-10 19:32:28 +02:00
use LibDNS\Records\Question;
use LibDNS\Records\QuestionFactory;
2022-10-29 20:03:07 +02:00
use function Amp\async;
2019-06-11 20:07:46 +02:00
use function Amp\Dns\normalizeName;
2019-06-10 19:32:28 +02:00
2023-01-12 15:57:52 +01:00
final class Rfc8484StubDoHResolver implements DnsResolver
2019-06-10 19:32:28 +02:00
{
const CACHE_PREFIX = "amphp.doh.";
2022-12-05 22:03:46 +01:00
private DnsConfigLoader $configLoader;
2022-10-29 20:03:07 +02:00
private QuestionFactory $questionFactory;
2022-12-05 22:03:46 +01:00
private ?DnsConfig $config = null;
2019-06-10 19:32:28 +02:00
2022-10-29 20:03:07 +02:00
private ?Future $pendingConfig = null;
2019-06-10 19:32:28 +02:00
2022-10-29 20:03:07 +02:00
private Cache $cache;
2019-06-10 19:32:28 +02:00
2022-10-30 20:47:30 +01:00
/** @var Future[] */
2022-10-29 20:03:07 +02:00
private array $pendingQueries = [];
2019-06-10 19:32:28 +02:00
2023-01-12 15:49:35 +01:00
private DnsResolver $subResolver;
2022-10-29 20:03:07 +02:00
private Encoder $encoder;
private Decoder $decoder;
2022-12-05 22:03:46 +01:00
private QueryEncoder $encoderJson;
2022-10-29 20:03:07 +02:00
private JsonDecoder $decoderJson;
private MessageFactory $messageFactory;
2023-02-15 18:28:06 +01:00
private HttpClient $httpClient;
2019-06-10 21:04:10 +02:00
2022-10-29 20:03:07 +02:00
public function __construct(private DoHConfig $dohConfig)
2019-06-10 19:32:28 +02:00
{
2022-10-29 20:03:07 +02:00
$this->cache = $dohConfig->getCache();
$this->configLoader = $dohConfig->getConfigLoader();
$this->subResolver = $dohConfig->getSubResolver();
2019-06-10 19:32:28 +02:00
$this->questionFactory = new QuestionFactory;
2022-10-29 20:03:07 +02:00
$this->encoder = (new EncoderFactory)->create();
$this->decoder = (new DecoderFactory)->create();
$this->encoderJson = (new QueryEncoderFactory)->create();
$this->decoderJson = (new JsonDecoderFactory)->create();
$this->httpClient = $dohConfig->getHttpClient();
$this->messageFactory = new MessageFactory;
2019-06-10 19:32:28 +02:00
}
/** @inheritdoc */
2022-10-29 20:03:07 +02:00
public function resolve(string $name, int $typeRestriction = null, ?Cancellation $cancellation = null): array
2019-06-10 19:32:28 +02:00
{
2023-01-12 15:49:35 +01:00
if ($typeRestriction !== null && $typeRestriction !== DnsRecord::A && $typeRestriction !== DnsRecord::AAAA) {
throw new \Error("Invalid value for parameter 2: null|DnsRecord::A|DnsRecord::AAAA expected");
2019-06-10 19:32:28 +02:00
}
2022-10-29 20:03:07 +02:00
if (!$this->config) {
try {
$this->reloadConfig();
2023-01-12 15:49:35 +01:00
} catch (DnsConfigException $e) {
2022-12-05 22:03:46 +01:00
$this->config = new DnsConfig(['0.0.0.0'], []);
2019-06-10 19:32:28 +02:00
}
2022-10-29 20:03:07 +02:00
}
2019-06-10 19:32:28 +02:00
2022-10-29 20:03:07 +02:00
switch ($typeRestriction) {
2023-01-12 15:49:35 +01:00
case DnsRecord::A:
2022-10-29 20:03:07 +02:00
if (\filter_var($name, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) {
2023-01-12 15:49:35 +01:00
return [new DnsRecord($name, DnsRecord::A, null)];
2022-10-29 20:07:24 +02:00
}
if (\filter_var($name, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) {
2022-10-29 20:03:07 +02:00
throw new DnsException("Got an IPv6 address, but type is restricted to IPv4");
}
break;
2023-01-12 15:49:35 +01:00
case DnsRecord::AAAA:
2022-10-29 20:03:07 +02:00
if (\filter_var($name, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) {
2023-01-12 15:49:35 +01:00
return [new DnsRecord($name, DnsRecord::AAAA, null)];
2022-10-29 20:07:24 +02:00
}
if (\filter_var($name, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) {
2022-10-29 20:03:07 +02:00
throw new DnsException("Got an IPv4 address, but type is restricted to IPv6");
}
break;
default:
if (\filter_var($name, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) {
2023-01-12 15:49:35 +01:00
return [new DnsRecord($name, DnsRecord::A, null)];
2022-10-29 20:07:24 +02:00
}
if (\filter_var($name, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) {
2023-01-12 15:49:35 +01:00
return [new DnsRecord($name, DnsRecord::AAAA, null)];
2022-10-29 20:03:07 +02:00
}
break;
}
2019-06-10 19:32:28 +02:00
2022-10-29 20:07:24 +02:00
$dots = \substr_count($name, ".");
$trailingDot = $name[-1] === ".";
2022-10-29 20:03:07 +02:00
$name = normalizeName($name);
2019-06-10 19:32:28 +02:00
2022-10-29 20:03:07 +02:00
if ($records = $this->queryHosts($name, $typeRestriction)) {
return $records;
}
2019-06-10 19:32:28 +02:00
2022-10-29 20:03:07 +02:00
// 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') {
2023-01-12 15:49:35 +01:00
return $typeRestriction === DnsRecord::AAAA
? [new DnsRecord('::1', DnsRecord::AAAA, null)]
: [new DnsRecord('127.0.0.1', DnsRecord::A, null)];
2022-10-29 20:03:07 +02:00
}
2019-06-10 19:32:28 +02:00
2023-01-12 15:57:52 +01:00
if ($this->dohConfig->isDoHNameserver($name)) {
2022-10-29 20:03:07 +02:00
return $this->subResolver->resolve($name, $typeRestriction, $cancellation);
}
2022-10-29 20:07:24 +02:00
\assert($this->config !== null);
2019-06-11 19:07:51 +02:00
2022-10-29 20:07:24 +02:00
$searchList = [null];
if (!$trailingDot && $dots < $this->config->getNdots()) {
$searchList = \array_merge($this->config->getSearchList(), $searchList);
}
foreach ($searchList as $searchIndex => $search) {
for ($redirects = 0; $redirects < 5; $redirects++) {
$searchName = $name;
if ($search !== null) {
$searchName = $name . "." . $search;
2022-10-29 20:03:07 +02:00
}
2019-06-10 21:04:10 +02:00
2022-10-29 20:07:24 +02:00
try {
if ($typeRestriction) {
return $this->query($searchName, $typeRestriction, $cancellation);
}
2022-10-29 20:03:07 +02:00
2022-10-29 20:07:24 +02:00
[$exceptions, $records] = Future\awaitAll([
2023-01-12 15:49:35 +01:00
async(fn () => $this->query($searchName, DnsRecord::A, $cancellation)),
async(fn () => $this->query($searchName, DnsRecord::AAAA, $cancellation)),
2022-10-29 20:07:24 +02:00
]);
if (\count($exceptions) === 2) {
$errors = [];
foreach ($exceptions as $reason) {
2023-01-12 15:49:35 +01:00
if ($reason instanceof MissingDnsRecordException) {
2022-10-29 20:07:24 +02:00
throw $reason;
}
if ($searchIndex < \count($searchList) - 1 && \in_array($reason->getCode(), [2, 3], true)) {
continue 2;
}
2022-10-29 20:03:07 +02:00
2022-10-29 20:07:24 +02:00
$errors[] = $reason->getMessage();
2022-10-29 20:03:07 +02:00
}
2022-10-29 20:07:24 +02:00
throw new DnsException(
"All query attempts failed for {$searchName}: " . \implode(", ", $errors),
0,
new CompositeException($exceptions)
);
2019-06-10 19:32:28 +02:00
}
2022-10-29 20:03:07 +02:00
2022-10-29 20:07:24 +02:00
return \array_merge(...$records);
2023-01-12 15:49:35 +01:00
} catch (MissingDnsRecordException) {
2022-10-29 20:07:24 +02:00
try {
2023-01-12 15:49:35 +01:00
$cnameRecords = $this->query($searchName, DnsRecord::CNAME, $cancellation);
2022-10-29 20:07:24 +02:00
$name = $cnameRecords[0]->getValue();
continue;
2023-01-12 15:49:35 +01:00
} catch (MissingDnsRecordException) {
$dnameRecords = $this->query($searchName, DnsRecord::DNAME, $cancellation);
2022-10-29 20:07:24 +02:00
$name = $dnameRecords[0]->getValue();
continue;
}
} catch (DnsException $e) {
if ($searchIndex < \count($searchList) - 1 && \in_array($e->getCode(), [2, 3], true)) {
continue 2;
}
throw $e;
2019-06-10 19:32:28 +02:00
}
}
2022-10-29 20:03:07 +02:00
}
2019-06-10 19:32:28 +02:00
2022-10-29 20:07:24 +02:00
\assert(isset($searchName));
throw new DnsException("Giving up resolution of '{$searchName}', too many redirects");
2019-06-10 19:32:28 +02:00
}
/**
* Reloads the configuration in the background.
*
* Once it's finished, the configuration will be used for new requests.
*/
2022-10-29 20:03:07 +02:00
public function reloadConfig(): void
2019-06-10 19:32:28 +02:00
{
2022-10-29 20:03:07 +02:00
if (!$this->pendingConfig) {
2022-10-29 20:44:35 +02:00
$promise = async(function () {
2022-10-29 20:03:07 +02:00
try {
2023-01-12 15:49:35 +01:00
if ($this->subResolver instanceof Rfc1035StubDnsResolver) {
$this->subResolver->reloadConfig();
}
2022-10-29 20:03:07 +02:00
$this->config = $this->configLoader->loadConfig();
} finally {
$this->pendingConfig = null;
}
});
2022-10-29 20:44:35 +02:00
$this->pendingConfig = $promise;
2019-06-10 19:32:28 +02:00
}
2022-10-29 20:03:07 +02:00
$this->pendingConfig->await();
2019-06-10 19:32:28 +02:00
}
private function queryHosts(string $name, int $typeRestriction = null): array
{
2022-10-30 20:47:30 +01:00
\assert($this->config !== null);
2019-06-10 19:32:28 +02:00
$hosts = $this->config->getKnownHosts();
$records = [];
2023-01-12 15:49:35 +01:00
$returnIPv4 = $typeRestriction === null || $typeRestriction === DnsRecord::A;
$returnIPv6 = $typeRestriction === null || $typeRestriction === DnsRecord::AAAA;
2019-06-10 19:32:28 +02:00
2023-01-12 15:49:35 +01:00
if ($returnIPv4 && isset($hosts[DnsRecord::A][$name])) {
$records[] = new DnsRecord($hosts[DnsRecord::A][$name], DnsRecord::A, null);
2019-06-10 19:32:28 +02:00
}
2023-01-12 15:49:35 +01:00
if ($returnIPv6 && isset($hosts[DnsRecord::AAAA][$name])) {
$records[] = new DnsRecord($hosts[DnsRecord::AAAA][$name], DnsRecord::AAAA, null);
2019-06-10 19:32:28 +02:00
}
return $records;
}
/** @inheritdoc */
2022-10-29 20:03:07 +02:00
public function query(string $name, int $type, ?Cancellation $cancellation = null): array
2019-06-10 19:32:28 +02:00
{
2022-10-29 20:03:07 +02:00
$cancellation ??= new NullCancellation;
2019-06-10 19:32:28 +02:00
$pendingQueryKey = $type." ".$name;
if (isset($this->pendingQueries[$pendingQueryKey])) {
2022-10-29 20:44:35 +02:00
return $this->pendingQueries[$pendingQueryKey]->await($cancellation);
2019-06-10 19:32:28 +02:00
}
2022-10-29 20:03:07 +02:00
$promise = async(function () use ($name, $type, $cancellation, $pendingQueryKey) {
try {
if (!$this->config) {
try {
$this->reloadConfig();
2023-01-12 15:49:35 +01:00
} catch (DnsConfigException $e) {
2022-12-05 22:03:46 +01:00
$this->config = new DnsConfig(['0.0.0.0'], []);
2022-10-29 20:03:07 +02:00
}
2019-06-12 13:03:38 +02:00
}
2019-06-10 19:32:28 +02:00
2022-10-30 20:47:30 +01:00
\assert($this->config !== null);
2022-10-29 20:03:07 +02:00
$name = $this->normalizeName($name, $type);
$question = $this->createQuestion($name, $type);
2019-06-10 19:32:28 +02:00
2022-10-29 20:03:07 +02:00
if (null !== $cachedValue = $this->cache->get($this->getCacheKey($name, $type))) {
return $this->decodeCachedResult($name, $type, $cachedValue);
}
2019-06-10 19:32:28 +02:00
2023-01-12 15:57:52 +01:00
$nameservers = $this->dohConfig->getDoHNameservers();
2022-10-29 20:03:07 +02:00
$attempts = $this->config->getAttempts() * \count($nameservers);
$attempt = 0;
2019-06-10 19:32:28 +02:00
2022-10-29 20:03:07 +02:00
$nameserver = $nameservers[0];
2019-12-13 00:18:38 +01:00
2022-10-29 20:03:07 +02:00
$attemptDescription = [];
2019-06-10 19:32:28 +02:00
2022-10-29 20:03:07 +02:00
while ($attempt < $attempts) {
2019-06-12 13:48:57 +02:00
try {
2022-10-29 20:03:07 +02:00
$attemptDescription[] = $nameserver;
2019-06-12 13:48:57 +02:00
2022-10-29 20:03:07 +02:00
$response = $this->ask($nameserver, $question, $cancellation);
$this->assertAcceptableResponse($response);
2019-06-12 13:48:57 +02:00
2022-10-29 20:03:07 +02:00
if ($response->isTruncated()) {
2023-01-12 15:49:35 +01:00
throw new DnsException("Server returned a truncated response for '{$name}' (".DnsRecord::getName($type).")");
2022-10-29 20:03:07 +02:00
}
2019-06-10 19:32:28 +02:00
2022-10-29 20:03:07 +02:00
$answers = $response->getAnswerRecords();
$result = [];
$ttls = [];
2019-06-10 19:32:28 +02:00
2022-10-29 20:03:07 +02:00
foreach ($answers as $record) {
$recordType = $record->getType();
$result[$recordType][] = (string) $record->getData();
2019-06-10 19:32:28 +02:00
2022-10-29 20:03:07 +02:00
// Cache for max one day
$ttls[$recordType] = \min($ttls[$recordType] ?? 86400, $record->getTTL());
}
2019-06-10 19:32:28 +02:00
2022-10-29 20:03:07 +02:00
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]);
}
2019-06-10 19:32:28 +02:00
2022-10-29 20:03:07 +02:00
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);
2023-01-12 15:49:35 +01:00
throw new MissingDnsRecordException("No records returned for '{$name}' (".DnsRecord::getName($type).")");
2022-10-29 20:03:07 +02:00
}
2019-06-10 19:32:28 +02:00
2022-10-29 20:03:07 +02:00
return \array_map(function ($data) use ($type, $ttls) {
2023-01-12 15:49:35 +01:00
return new DnsRecord($data, $type, $ttls[$type]);
2022-10-29 20:03:07 +02:00
}, $result[$type]);
2023-01-12 15:49:35 +01:00
} catch (DnsTimeoutException) {
2022-10-29 20:03:07 +02:00
$i = ++$attempt % \count($nameservers);
$nameserver = $nameservers[$i];
2019-06-10 19:32:28 +02:00
}
}
2023-01-12 15:49:35 +01:00
throw new DnsTimeoutException(\sprintf(
2022-10-29 20:03:07 +02:00
"No response for '%s' (%s) from any nameserver within %d ms after %d attempts, tried %s",
$name,
2023-01-12 15:49:35 +01:00
DnsRecord::getName($type),
2022-10-29 20:03:07 +02:00
$this->config->getTimeout(),
$attempts,
\implode(", ", $attemptDescription)
));
} finally {
unset($this->pendingQueries[$pendingQueryKey]);
2019-12-12 22:27:22 +01:00
}
2019-06-10 19:32:28 +02:00
});
2022-10-29 20:03:07 +02:00
$this->pendingQueries[$pendingQueryKey] = $promise;
return $promise->await($cancellation);
}
2023-02-15 18:28:06 +01:00
/**
* Base64URL encode.
*
* @param string $data Data to encode
*/
private static function base64urlEncode(string $data): string
{
return \rtrim(\strtr(\base64_encode($data), '+/', '-_'), '=');
}
2022-10-29 20:03:07 +02:00
2023-01-12 15:57:52 +01:00
private function ask(DoHNameserver $nameserver, Question $question, Cancellation $cancellation): Message
2022-10-29 20:03:07 +02:00
{
$message = $this->createMessage($question, \random_int(0, 0xffff));
2022-10-30 20:47:30 +01:00
$request = null;
2022-10-29 20:03:07 +02:00
switch ($nameserver->getType()) {
2023-01-12 15:57:52 +01:00
case DoHNameserverType::RFC8484_GET:
2022-10-29 20:03:07 +02:00
$data = $this->encoder->encode($message);
2023-02-15 18:28:06 +01:00
$request = new Request($nameserver->getUri().'?'.\http_build_query(['dns' => self::base64urlEncode($data), 'ct' => 'application/dns-message']), "GET");
2022-10-29 20:03:07 +02:00
$request->setHeaders($nameserver->getHeaders());
2023-02-15 18:28:06 +01:00
$request->setHeader('accept', 'application/dns-message');
2022-10-29 20:03:07 +02:00
break;
2023-01-12 15:57:52 +01:00
case DoHNameserverType::RFC8484_POST:
2022-10-29 20:03:07 +02:00
$data = $this->encoder->encode($message);
$request = new Request($nameserver->getUri(), "POST");
$request->setBody($data);
2023-02-15 18:28:06 +01:00
$request->setHeaders($nameserver->getHeaders());
2022-10-29 20:03:07 +02:00
$request->setHeader('content-type', 'application/dns-message');
$request->setHeader('accept', 'application/dns-message');
2022-10-30 20:47:30 +01:00
$request->setHeader('content-length', (string) \strlen($data));
2022-10-29 20:03:07 +02:00
break;
2023-01-12 15:57:52 +01:00
case DoHNameserverType::GOOGLE_JSON:
2022-10-29 20:03:07 +02:00
$data = $this->encoderJson->encode($message);
$request = new Request($nameserver->getUri().'?'.$data, "GET");
$request->setHeaders($nameserver->getHeaders());
2023-02-15 18:28:06 +01:00
$request->setHeader('accept', 'application/dns-json');
2022-10-29 20:03:07 +02:00
break;
}
2019-06-10 19:32:28 +02:00
2022-10-29 20:03:07 +02:00
$response = $this->httpClient->request($request, $cancellation);
if ($response->getStatus() !== 200) {
throw new DoHException("HTTP result !== 200: ".$response->getStatus()." ".$response->getReason(), $response->getStatus());
}
$response = $response->getBody()->buffer();
switch ($nameserver->getType()) {
2023-01-12 15:57:52 +01:00
case DoHNameserverType::RFC8484_GET:
case DoHNameserverType::RFC8484_POST:
2022-10-29 20:03:07 +02:00
return $this->decoder->decode($response);
2023-01-12 15:57:52 +01:00
case DoHNameserverType::GOOGLE_JSON:
2022-10-29 20:03:07 +02:00
return $this->decoderJson->decode($response, $message->getID());
}
2019-06-10 19:32:28 +02:00
}
2022-10-30 20:47:30 +01:00
private function normalizeName(string $name, int $type): string
2019-06-10 19:32:28 +02:00
{
2023-01-12 15:49:35 +01:00
if ($type === DnsRecord::PTR) {
2019-06-10 19:32:28 +02:00
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";
}
}
2023-01-12 15:49:35 +01:00
} elseif (\in_array($type, [DnsRecord::A, DnsRecord::AAAA])) {
2019-06-10 19:32:28 +02:00
$name = normalizeName($name);
}
return $name;
}
private function createQuestion(string $name, int $type): Question
{
if (0 > $type || 0xffff < $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;
}
2022-10-29 20:03:07 +02:00
private function createMessage(Question $question, int $id): Message
{
$request = $this->messageFactory->create(MessageTypes::QUERY);
$request->getQuestionRecords()->add($question);
$request->isRecursionDesired(true);
$request->setID($id);
return $request;
}
2019-06-10 19:32:28 +02:00
private function getCacheKey(string $name, int $type): string
{
return self::CACHE_PREFIX.$name."#".$type;
}
2022-10-30 20:47:30 +01:00
/**
2023-01-12 15:49:35 +01:00
* @return list<DnsRecord>
2022-10-30 20:47:30 +01:00
*/
private function decodeCachedResult(string $name, int $type, string $encoded): array
2019-06-10 19:32:28 +02:00
{
$decoded = \json_decode($encoded, true);
if (!$decoded) {
2023-01-12 15:49:35 +01:00
throw new MissingDnsRecordException("No records returned for {$name} (cached result)");
2019-06-10 19:32:28 +02:00
}
$result = [];
foreach ($decoded as $data) {
2023-01-12 15:49:35 +01:00
$result[] = new DnsRecord($data, $type);
2019-06-10 19:32:28 +02:00
}
return $result;
}
2022-10-29 20:03:07 +02:00
private function assertAcceptableResponse(Message $response): void
2019-06-10 19:32:28 +02:00
{
if ($response->getResponseCode() !== 0) {
throw new DnsException(\sprintf("Server returned error code: %d", $response->getResponseCode()));
}
}
}