1
0
mirror of https://github.com/danog/dns.git synced 2024-11-30 04:29:06 +01:00

Use input/output streams

This commit is contained in:
Aaron Piotrowski 2017-06-22 19:38:36 -05:00
parent ba8a8423eb
commit fdeb03ca44
No known key found for this signature in database
GPG Key ID: ADD1EF783EDE9EEB

View File

@ -3,9 +3,12 @@
namespace Amp\Dns;
use Amp;
use Amp\ByteStream\ResourceInputStream;
use Amp\ByteStream\ResourceOutputStream;
use Amp\ByteStream\StreamException;
use Amp\Cache\ArrayCache;
use Amp\Cache\Cache;
use Amp\CallableMaker;
use Amp\Coroutine;
use Amp\Deferred;
use Amp\Failure;
use Amp\Loop;
@ -21,8 +24,6 @@ use LibDNS\Records\QuestionFactory;
use function Amp\call;
class DefaultResolver implements Resolver {
use CallableMaker;
const MAX_REQUEST_ID = 65536;
const IDLE_TIMEOUT = 15000;
const CACHE_PREFIX = "amphp.dns.";
@ -145,7 +146,7 @@ class DefaultResolver implements Resolver {
}, $records);
}
private function recurseWithHosts($name, array $types) {
private function recurseWithHosts($name, array $types): \Generator {
/** @var Config $config */
$config = yield $this->loadConfig();
$hosts = $config->getKnownHosts();
@ -166,7 +167,7 @@ class DefaultResolver implements Resolver {
return yield from $this->doRecurse($name, $types);
}
private function doRecurse($name, array $types) {
private function doRecurse($name, array $types): \Generator {
if (\array_intersect($types, [Record::CNAME, Record::DNAME])) {
throw new \Error("Cannot use recursion for CNAME and DNAME records");
}
@ -194,17 +195,15 @@ class DefaultResolver implements Resolver {
throw new ResolutionException("CNAME or DNAME chain too long (possible recursion?)");
}
private function doRequest($uri, $name, $type) {
$server = $this->loadExistingServer($uri) ?: $this->loadNewServer($uri);
$useTCP = \substr($uri, 0, 6) == "tcp://";
if ($useTCP && isset($server->connect)) {
return call(function () use ($server, $uri, $name, $type) {
yield $server->connect;
return $this->doRequest($uri, $name, $type);
});
private function loadServer($uri): Promise {
if ($server = $this->loadExistingServer($uri)) {
return new Success($server);
}
return $this->loadNewServer($uri);
}
private function doRequest($server, $name, $type): \Generator {
// Get the next available request ID
do {
$requestId = $this->requestIdCounter++;
@ -226,27 +225,66 @@ class DefaultResolver implements Resolver {
// Encode request message
$requestPacket = $this->encoder->encode($request);
if ($useTCP) {
if ($server->tcp) {
$requestPacket = \pack("n", \strlen($requestPacket)) . $requestPacket;
}
// Send request
// FIXME: Fix might not write all bytes if TCP is used, as the buffer might be full
$bytesWritten = @\fwrite($server->socket, $requestPacket);
if ($bytesWritten === false || $bytesWritten === 0 && (!\is_resource($server->socket) || !\feof($server->socket))) {
$exception = new ResolutionException("Request send failed");
try {
yield $server->output->write($requestPacket);
} catch (StreamException $exception) {
$exception = new ResolutionException("Request send failed", 0, $exception);
$this->unloadServer($server->id, $exception);
throw $exception;
}
$deferred = new Deferred;
$server->pendingRequests[$requestId] = true;
$this->pendingRequests[$requestId] = [$deferred, $name, $type, $uri];
$this->pendingRequests[$requestId] = [$deferred, $name, $type, $server->uri];
return $deferred->promise();
$packet = yield $server->input->read();
if ($packet === null) {
$exception = new ResolutionException("Server connection failed");
$this->unloadServer($server->id, $exception);
throw $exception;
}
if ($server->tcp) {
if ($server->length === INF) {
$server->length = \unpack("n", $packet)[1];
$packet = \substr($packet, 2);
}
$server->buffer .= $packet;
while ($server->length > \strlen($server->buffer)) {
$packet = yield $server->input->read();
if ($packet === null) {
$exception = new ResolutionException("Server connection failed");
$this->unloadServer($server->id, $exception);
throw $exception;
}
}
while ($server->length <= \strlen($server->buffer)) {
$this->decodeResponsePacket($server->id, \substr($server->buffer, 0, $server->length));
$server->buffer = \substr($server->buffer, $server->length);
if (\strlen($server->buffer) >= 2 + $server->length) {
$server->length = \unpack("n", $server->buffer)[1];
$server->buffer = \substr($server->buffer, 2);
} else {
$server->length = INF;
}
}
} else {
$this->decodeResponsePacket($server->id, $packet);
}
return yield $deferred->promise();
}
private function doResolve($name, array $types) {
private function doResolve($name, array $types): \Generator {
/** @var Config $config */
$config = yield $this->loadConfig();
@ -298,9 +336,11 @@ class DefaultResolver implements Resolver {
$i = $attempt % \count($nameservers);
$uri = "udp://" . $nameservers[$i];
$server = yield $this->loadServer($uri);
$promises = [];
foreach ($types as $type) {
$promises[] = $this->doRequest($uri, $name, $type);
$promises[] = new Coroutine($this->doRequest($server, $name, $type));
}
try {
@ -341,7 +381,6 @@ class DefaultResolver implements Resolver {
if (\is_resource($server->socket)) {
unset($this->serverIdTimeoutMap[$server->id]);
Loop::enable($server->watcherId);
return $server;
}
@ -349,7 +388,7 @@ class DefaultResolver implements Resolver {
return null;
}
private function loadNewServer($uri) {
private function loadNewServer($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",
@ -367,32 +406,30 @@ class DefaultResolver implements Resolver {
public $id;
public $uri;
public $server;
public $socket;
public $buffer = "";
public $length = INF;
public $pendingRequests = [];
public $watcherId;
public $connect;
public $tcp = false;
public $input;
public $output;
};
$server->id = $id;
$server->uri = $uri;
$server->socket = $socket;
$server->pendingRequests = [];
$server->watcherId = Loop::onReadable($socket, $this->callableFromInstanceMethod("onReadable"));
Loop::disable($server->watcherId);
$server->input = new ResourceInputStream($socket);
$server->output = new ResourceOutputStream($socket);
$this->serverIdMap[$id] = $server;
$this->serverUriMap[$uri] = $server;
if (\substr($uri, 0, 6) == "tcp://") {
$server->tcp = true;
$deferred = new Deferred;
$server->connect = $deferred->promise();
$watcher = Loop::onWritable($server->socket, static function ($watcher) use ($server, $deferred, &$timer) {
$watcher = Loop::onWritable($socket, static function ($watcher) use ($server, $deferred, &$timer) {
Loop::cancel($watcher);
Loop::cancel($timer);
$server->connect = null;
$deferred->resolve();
$deferred->resolve($server);
});
// TODO: Respect timeout
$timer = Loop::delay(5000, function () use ($id, $deferred, $watcher, $uri) {
@ -400,9 +437,10 @@ class DefaultResolver implements Resolver {
$this->unloadServer($id);
$deferred->fail(new TimeoutException("Name resolution timed out, could not connect to server at $uri"));
});
return $deferred->promise();
}
return $server;
return new Success($server);
}
private function unloadServer($serverId, $error = null) {
@ -412,7 +450,6 @@ class DefaultResolver implements Resolver {
}
$server = $this->serverIdMap[$serverId];
Loop::cancel($server->watcherId);
unset(
$this->serverIdMap[$serverId],
$this->serverUriMap[$server->uri]
@ -428,37 +465,6 @@ class DefaultResolver implements Resolver {
}
}
private function onReadable($watcherId, $socket) {
$serverId = (int) $socket;
$packet = @\fread($socket, 512);
if ($packet != "") {
$server = $this->serverIdMap[$serverId];
if (\substr($server->uri, 0, 6) == "tcp://") {
if ($server->length == INF) {
$server->length = \unpack("n", $packet)[1];
$packet = \substr($packet, 2);
}
$server->buffer .= $packet;
while ($server->length <= \strlen($server->buffer)) {
$this->decodeResponsePacket($serverId, \substr($server->buffer, 0, $server->length));
$server->buffer = substr($server->buffer, $server->length);
if (\strlen($server->buffer) >= 2 + $server->length) {
$server->length = \unpack("n", $server->buffer)[1];
$server->buffer = \substr($server->buffer, 2);
} else {
$server->length = INF;
}
}
} else {
$this->decodeResponsePacket($serverId, $packet);
}
} else {
$this->unloadServer($serverId, new ResolutionException(
"Server connection failed"
));
}
}
private function decodeResponsePacket($serverId, $packet) {
try {
$response = $this->decoder->decode($packet);
@ -485,13 +491,17 @@ class DefaultResolver implements Resolver {
}
private function processDecodedResponse($serverId, $requestId, $response) {
/** @var \Amp\Deferred $deferred */
list($deferred, $name, $type, $uri) = $this->pendingRequests[$requestId];
// Retry via tcp if message has been truncated
if ($response->isTruncated()) {
if (\substr($uri, 0, 6) != "tcp://") {
$uri = \preg_replace("#[a-z.]+://#", "tcp://", $uri);
$deferred->resolve($this->doRequest($uri, $name, $type));
$deferred->resolve(call(function () use ($uri, $name, $type) {
$server = yield $this->loadServer($uri);
return yield from $this->doRequest($server, $name, $type);
}));
} else {
$this->finalizeResult($serverId, $requestId, new ResolutionException(
"Server returned truncated response"
@ -528,7 +538,6 @@ class DefaultResolver implements Resolver {
);
if (empty($server->pendingRequests)) {
$this->serverIdTimeoutMap[$server->id] = $this->now + self::IDLE_TIMEOUT;
Loop::disable($server->watcherId);
Loop::enable($this->serverTimeoutWatcher);
}
if ($error) {