Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 49 additions & 0 deletions src/Query/FallbackExecutor.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
<?php

namespace React\Dns\Query;

use React\Promise\Promise;

final class FallbackExecutor implements ExecutorInterface
{
private $executor;
private $fallback;

public function __construct(ExecutorInterface $executor, ExecutorInterface $fallback)
{
$this->executor = $executor;
$this->fallback = $fallback;
}

public function query(Query $query)
{
$cancelled = false;
$fallback = $this->fallback;
$promise = $this->executor->query($query);

return new Promise(function ($resolve, $reject) use (&$promise, $fallback, $query, &$cancelled) {
$promise->then($resolve, function (\Exception $e1) use ($fallback, $query, $resolve, $reject, &$cancelled, &$promise) {
// reject if primary resolution rejected due to cancellation
if ($cancelled) {
$reject($e1);
return;
}

// start fallback query if primary query rejected
$promise = $fallback->query($query)->then($resolve, function (\Exception $e2) use ($e1, $reject) {
$append = $e2->getMessage();
if (($pos = strpos($append, ':')) !== false) {
$append = substr($append, $pos + 2);
}

// reject with combined error message if both queries fail
$reject(new \RuntimeException($e1->getMessage() . '. ' . $append));
});
});
}, function () use (&$promise, &$cancelled) {
// cancel pending query (primary or fallback)
$cancelled = true;
$promise->cancel();
});
}
}
87 changes: 72 additions & 15 deletions src/Resolver/Factory.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
use React\Dns\Query\CachingExecutor;
use React\Dns\Query\CoopExecutor;
use React\Dns\Query\ExecutorInterface;
use React\Dns\Query\FallbackExecutor;
use React\Dns\Query\HostsFileExecutor;
use React\Dns\Query\RetryExecutor;
use React\Dns\Query\SelectiveTransportExecutor;
Expand All @@ -24,8 +25,9 @@ final class Factory
*
* As of v1.7.0 it's recommended to pass a `Config` object instead of a
* single nameserver address. If the given config contains more than one DNS
* nameserver, only the primary will be used at the moment. A future version
* may take advantage of fallback DNS servers.
* nameserver, all DNS nameservers will be used in order. The primary DNS
* server will always be used first before falling back to the secondary or
* tertiary DNS server.
*
* @param Config|string $config DNS Config object (recommended) or single nameserver address
* @param LoopInterface $loop
Expand All @@ -45,8 +47,9 @@ public function create($config, LoopInterface $loop)
*
* As of v1.7.0 it's recommended to pass a `Config` object instead of a
* single nameserver address. If the given config contains more than one DNS
* nameserver, only the primary will be used at the moment. A future version
* may take advantage of fallback DNS servers.
* nameserver, all DNS nameservers will be used in order. The primary DNS
* server will always be used first before falling back to the secondary or
* tertiary DNS server.
*
* @param Config|string $config DNS Config object (recommended) or single nameserver address
* @param LoopInterface $loop
Expand Down Expand Up @@ -109,12 +112,56 @@ private function decorateHostsFileExecutor(ExecutorInterface $executor)
private function createExecutor($nameserver, LoopInterface $loop)
{
if ($nameserver instanceof Config) {
$nameserver = \reset($nameserver->nameservers);
if ($nameserver === false) {
if (!$nameserver->nameservers) {
throw new \UnderflowException('Empty config with no DNS servers');
}

// Hard-coded to check up to 3 DNS servers to match default limits in place in most systems (see MAXNS config).
// Note to future self: Recursion isn't too hard, but how deep do we really want to go?
$primary = reset($nameserver->nameservers);
$secondary = next($nameserver->nameservers);
$tertiary = next($nameserver->nameservers);

if ($tertiary !== false) {
// 3 DNS servers given => nest first with fallback for second and third
return new CoopExecutor(
new RetryExecutor(
new FallbackExecutor(
$this->createSingleExecutor($primary, $loop),
new FallbackExecutor(
$this->createSingleExecutor($secondary, $loop),
$this->createSingleExecutor($tertiary, $loop)
)
)
)
);
} elseif ($secondary !== false) {
// 2 DNS servers given => fallback from first to second
return new CoopExecutor(
new RetryExecutor(
new FallbackExecutor(
$this->createSingleExecutor($primary, $loop),
$this->createSingleExecutor($secondary, $loop)
)
)
);
} else {
// 1 DNS server given => use single executor
$nameserver = $primary;
}
}

return new CoopExecutor(new RetryExecutor($this->createSingleExecutor($nameserver, $loop)));
}

/**
* @param string $nameserver
* @param LoopInterface $loop
* @return ExecutorInterface
* @throws \InvalidArgumentException for invalid DNS server address
*/
private function createSingleExecutor($nameserver, LoopInterface $loop)
{
$parts = \parse_url($nameserver);

if (isset($parts['scheme']) && $parts['scheme'] === 'tcp') {
Expand All @@ -128,9 +175,15 @@ private function createExecutor($nameserver, LoopInterface $loop)
);
}

return new CoopExecutor($executor);
return $executor;
}

/**
* @param string $nameserver
* @param LoopInterface $loop
* @return TimeoutExecutor
* @throws \InvalidArgumentException for invalid DNS server address
*/
private function createTcpExecutor($nameserver, LoopInterface $loop)
{
return new TimeoutExecutor(
Expand All @@ -140,17 +193,21 @@ private function createTcpExecutor($nameserver, LoopInterface $loop)
);
}

/**
* @param string $nameserver
* @param LoopInterface $loop
* @return TimeoutExecutor
* @throws \InvalidArgumentException for invalid DNS server address
*/
private function createUdpExecutor($nameserver, LoopInterface $loop)
{
return new RetryExecutor(
new TimeoutExecutor(
new UdpTransportExecutor(
$nameserver,
$loop
),
5.0,
return new TimeoutExecutor(
new UdpTransportExecutor(
$nameserver,
$loop
)
),
5.0,
$loop
);
}
}
Loading