mirror of
https://github.com/danog/dns.git
synced 2024-11-26 20:14:51 +01:00
Use random IDs and different UDP ports for each request
This is another countermeasure as outlined in https://tools.ietf.org/html/rfc5452. Request IDs have 16 bits of entropy now. The port will change during requests, but is usually not random. However, there are systems that use a random port for :0 port requests.
This commit is contained in:
parent
58b9ed1035
commit
ee05df2e1e
@ -9,7 +9,6 @@ use Amp\MultiReasonException;
|
||||
use Amp\Promise;
|
||||
use Amp\Success;
|
||||
use LibDNS\Messages\Message;
|
||||
use LibDNS\Messages\MessageTypes;
|
||||
use LibDNS\Records\Question;
|
||||
use LibDNS\Records\QuestionFactory;
|
||||
use function Amp\call;
|
||||
@ -51,6 +50,10 @@ final class BasicResolver implements Resolver {
|
||||
$this->questionFactory = new QuestionFactory;
|
||||
|
||||
$this->gcWatcher = Loop::repeat(5000, function () {
|
||||
if (empty($this->servers)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$now = \time();
|
||||
|
||||
foreach ($this->servers as $key => $server) {
|
||||
@ -183,21 +186,21 @@ final class BasicResolver implements Resolver {
|
||||
|
||||
$nameservers = $this->config->getNameservers();
|
||||
$attempts = $this->config->getAttempts();
|
||||
$receivedTruncatedResponse = false;
|
||||
$protocol = "udp";
|
||||
$attempt = 0;
|
||||
|
||||
for ($attempt = 0; $attempt < $attempts; ++$attempt) {
|
||||
$i = $attempt % \count($nameservers);
|
||||
$protocol = $receivedTruncatedResponse ? "tcp" : "udp";
|
||||
/** @var Server $server */
|
||||
$uri = $protocol . "://" . $nameservers[0];
|
||||
$server = yield $this->getServer($uri);
|
||||
|
||||
while ($attempt < $attempts) {
|
||||
try {
|
||||
/** @var \Amp\Dns\Server $server */
|
||||
$server = yield $this->getServer($protocol . "://" . $nameservers[$i]);
|
||||
|
||||
if (!$server->isAlive()) {
|
||||
$this->servers[$protocol . "://" . $nameservers[$i]]->close();
|
||||
unset($this->servers[$protocol . "://" . $nameservers[$i]]);
|
||||
unset($this->servers[$uri]);
|
||||
$server->close();
|
||||
|
||||
/** @var \Amp\Dns\Server $server */
|
||||
$i = $attempt % \count($nameservers);
|
||||
$server = yield $this->getServer($protocol . "://" . $nameservers[$i]);
|
||||
}
|
||||
|
||||
@ -206,10 +209,11 @@ final class BasicResolver implements Resolver {
|
||||
$this->assertAcceptableResponse($response);
|
||||
|
||||
if ($response->isTruncated()) {
|
||||
if (!$receivedTruncatedResponse) {
|
||||
if ($protocol !== "tcp") {
|
||||
// Retry with TCP, don't count attempt
|
||||
$receivedTruncatedResponse = true;
|
||||
$attempt--;
|
||||
$protocol = "tcp";
|
||||
$i = $attempt % \count($nameservers);
|
||||
$server = yield $this->getServer($protocol . "://" . $nameservers[$i]);
|
||||
continue;
|
||||
}
|
||||
|
||||
@ -244,6 +248,9 @@ final class BasicResolver implements Resolver {
|
||||
return new Record($data, $type, $ttls[$type]);
|
||||
}, $result[$type]);
|
||||
} catch (TimeoutException $e) {
|
||||
$i = ++$attempt % \count($nameservers);
|
||||
$server = yield $this->getServer($protocol . "://" . $nameservers[$i]);
|
||||
|
||||
continue;
|
||||
}
|
||||
}
|
||||
@ -327,6 +334,10 @@ final class BasicResolver implements Resolver {
|
||||
}
|
||||
|
||||
private function getServer($uri): Promise {
|
||||
if (\substr($uri, 0, 3) === "udp") {
|
||||
return UdpServer::connect($uri);
|
||||
}
|
||||
|
||||
if (isset($this->servers[$uri])) {
|
||||
return new Success($this->servers[$uri]);
|
||||
}
|
||||
@ -335,16 +346,12 @@ final class BasicResolver implements Resolver {
|
||||
return $this->pendingServers[$uri];
|
||||
}
|
||||
|
||||
if (\substr($uri, 0, 3) === "udp") {
|
||||
$server = UdpServer::connect($uri);
|
||||
} else {
|
||||
$server = TcpServer::connect($uri);
|
||||
}
|
||||
$server = TcpServer::connect($uri);
|
||||
|
||||
$server->onResolve(function ($error, $server) use ($uri) {
|
||||
if ($error) {
|
||||
unset($this->pendingServers[$uri]);
|
||||
} else {
|
||||
unset($this->pendingServers[$uri]);
|
||||
|
||||
if (!$error) {
|
||||
$this->servers[$uri] = $server;
|
||||
}
|
||||
});
|
||||
|
@ -16,6 +16,8 @@ use function Amp\call;
|
||||
|
||||
/** @internal */
|
||||
abstract class Server {
|
||||
const MAX_CONCURRENT_REQUESTS = 500;
|
||||
|
||||
/** @var ResourceInputStream */
|
||||
private $input;
|
||||
|
||||
@ -28,9 +30,6 @@ abstract class Server {
|
||||
/** @var MessageFactory */
|
||||
private $messageFactory;
|
||||
|
||||
/** @var int */
|
||||
private $nextId = 0;
|
||||
|
||||
/** @var callable */
|
||||
private $onResolve;
|
||||
|
||||
@ -40,6 +39,9 @@ abstract class Server {
|
||||
/** @var bool */
|
||||
private $receiving = false;
|
||||
|
||||
/** @var array */
|
||||
private $queue = [];
|
||||
|
||||
/**
|
||||
* @param string $uri
|
||||
*
|
||||
@ -113,17 +115,15 @@ abstract class Server {
|
||||
return call(function () use ($question, $timeout) {
|
||||
$this->lastActivity = \time();
|
||||
|
||||
$id = $this->nextId++;
|
||||
if ($this->nextId > 0xffff) {
|
||||
$this->nextId %= 0xffff;
|
||||
if (\count($this->pending) > self::MAX_CONCURRENT_REQUESTS) {
|
||||
$deferred = new Deferred;
|
||||
$this->queue[] = $deferred;
|
||||
yield $deferred->promise();
|
||||
}
|
||||
|
||||
if (isset($this->pending[$id])) {
|
||||
/** @var Deferred $deferred */
|
||||
$deferred = $this->pending[$id]->deferred;
|
||||
unset($this->pending[$id]);
|
||||
$deferred->fail(new ResolutionException("Request hasn't been answered with 65k requests in between"));
|
||||
}
|
||||
do {
|
||||
$id = \random_int(0, 0xffff);
|
||||
} while (isset($this->pending[$id]));
|
||||
|
||||
$message = $this->createMessage($question, $id);
|
||||
|
||||
@ -158,10 +158,17 @@ abstract class Server {
|
||||
return yield Promise\timeout($deferred->promise(), $timeout);
|
||||
} catch (Amp\TimeoutException $exception) {
|
||||
unset($this->pending[$id]);
|
||||
|
||||
if (empty($this->pending)) {
|
||||
$this->input->unreference();
|
||||
}
|
||||
|
||||
throw new TimeoutException("Didn't receive a response within {$timeout} milliseconds.");
|
||||
} finally {
|
||||
if ($this->queue) {
|
||||
$deferred = array_shift($this->queue);
|
||||
$deferred->resolve();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user