mirror of
https://github.com/danog/dns.git
synced 2024-11-26 20:14:51 +01:00
WIP
This commit is contained in:
parent
fdeb03ca44
commit
c250d471c8
@ -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
92
lib/BasicResolver.php
Normal 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
110
lib/Server.php
Normal 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
85
lib/TcpServer.php
Normal 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
54
lib/UdpServer.php
Normal 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);
|
||||
});
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user