1
0
mirror of https://github.com/danog/dns.git synced 2024-11-26 20:14:51 +01:00

Match question section of responses to the asked question

This makes forging responses and poisoning the cache harder and is one method suggested in https://tools.ietf.org/html/rfc5452.
This commit is contained in:
Niklas Keller 2017-06-27 16:55:24 +02:00
parent cbffb766f3
commit 58b9ed1035
2 changed files with 60 additions and 20 deletions

View File

@ -356,9 +356,5 @@ final class BasicResolver implements Resolver {
if ($response->getResponseCode() !== 0) {
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");
}
}
}

View File

@ -22,8 +22,8 @@ abstract class Server {
/** @var ResourceOutputStream */
private $output;
/** @var Deferred[] */
private $questions = [];
/** @var array */
private $pending = [];
/** @var MessageFactory */
private $messageFactory;
@ -85,13 +85,15 @@ abstract class Server {
$id = $message->getId();
if (isset($this->questions[$id])) { // Ignore duplicate response.
$deferred = $this->questions[$id];
unset($this->questions[$id]);
// Ignore duplicate and invalid responses.
if (isset($this->pending[$id]) && $this->matchesQuestion($message, $this->pending[$id]->question)) {
/** @var Deferred $deferred */
$deferred = $this->pending[$id]->deferred;
unset($this->pending[$id]);
$deferred->resolve($message);
}
if (empty($this->questions)) {
if (empty($this->pending)) {
$this->input->unreference();
} elseif (!$this->receiving) {
$this->input->reference();
@ -116,9 +118,10 @@ abstract class Server {
$this->nextId %= 0xffff;
}
if (isset($this->questions[$id])) {
$deferred = $this->questions[$id];
unset($this->questions[$id]);
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"));
}
@ -132,7 +135,17 @@ abstract class Server {
throw $exception;
}
$this->questions[$id] = $deferred = new Deferred;
$deferred = new Deferred;
$pending = new class {
use Amp\Struct;
public $deferred;
public $question;
};
$pending->deferred = $deferred;
$pending->question = $question;
$this->pending[$id] = $pending;
$this->input->reference();
@ -144,8 +157,8 @@ abstract class Server {
try {
return yield Promise\timeout($deferred->promise(), $timeout);
} catch (Amp\TimeoutException $exception) {
unset($this->questions[$id]);
if (empty($this->questions)) {
unset($this->pending[$id]);
if (empty($this->pending)) {
$this->input->unreference();
}
throw new TimeoutException("Didn't receive a response within {$timeout} milliseconds.");
@ -161,7 +174,7 @@ abstract class Server {
private function error(\Throwable $exception) {
$this->close();
if (empty($this->questions)) {
if (empty($this->pending)) {
return;
}
@ -170,10 +183,12 @@ abstract class Server {
$exception = new ResolutionException($message, 0, $exception);
}
$questions = $this->questions;
$this->questions = [];
$pending = $this->pending;
$this->pending = [];
foreach ($questions as $deferred) {
foreach ($pending as $pendingQuestion) {
/** @var Deferred $deferred */
$deferred = $pendingQuestion->deferred;
$deferred->fail($exception);
}
}
@ -193,4 +208,33 @@ abstract class Server {
$request->setID($id);
return $request;
}
private function matchesQuestion(Message $message, Question $question): bool {
if ($message->getType() !== MessageTypes::RESPONSE) {
return false;
}
$questionRecords = $message->getQuestionRecords();
// We only ever ask one question at a time
if (\count($questionRecords) !== 1) {
return false;
}
$questionRecord = $questionRecords->current();
if ($questionRecord->getClass() !== $question->getClass()) {
return false;
}
if ($questionRecord->getType() !== $question->getType()) {
return false;
}
if ($questionRecord->getName()->getValue() !== $question->getName()->getValue()) {
return false;
}
return true;
}
}