1
0
mirror of https://github.com/danog/dns.git synced 2024-11-30 04:29:06 +01:00
This commit is contained in:
Aaron Piotrowski 2017-06-23 00:34:11 -05:00
parent fdeb03ca44
commit c250d471c8
No known key found for this signature in database
GPG Key ID: ADD1EF783EDE9EEB
5 changed files with 343 additions and 0 deletions

View File

@ -32,8 +32,10 @@
"require": {
"php": ">=7.0",
"amphp/amp": "^2",
"amphp/byte-stream": "^1",
"amphp/cache": "^1",
"amphp/file": "^0.2",
"amphp/parser": "^1",
"amphp/windows-registry": "^0.3",
"daverandom/libdns": "^1"
},

92
lib/BasicResolver.php Normal file
View File

@ -0,0 +1,92 @@
<?php
namespace Amp\Dns;
use Amp\Coroutine;
use Amp\Promise;
use LibDNS\Records\Question;
use LibDNS\Records\QuestionFactory;
class BasicResolver implements Resolver {
/** @var \Amp\Dns\ConfigLoader */
private $configLoader;
/** @var \LibDNS\Records\QuestionFactory */
private $questionFactory;
/** @var \Amp\Dns\Config|null */
private $config;
public function __construct(ConfigLoader $configLoader = null) {
$this->configLoader = $configLoader ?? \stripos(PHP_OS, "win") === 0
? new WindowsConfigLoader
: new UnixConfigLoader;
$this->questionFactory = new QuestionFactory;
}
/**
* @see \Amp\Dns\resolve
*/
public function resolve(string $name): Promise {
// TODO: Implement resolve() method.
}
public function query(string $name, $type): Promise {
return new Coroutine($this->doQuery($name, $type));
}
public function doQuery(string $name, int $type): \Generator {
if (!$this->config) {
$this->config = yield $this->configLoader->loadConfig();
}
$question = $this->createQuestion($name, $type);
$nameservers = $this->config->getNameservers();
$attempts = $this->config->getAttempts();
for ($attempt = 0; $attempt < $attempts; ++$attempt) {
$i = $attempt % \count($nameservers);
$uri = "udp://" . $nameservers[$i];
var_dump($uri);
/** @var \Amp\Dns\Server $server */
$server = yield UdpServer::connect($uri);
/** @var \LibDNS\Messages\Message $response */
$response = yield $server->ask($question);
if ($response->getResponseCode() !== 0) {
throw new ResolutionException(\sprintf("Got a response code of %d", $response->getResponseCode()));
}
$answers = $response->getAnswerRecords();
$result = [];
/** @var \LibDNS\Records\Resource $record */
foreach ($answers as $record) {
$result[] = $record;
}
return $result;
}
throw new ResolutionException("No response from any nameserver");
}
/**
* @param string $name
* @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));
}
$question = $this->questionFactory->create($type);
$question->setName($name);
return $question;
}
}

110
lib/Server.php Normal file
View File

@ -0,0 +1,110 @@
<?php
namespace Amp\Dns;
use Amp\ByteStream\ResourceInputStream;
use Amp\ByteStream\ResourceOutputStream;
use Amp\Coroutine;
use Amp\Deferred;
use Amp\Promise;
use LibDNS\Messages\Message;
use LibDNS\Messages\MessageFactory;
use LibDNS\Messages\MessageTypes;
use LibDNS\Records\Question;
abstract class Server {
/** @var \Amp\ByteStream\ResourceInputStream */
private $input;
/** @var \Amp\ByteStream\ResourceOutputStream */
private $output;
/** @var \Amp\Deferred[] */
private $questions = [];
private $messageFactory;
/** @var int */
private $nextId = 0;
/** @var callable */
private $onResolve;
/**
* @param string $uri
*
* @return \Amp\Promise<\Amp\Dns\Server>
*/
abstract public static function connect(string $uri): Promise;
/**
* @param \LibDNS\Messages\Message $message
*
* @return \Amp\Promise<int>
*/
abstract protected function send(Message $message): Promise;
/**
* @return \Amp\Promise<\LibDNS\Messages\Message>
*/
abstract protected function receive(): Promise;
protected function __construct($socket) {
$this->input = new ResourceInputStream($socket);
$this->output = new ResourceOutputStream($socket);
$this->messageFactory = new MessageFactory;
$this->onResolve = function (\Throwable $exception = null, Message $message = null) {
if ($exception) {
return;
}
$id = $message->getId();
if (!isset($this->questions[$id])) {
return;
}
$deferred = $this->questions[$id];
$deferred->resolve($message);
};
}
public function ask(Question $question): Promise {
return new Coroutine($this->doAsk($question));
}
private function doAsk(Question $question): \Generator {
$id = $this->nextId++;
if ($this->nextId > 0xffff) {
$this->nextId %= 0xffff;
}
$message = $this->createMessage($question, $id);
$this->questions[$id] = $deferred = new Deferred;
yield $this->send($message);
$this->receive()->onResolve($this->onResolve);
return yield $deferred->promise();
}
protected function read(): Promise {
return $this->input->read();
}
protected function write(string $data): Promise {
return $this->output->write($data);
}
protected 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;
}
}

