From 22c90049f38e222828e31c10031a8f7db6782c2a Mon Sep 17 00:00:00 2001 From: Chris Wright Date: Tue, 17 Jun 2014 18:01:10 +0100 Subject: [PATCH] Handle CNAME resource records correctly --- lib/Addr/AddressModes.php | 1 + lib/Addr/Client.php | 43 ++++++++++++++++++++++++---- lib/Addr/ResponseInterpreter.php | 48 +++++++++++++++++++++++++++----- 3 files changed, 79 insertions(+), 13 deletions(-) diff --git a/lib/Addr/AddressModes.php b/lib/Addr/AddressModes.php index 7c80c02..b8cccd5 100644 --- a/lib/Addr/AddressModes.php +++ b/lib/Addr/AddressModes.php @@ -7,4 +7,5 @@ class AddressModes const INET4_ADDR = 1; const INET6_ADDR = 2; const PREFER_INET6 = 4; + const CNAME = 8; } diff --git a/lib/Addr/Client.php b/lib/Addr/Client.php index 8227b0d..6ba8c6b 100644 --- a/lib/Addr/Client.php +++ b/lib/Addr/Client.php @@ -198,24 +198,40 @@ class Client { $packet = fread($this->socket, 512); - $response = $this->responseInterpreter->interpret($packet); - if ($response === null) { + // Decode the response and clean up the pending requests list + $decoded = $this->responseInterpreter->decode($packet); + if ($decoded === null) { return; } - list($id, $addr, $ttl) = $response; + list($id, $response) = $decoded; $request = $this->pendingRequestsById[$id]; - $type = $request['type']; $name = $request['name']; $this->reactor->cancel($request['timeout_id']); - unset($this->pendingRequestsById[$id], $this->pendingRequestsByNameAndType[$name][$type]); + unset($this->pendingRequestsById[$id], $this->pendingRequestsByNameAndType[$name][$request['type']]); if (!$this->pendingRequestsById) { $this->reactor->cancel($this->readWatcherId); $this->readWatcherId = null; } - if ($addr !== null) { + // Interpret the response and make sure we have at least one resource record + $interpreted = $this->responseInterpreter->interpret($response, $request['type']); + if ($interpreted === null) { + foreach ($request['lookups'] as $id => $lookup) { + $this->processPendingLookup($id); + } + + return; + } + + // Distribute the result to the appropriate lookup routine + list($type, $addr, $ttl) = $interpreted; + if ($type === AddressModes::CNAME) { + foreach ($request['lookups'] as $id => $lookup) { + $this->redirectPendingLookup($id, $addr); + } + } else if ($addr !== null) { if ($request['cache_store']) { call_user_func($request['cache_store'], $name, $addr, $type, $ttl); } @@ -277,6 +293,21 @@ class Client } } + /** + * Redirect a lookup to search for another name + * + * @param int $id + * @param string $name + */ + private function redirectPendingLookup($id, $name) + { + array_unshift($this->pendingLookups[$id]['requests'], $this->pendingLookups[$id]['last_type']); + $this->pendingLookups[$id]['last_type'] = null; + $this->pendingLookups[$id]['name'] = $name; + + $this->processPendingLookup($id); + } + /** * Resolve a name from a server * diff --git a/lib/Addr/ResponseInterpreter.php b/lib/Addr/ResponseInterpreter.php index 859fe57..ec0aa13 100644 --- a/lib/Addr/ResponseInterpreter.php +++ b/lib/Addr/ResponseInterpreter.php @@ -3,7 +3,9 @@ namespace Addr; use LibDNS\Decoder\Decoder, - LibDNS\Messages\MessageTypes; + LibDNS\Messages\Message, + LibDNS\Messages\MessageTypes, + LibDNS\Records\ResourceTypes; class ResponseInterpreter { @@ -23,12 +25,12 @@ class ResponseInterpreter } /** - * Extract the message ID and response data from a DNS response packet + * Attempt to decode a data packet to a DNS response message * * @param string $packet - * @return array|null + * @return Message|null */ - public function interpret($packet) + public function decode($packet) { try { $message = $this->decoder->decode($packet); @@ -40,13 +42,45 @@ class ResponseInterpreter return null; } + return [$message->getID(), $message]; + } + + /** + * Extract the message ID and response data from a DNS response packet + * + * @param Message $message + * @param int $expectedType + * @return array|null + */ + public function interpret($message, $expectedType) + { + static $typeMap = [ + AddressModes::INET4_ADDR => ResourceTypes::A, + AddressModes::INET6_ADDR => ResourceTypes::AAAA, + ]; + $answers = $message->getAnswerRecords(); if (!count($answers)) { - return [$message->getID(), null]; + return null; } /** @var \LibDNS\Records\Resource $record */ - $record = $answers->getRecordByIndex(0); - return [$message->getID(), (string)$record->getData(), $record->getTTL()]; + $cname = null; + foreach ($answers as $record) { + switch ($record->getType()) { + case $typeMap[$expectedType]: + return [$expectedType, (string)$record->getData(), $record->getTTL()]; + + case ResourceTypes::CNAME: + $cname = (string)$record->getData(); + break; + } + } + + if ($cname) { + return [AddressModes::CNAME, $cname, null]; + } + + return null; } }