diff --git a/README.md b/README.md index 73ea2fb6..1544ca56 100644 --- a/README.md +++ b/README.md @@ -70,6 +70,10 @@ as above if none can be found. Ideally, this method should thus be executed only once before the loop starts and not repeatedly while it is running. +> Also note that loading the system config will also read attempts, and + timeout from the options in resolv.conf and configures the retry and + timeout executors with the values it finds there. + But there's more. ## Caching diff --git a/src/Config/Config.php b/src/Config/Config.php index 0b19e9af..5d55b097 100644 --- a/src/Config/Config.php +++ b/src/Config/Config.php @@ -97,6 +97,29 @@ public static function loadResolvConfBlocking($path = null) } } + $matches = []; + preg_match_all('/^options.*\s*$/m', $contents, $matches); + if (isset($matches[0][0])) { + $options = preg_split('/\s+/', trim($matches[0][0])); + array_shift($options); + + foreach ($options as $option) { + $value = null; + if (strpos($option, ':') !== false) { + [$option, $value] = explode(':', $option, 2); + } + + switch ($option) { + case 'attempts': + $config->options->attempts = ((int) $value) > 5 ? 5 : (int) $value; + break; + case 'timeout': + $config->options->timeout = ((int) $value) > 30 ? 30 : (int) $value; + break; + } + } + } + return $config; } @@ -134,4 +157,13 @@ public static function loadWmicBlocking($command = null) } public $nameservers = []; + + /** + * @var Options + */ + public $options; + + public function __construct() { + $this->options = new Options(); + } } diff --git a/src/Config/Options.php b/src/Config/Options.php new file mode 100644 index 00000000..b80afae9 --- /dev/null +++ b/src/Config/Options.php @@ -0,0 +1,15 @@ + + */ + public $attempts = 2; + /** + * @var int<1, 30> + */ + public $timeout = 5; +} diff --git a/src/Resolver/Factory.php b/src/Resolver/Factory.php index eedebe1a..e05f59ed 100644 --- a/src/Resolver/Factory.php +++ b/src/Resolver/Factory.php @@ -6,6 +6,7 @@ use React\Cache\CacheInterface; use React\Dns\Config\Config; use React\Dns\Config\HostsFile; +use React\Dns\Config\Options; use React\Dns\Query\CachingExecutor; use React\Dns\Query\CoopExecutor; use React\Dns\Query\ExecutorInterface; @@ -125,26 +126,24 @@ private function createExecutor($nameserver, LoopInterface $loop) if ($tertiary !== false) { // 3 DNS servers given => nest first with fallback for second and third - return new CoopExecutor( - new RetryExecutor( + return $this->createTopLevelDecoratingExectors( + new FallbackExecutor( + $this->createSingleExecutor($primary, $nameserver->options, $loop), new FallbackExecutor( - $this->createSingleExecutor($primary, $loop), - new FallbackExecutor( - $this->createSingleExecutor($secondary, $loop), - $this->createSingleExecutor($tertiary, $loop) - ) + $this->createSingleExecutor($secondary, $nameserver->options, $loop), + $this->createSingleExecutor($tertiary, $nameserver->options, $loop) ) - ) + ), + $nameserver ); } 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) - ) - ) + return $this->createTopLevelDecoratingExectors( + new FallbackExecutor( + $this->createSingleExecutor($primary, $nameserver->options, $loop), + $this->createSingleExecutor($secondary, $nameserver->options, $loop) + ), + $nameserver ); } else { // 1 DNS server given => use single executor @@ -152,27 +151,35 @@ private function createExecutor($nameserver, LoopInterface $loop) } } - return new CoopExecutor(new RetryExecutor($this->createSingleExecutor($nameserver, $loop))); + return $this->createTopLevelDecoratingExectors($this->createSingleExecutor($nameserver, new Options(), $loop), $nameserver); + } + + private function createTopLevelDecoratingExectors(ExecutorInterface $executor, $nameserver) + { + $executor = new RetryExecutor($executor, (is_string($nameserver) ? new Options() : $nameserver->options)->attempts); + + return new CoopExecutor($executor); } /** * @param string $nameserver + * @param Options $options * @param LoopInterface $loop * @return ExecutorInterface * @throws \InvalidArgumentException for invalid DNS server address */ - private function createSingleExecutor($nameserver, LoopInterface $loop) + private function createSingleExecutor($nameserver, Options $options, LoopInterface $loop) { $parts = \parse_url($nameserver); if (isset($parts['scheme']) && $parts['scheme'] === 'tcp') { - $executor = $this->createTcpExecutor($nameserver, $loop); + $executor = $this->createTcpExecutor($nameserver, $options->timeout, $loop); } elseif (isset($parts['scheme']) && $parts['scheme'] === 'udp') { - $executor = $this->createUdpExecutor($nameserver, $loop); + $executor = $this->createUdpExecutor($nameserver, $options->timeout, $loop); } else { $executor = new SelectiveTransportExecutor( - $this->createUdpExecutor($nameserver, $loop), - $this->createTcpExecutor($nameserver, $loop) + $this->createUdpExecutor($nameserver, $options->timeout, $loop), + $this->createTcpExecutor($nameserver, $options->timeout, $loop) ); } @@ -181,33 +188,35 @@ private function createSingleExecutor($nameserver, LoopInterface $loop) /** * @param string $nameserver + * @param int $timeout * @param LoopInterface $loop * @return TimeoutExecutor * @throws \InvalidArgumentException for invalid DNS server address */ - private function createTcpExecutor($nameserver, LoopInterface $loop) + private function createTcpExecutor($nameserver, int $timeout, LoopInterface $loop) { return new TimeoutExecutor( new TcpTransportExecutor($nameserver, $loop), - 5.0, + $timeout, $loop ); } /** * @param string $nameserver + * @param int $timeout * @param LoopInterface $loop * @return TimeoutExecutor * @throws \InvalidArgumentException for invalid DNS server address */ - private function createUdpExecutor($nameserver, LoopInterface $loop) + private function createUdpExecutor($nameserver, int $timeout, LoopInterface $loop) { return new TimeoutExecutor( new UdpTransportExecutor( $nameserver, $loop ), - 5.0, + $timeout, $loop ); } diff --git a/tests/Config/ConfigTest.php b/tests/Config/ConfigTest.php index 46d2f53d..097c556f 100644 --- a/tests/Config/ConfigTest.php +++ b/tests/Config/ConfigTest.php @@ -30,6 +30,8 @@ public function testLoadsFromExplicitPath() $config = Config::loadResolvConfBlocking(__DIR__ . '/../Fixtures/etc/resolv.conf'); $this->assertEquals(['8.8.8.8'], $config->nameservers); + $this->assertEquals(4, $config->options->attempts); + $this->assertEquals(29, $config->options->timeout); } public function testLoadThrowsWhenPathIsInvalid() diff --git a/tests/Fixtures/etc/resolv.conf b/tests/Fixtures/etc/resolv.conf index cae093a8..f11cde9f 100644 --- a/tests/Fixtures/etc/resolv.conf +++ b/tests/Fixtures/etc/resolv.conf @@ -1 +1,2 @@ nameserver 8.8.8.8 +options timeout:29 trust-ad attempts:4