85
lib/TcpServer.php Normal file
View File

@ -0,0 +1,85 @@
<?php
namespace Amp\Dns;
use Amp\Deferred;
use Amp\Loop;
use Amp\Parser\Parser;
use Amp\Promise;
use Amp\Success;
use LibDNS\Decoder\DecoderFactory;
use LibDNS\Encoder\EncoderFactory;
use LibDNS\Messages\Message;
use function Amp\call;
class TcpServer extends Server {
private $encoder;
private $queue;
private $parser;
public static function connect(string $uri, int $timeout = 5000): Promise {
if (!$socket = @\stream_socket_client($uri, $errno, $errstr, 0, STREAM_CLIENT_ASYNC_CONNECT)) {
throw new ResolutionException(\sprintf(
"Connection to %s failed: [Error #%d] %s",
$uri,
$errno,
$errstr
));
}
\stream_set_blocking($socket, false);
$deferred = new Deferred;
$watcher = Loop::onWritable($socket, static function ($watcher) use ($socket, $deferred, &$timer) {
Loop::cancel($watcher);
Loop::cancel($timer);
$deferred->resolve(new self($socket));
});
$timer = Loop::delay($timeout, function () use ($deferred, $watcher, $uri) {
Loop::cancel($watcher);
$deferred->fail(new TimeoutException("Name resolution timed out, could not connect to server at $uri"));
});
return $deferred->promise();
}
public static function parser(callable $callback) {
$decoder = (new DecoderFactory)->create();
$length = \unpack("n", yield 2)[1];
$callback($decoder->decode(yield $length));
}
protected function __construct($socket) {
parent::__construct($socket);
$this->encoder = (new EncoderFactory)->create();
$this->queue = new \SplQueue;
$this->parser = new Parser(self::parser([$this->queue, 'push']));
}
public function send(Message $message): Promise {
$data = $this->encoder->encode($message);
return $this->write(\pack("n", \strlen($data)) . $data);
}
public function receive(): Promise {
if ($this->queue->isEmpty()) {
return call(function () {
do {
$chunk = $this->read();
if ($chunk === null) {
throw new ResolutionException("Reading from the server failed");
}
$this->parser->push($chunk);
} while ($this->queue->isEmpty());
return $this->queue->shift();
});
}
return new Success($this->queue->shift());
}
}

54
lib/UdpServer.php Normal file
View File

@ -0,0 +1,54 @@
<?php
namespace Amp\Dns;
use Amp\Promise;
use Amp\Success;
use LibDNS\Decoder\DecoderFactory;
use LibDNS\Encoder\EncoderFactory;
use LibDNS\Messages\Message;
use function Amp\call;
class UdpServer extends Server {
private $encoder;
private $decoder;
public static function connect(string $uri): Promise {
if (!$socket = @\stream_socket_client($uri, $errno, $errstr, 0, STREAM_CLIENT_ASYNC_CONNECT)) {
throw new ResolutionException(\sprintf(
"Connection to %s failed: [Error #%d] %s",
$uri,
$errno,
$errstr
));
}
return new Success(new self($socket));
}
protected function __construct($socket) {
parent::__construct($socket);
$this->encoder = (new EncoderFactory)->create();
$this->decoder = (new DecoderFactory)->create();
}
protected function send(Message $message): Promise {
$data = $this->encoder->encode($message);
return $this->write($data);
}
protected function receive(): Promise {
return call(function () {
$data = yield $this->read();
if ($data === null) {
throw new ResolutionException("Reading from the server failed");
}
return $this->decoder->decode($data);
});
}
}