From fa1684dcf3e2c32d3d379563f41327ad22ca4fe5 Mon Sep 17 00:00:00 2001 From: chrisdowson Date: Sat, 21 May 2022 21:03:30 +0800 Subject: [PATCH 1/7] support for search options in /etc/resolv.conf --- src/Config/Config.php | 9 ++++++++- src/Query/RetryExecutor.php | 20 ++++++++++++++++++-- 2 files changed, 26 insertions(+), 3 deletions(-) diff --git a/src/Config/Config.php b/src/Config/Config.php index 9677ee57..29d2c5b3 100644 --- a/src/Config/Config.php +++ b/src/Config/Config.php @@ -96,7 +96,13 @@ public static function loadResolvConfBlocking($path = null) $config->nameservers[] = $ip; } } - + $matches = array(); + preg_match_all('/^search.*\s*$/m', $contents, $matches); + if(isset($matches[0][0])){ + $searches = preg_split('/\s+/', trim($matches[0][0])); + unset($searches[0]); + $config->searches= array_values($searches); + } return $config; } @@ -134,4 +140,5 @@ public static function loadWmicBlocking($command = null) } public $nameservers = array(); + public $searches = array(); } diff --git a/src/Query/RetryExecutor.php b/src/Query/RetryExecutor.php index 64a15642..e6fb4eb3 100644 --- a/src/Query/RetryExecutor.php +++ b/src/Query/RetryExecutor.php @@ -2,6 +2,7 @@ namespace React\Dns\Query; +use React\Dns\Model\Message; use React\Promise\CancellablePromiseInterface; use React\Promise\Deferred; use React\Promise\PromiseInterface; @@ -10,11 +11,13 @@ final class RetryExecutor implements ExecutorInterface { private $executor; private $retries; + private $config; public function __construct(ExecutorInterface $executor, $retries = 2) { $this->executor = $executor; $this->retries = $retries; + $this->config = \React\Dns\Config\Config::loadSystemConfigBlocking(); } public function query(Query $query) @@ -30,9 +33,22 @@ public function tryQuery(Query $query, $retries) } }); - $success = function ($value) use ($deferred, &$errorback) { + $executor = $this->executor; + $q = clone $query; + $index = 0; + $success = function ($value) use ($deferred, &$promise, &$errorback, &$success, $query, $q, &$index, $executor) { $errorback = null; - $deferred->resolve($value); + //if Non-Existent Domain / NXDOMAIN, append domain option and retry + if($value->rcode==Message::RCODE_NAME_ERROR&&isset($this->config->searches[$index])){ + $query->name = $q->name.'.'.$this->config->searches[$index]; + $index++; + $promise = $executor->query($query)->then( + $success, + $errorback + ); + }else{ + $deferred->resolve($value); + } }; $executor = $this->executor; From 54ed2cf1d0632c6cdd21a97b0728765eb470f961 Mon Sep 17 00:00:00 2001 From: chrisdowson Date: Sat, 28 May 2022 23:38:36 +0800 Subject: [PATCH 2/7] 1. fix test case and optimized code --- src/Query/RetryExecutor.php | 17 +------ src/Query/TimeoutExecutor.php | 15 +++++- src/Query/UdpTransportExecutor.php | 9 +++- tests/FunctionalResolverTest.php | 58 ++++++++++++------------ tests/Query/TcpTransportExecutorTest.php | 6 +-- 5 files changed, 57 insertions(+), 48 deletions(-) diff --git a/src/Query/RetryExecutor.php b/src/Query/RetryExecutor.php index e6fb4eb3..75143225 100644 --- a/src/Query/RetryExecutor.php +++ b/src/Query/RetryExecutor.php @@ -34,24 +34,11 @@ public function tryQuery(Query $query, $retries) }); $executor = $this->executor; - $q = clone $query; - $index = 0; - $success = function ($value) use ($deferred, &$promise, &$errorback, &$success, $query, $q, &$index, $executor) { + $success = function ($value) use ($deferred, &$errorback) { $errorback = null; - //if Non-Existent Domain / NXDOMAIN, append domain option and retry - if($value->rcode==Message::RCODE_NAME_ERROR&&isset($this->config->searches[$index])){ - $query->name = $q->name.'.'.$this->config->searches[$index]; - $index++; - $promise = $executor->query($query)->then( - $success, - $errorback - ); - }else{ - $deferred->resolve($value); - } + $deferred->resolve($value); }; - $executor = $this->executor; $errorback = function ($e) use ($deferred, &$promise, $query, $success, &$errorback, &$retries, $executor) { if (!$e instanceof TimeoutException) { $errorback = null; diff --git a/src/Query/TimeoutExecutor.php b/src/Query/TimeoutExecutor.php index 15c8c22a..7e84b8d1 100644 --- a/src/Query/TimeoutExecutor.php +++ b/src/Query/TimeoutExecutor.php @@ -2,6 +2,7 @@ namespace React\Dns\Query; +use React\Dns\Model\Message; use React\EventLoop\Loop; use React\EventLoop\LoopInterface; use React\Promise\Timer; @@ -11,20 +12,32 @@ final class TimeoutExecutor implements ExecutorInterface private $executor; private $loop; private $timeout; + private $config; public function __construct(ExecutorInterface $executor, $timeout, LoopInterface $loop = null) { $this->executor = $executor; $this->loop = $loop ?: Loop::get(); $this->timeout = $timeout; + $this->config = \React\Dns\Config\Config::loadSystemConfigBlocking(); } public function query(Query $query) { - return Timer\timeout($this->executor->query($query), $this->timeout, $this->loop)->then(null, function ($e) use ($query) { + return $this->tryQuery($query, $query->name); + } + public function tryQuery(Query $query,$queryName="", $index=0){ + return Timer\timeout($this->executor->query($query), $this->timeout, $this->loop)->then(null, function ($e) use ($query, $queryName, $index) { if ($e instanceof Timer\TimeoutException) { $e = new TimeoutException(sprintf("DNS query for %s timed out", $query->describe()), 0, $e); } + //if Non-Existent Domain / NXDOMAIN, append domain option and retry + if ($e->getCode() == Message::RCODE_NAME_ERROR&&isset($this->config->searches[$index])) { + $query->name = $queryName.".".$this->config->searches[$index]; + echo $query->name; + $index++; + return $this->tryQuery($query, $queryName, $index); + } throw $e; }); } diff --git a/src/Query/UdpTransportExecutor.php b/src/Query/UdpTransportExecutor.php index 30a3d705..eea89733 100644 --- a/src/Query/UdpTransportExecutor.php +++ b/src/Query/UdpTransportExecutor.php @@ -5,6 +5,7 @@ use React\Dns\Model\Message; use React\Dns\Protocol\BinaryDumper; use React\Dns\Protocol\Parser; +use React\Dns\RecordNotFoundException; use React\EventLoop\Loop; use React\EventLoop\LoopInterface; use React\Promise\Deferred; @@ -208,7 +209,13 @@ public function query(Query $query) )); return; } - + if($response->rcode==Message::RCODE_NAME_ERROR){ + $deferred->reject(new RecordNotFoundException( + 'DNS query for ' . $query->describe() . ' failed: Non-Existent Domain / NXDOMAIN', + $response->rcode + )); + return; + } $deferred->resolve($response); }); diff --git a/tests/FunctionalResolverTest.php b/tests/FunctionalResolverTest.php index 9cb05615..adc78a93 100644 --- a/tests/FunctionalResolverTest.php +++ b/tests/FunctionalResolverTest.php @@ -39,9 +39,9 @@ public function testResolveAllLocalhostResolvesWithArray() /** * @group internet */ - public function testResolveGoogleResolves() + public function testResolveBingResolves() { - $promise = $this->resolver->resolve('google.com'); + $promise = $this->resolver->resolve('bing.com'); $promise->then($this->expectCallableOnce(), $this->expectCallableNever()); $this->loop->run(); @@ -50,12 +50,12 @@ public function testResolveGoogleResolves() /** * @group internet */ - public function testResolveGoogleOverUdpResolves() + public function testResolveBingleOverUdpResolves() { $factory = new Factory($this->loop); $this->resolver = $factory->create('udp://8.8.8.8', $this->loop); - $promise = $this->resolver->resolve('google.com'); + $promise = $this->resolver->resolve('bing.com'); $promise->then($this->expectCallableOnce(), $this->expectCallableNever()); $this->loop->run(); @@ -64,12 +64,12 @@ public function testResolveGoogleOverUdpResolves() /** * @group internet */ - public function testResolveGoogleOverTcpResolves() + public function testResolveBingOverTcpResolves() { $factory = new Factory($this->loop); $this->resolver = $factory->create('tcp://8.8.8.8', $this->loop); - $promise = $this->resolver->resolve('google.com'); + $promise = $this->resolver->resolve('bing.com'); $promise->then($this->expectCallableOnce(), $this->expectCallableNever()); $this->loop->run(); @@ -78,12 +78,12 @@ public function testResolveGoogleOverTcpResolves() /** * @group internet */ - public function testResolveAllGoogleMxResolvesWithCache() + public function testResolveAllBingMxResolvesWithCache() { $factory = new Factory(); $this->resolver = $factory->createCached('8.8.8.8', $this->loop); - $promise = $this->resolver->resolveAll('google.com', Message::TYPE_MX); + $promise = $this->resolver->resolveAll('bing.com', Message::TYPE_MX); $promise->then($this->expectCallableOnceWith($this->isType('array')), $this->expectCallableNever()); $this->loop->run(); @@ -91,12 +91,12 @@ public function testResolveAllGoogleMxResolvesWithCache() /** * @group internet */ - public function testResolveAllGoogleCaaResolvesWithCache() + public function testResolveAllbingCaaResolvesWithCache() { $factory = new Factory(); $this->resolver = $factory->createCached('8.8.8.8', $this->loop); - $promise = $this->resolver->resolveAll('google.com', Message::TYPE_CAA); + $promise = $this->resolver->resolveAll('bing.com', Message::TYPE_CAA); $promise->then($this->expectCallableOnceWith($this->isType('array')), $this->expectCallableNever()); $this->loop->run(); @@ -115,11 +115,13 @@ public function testResolveInvalidRejects() $promise->then(null, function ($reason) use (&$exception) { $exception = $reason; }); - - /** @var \React\Dns\RecordNotFoundException $exception */ - $this->assertInstanceOf('React\Dns\RecordNotFoundException', $exception); - $this->assertEquals('DNS query for example.invalid (A) returned an error response (Non-Existent Domain / NXDOMAIN)', $exception->getMessage()); - $this->assertEquals(Message::RCODE_NAME_ERROR, $exception->getCode()); + $config = \React\Dns\Config\Config::loadSystemConfigBlocking(); + if (!count($config->searches)) { + /** @var \React\Dns\RecordNotFoundException $exception */ + $this->assertInstanceOf('React\Dns\RecordNotFoundException', $exception); + $this->assertEquals('DNS query for example.invalid (A) returned an error response (Non-Existent Domain / NXDOMAIN)', $exception->getMessage()); + $this->assertEquals(Message::RCODE_NAME_ERROR, $exception->getCode()); + } } public function testResolveCancelledRejectsImmediately() @@ -127,7 +129,7 @@ public function testResolveCancelledRejectsImmediately() // max_nesting_level was set to 100 for PHP Versions < 5.4 which resulted in failing test for legacy PHP ini_set('xdebug.max_nesting_level', 256); - $promise = $this->resolver->resolve('google.com'); + $promise = $this->resolver->resolve('bing.com'); $promise->cancel(); $time = microtime(true); @@ -143,7 +145,7 @@ public function testResolveCancelledRejectsImmediately() /** @var \React\Dns\Query\CancellationException $exception */ $this->assertInstanceOf('React\Dns\Query\CancellationException', $exception); - $this->assertEquals('DNS query for google.com (A) has been cancelled', $exception->getMessage()); + $this->assertEquals('DNS query for bing.com (A) has been cancelled', $exception->getMessage()); } /** @@ -151,7 +153,7 @@ public function testResolveCancelledRejectsImmediately() */ public function testResolveAllInvalidTypeRejects() { - $promise = $this->resolver->resolveAll('google.com', Message::TYPE_PTR); + $promise = $this->resolver->resolveAll('bing.com', Message::TYPE_PTR); $this->loop->run(); @@ -162,17 +164,17 @@ public function testResolveAllInvalidTypeRejects() /** @var \React\Dns\RecordNotFoundException $exception */ $this->assertInstanceOf('React\Dns\RecordNotFoundException', $exception); - $this->assertEquals('DNS query for google.com (PTR) did not return a valid answer (NOERROR / NODATA)', $exception->getMessage()); + $this->assertEquals('DNS query for bing.com (PTR) did not return a valid answer (NOERROR / NODATA)', $exception->getMessage()); $this->assertEquals(0, $exception->getCode()); } - public function testInvalidResolverDoesNotResolveGoogle() + public function testInvalidResolverDoesNotResolvebing() { - $factory = new Factory(); - $this->resolver = $factory->create('255.255.255.255', $this->loop); - - $promise = $this->resolver->resolve('google.com'); + $factory = new \React\Dns\Resolver\Factory(); + $resolver = $factory->create('255.255.255.255', $this->loop); + $promise = $resolver->resolve('bing.com'); $promise->then($this->expectCallableNever(), $this->expectCallableOnce()); + $this->loop->run(); } public function testResolveShouldNotCauseGarbageReferencesWhenUsingInvalidNameserver() @@ -187,7 +189,7 @@ public function testResolveShouldNotCauseGarbageReferencesWhenUsingInvalidNamese gc_collect_cycles(); gc_collect_cycles(); // clear twice to avoid leftovers in PHP 7.4 with ext-xdebug and code coverage turned on - $promise = $this->resolver->resolve('google.com'); + $promise = $this->resolver->resolve('bing.com'); unset($promise); $this->assertEquals(0, gc_collect_cycles()); @@ -205,7 +207,7 @@ public function testResolveCachedShouldNotCauseGarbageReferencesWhenUsingInvalid gc_collect_cycles(); gc_collect_cycles(); // clear twice to avoid leftovers in PHP 7.4 with ext-xdebug and code coverage turned on - $promise = $this->resolver->resolve('google.com'); + $promise = $this->resolver->resolve('bing.com'); unset($promise); $this->assertEquals(0, gc_collect_cycles()); @@ -223,7 +225,7 @@ public function testCancelResolveShouldNotCauseGarbageReferences() gc_collect_cycles(); gc_collect_cycles(); // clear twice to avoid leftovers in PHP 7.4 with ext-xdebug and code coverage turned on - $promise = $this->resolver->resolve('google.com'); + $promise = $this->resolver->resolve('bing.com'); $promise->cancel(); $promise = null; @@ -242,7 +244,7 @@ public function testCancelResolveCachedShouldNotCauseGarbageReferences() gc_collect_cycles(); gc_collect_cycles(); // clear twice to avoid leftovers in PHP 7.4 with ext-xdebug and code coverage turned on - $promise = $this->resolver->resolve('google.com'); + $promise = $this->resolver->resolve('bing.com'); $promise->cancel(); $promise = null; diff --git a/tests/Query/TcpTransportExecutorTest.php b/tests/Query/TcpTransportExecutorTest.php index 860ad0dc..19c12ec2 100644 --- a/tests/Query/TcpTransportExecutorTest.php +++ b/tests/Query/TcpTransportExecutorTest.php @@ -533,7 +533,7 @@ public function testQueryRejectsWhenServerSendsInvalidMessage() $address = stream_socket_get_name($server, false); $executor = new TcpTransportExecutor($address, $loop); - $query = new Query('google.com', Message::TYPE_A, Message::CLASS_IN); + $query = new Query('bing.com', Message::TYPE_A, Message::CLASS_IN); $exception = null; $executor->query($query)->then( @@ -550,7 +550,7 @@ function ($e) use (&$exception) { /** @var \RuntimeException $exception */ $this->assertInstanceOf('RuntimeException', $exception); - $this->assertEquals('DNS query for google.com (A) failed: Invalid message received from DNS server tcp://' . $address, $exception->getMessage()); + $this->assertEquals('DNS query for bing.com (A) failed: Invalid message received from DNS server tcp://' . $address, $exception->getMessage()); } public function testQueryRejectsWhenServerSendsInvalidId() @@ -683,7 +683,7 @@ public function testQueryResolvesIfServerSendsValidResponse() $this->assertInstanceOf('React\Dns\Model\Message', $response); } - + public function testQueryRejectsIfSocketIsClosedAfterPreviousQueryThatWasStillPending() { $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock(); From 1a1b72d90956259ce686ec67d35091740639f906 Mon Sep 17 00:00:00 2001 From: chrisdowson Date: Sun, 29 May 2022 11:29:22 +0800 Subject: [PATCH 3/7] fix test case --- src/Query/TimeoutExecutor.php | 9 +++++---- src/Query/UdpTransportExecutor.php | 2 +- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/Query/TimeoutExecutor.php b/src/Query/TimeoutExecutor.php index 7e84b8d1..d7e0bb15 100644 --- a/src/Query/TimeoutExecutor.php +++ b/src/Query/TimeoutExecutor.php @@ -27,16 +27,17 @@ public function query(Query $query) return $this->tryQuery($query, $query->name); } public function tryQuery(Query $query,$queryName="", $index=0){ - return Timer\timeout($this->executor->query($query), $this->timeout, $this->loop)->then(null, function ($e) use ($query, $queryName, $index) { + $obj = $this; + return Timer\timeout($this->executor->query($query), $this->timeout, $this->loop)->then(null, function ($e) use ($query, $queryName, $index, $obj) { if ($e instanceof Timer\TimeoutException) { $e = new TimeoutException(sprintf("DNS query for %s timed out", $query->describe()), 0, $e); } //if Non-Existent Domain / NXDOMAIN, append domain option and retry - if ($e->getCode() == Message::RCODE_NAME_ERROR&&isset($this->config->searches[$index])) { - $query->name = $queryName.".".$this->config->searches[$index]; + if ($e->getCode() == Message::RCODE_NAME_ERROR&&isset($obj->config->searches[$index])) { + $query->name = $queryName.".".$obj->config->searches[$index]; echo $query->name; $index++; - return $this->tryQuery($query, $queryName, $index); + return $obj->tryQuery($query, $queryName, $index); } throw $e; }); diff --git a/src/Query/UdpTransportExecutor.php b/src/Query/UdpTransportExecutor.php index eea89733..7a53145a 100644 --- a/src/Query/UdpTransportExecutor.php +++ b/src/Query/UdpTransportExecutor.php @@ -211,7 +211,7 @@ public function query(Query $query) } if($response->rcode==Message::RCODE_NAME_ERROR){ $deferred->reject(new RecordNotFoundException( - 'DNS query for ' . $query->describe() . ' failed: Non-Existent Domain / NXDOMAIN', + 'DNS query for ' . $query->describe() . ' returned an error response (Non-Existent Domain / NXDOMAIN)', $response->rcode )); return; From 0ba20109822277e38437d23a3b4745965765392a Mon Sep 17 00:00:00 2001 From: chrisdowson Date: Sun, 29 May 2022 11:37:51 +0800 Subject: [PATCH 4/7] fix test case --- src/Query/TimeoutExecutor.php | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/Query/TimeoutExecutor.php b/src/Query/TimeoutExecutor.php index d7e0bb15..6871236d 100644 --- a/src/Query/TimeoutExecutor.php +++ b/src/Query/TimeoutExecutor.php @@ -28,13 +28,14 @@ public function query(Query $query) } public function tryQuery(Query $query,$queryName="", $index=0){ $obj = $this; - return Timer\timeout($this->executor->query($query), $this->timeout, $this->loop)->then(null, function ($e) use ($query, $queryName, $index, $obj) { + $config = $this->config; + return Timer\timeout($this->executor->query($query), $this->timeout, $this->loop)->then(null, function ($e) use ($query, $queryName, $index, $obj, $config) { if ($e instanceof Timer\TimeoutException) { $e = new TimeoutException(sprintf("DNS query for %s timed out", $query->describe()), 0, $e); } //if Non-Existent Domain / NXDOMAIN, append domain option and retry - if ($e->getCode() == Message::RCODE_NAME_ERROR&&isset($obj->config->searches[$index])) { - $query->name = $queryName.".".$obj->config->searches[$index]; + if ($e->getCode() == Message::RCODE_NAME_ERROR&&isset($config->searches[$index])) { + $query->name = $queryName.".".$config->searches[$index]; echo $query->name; $index++; return $obj->tryQuery($query, $queryName, $index); From 38c9526c5b672b6e1cffafaf9027ebbfa763632e Mon Sep 17 00:00:00 2001 From: chrisdowson Date: Sat, 24 Dec 2022 21:41:52 +0800 Subject: [PATCH 5/7] 1.revert --- tests/FunctionalResolverTest.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/FunctionalResolverTest.php b/tests/FunctionalResolverTest.php index 97f6ad78..94c0df1b 100644 --- a/tests/FunctionalResolverTest.php +++ b/tests/FunctionalResolverTest.php @@ -170,10 +170,10 @@ public function testResolveAllInvalidTypeRejects() public function testInvalidResolverDoesNotResolvebing() { $factory = new Factory(); - $this->resolver = $factory->create('255.255.255.255'); - $promise = $this->resolver->resolve('google.com'); + $this->resolver = $factory->create('255.255.255.255', $this->loop); + + $promise = $this->resolver->resolve('bing.com'); $promise->then($this->expectCallableNever(), $this->expectCallableOnce()); - $this->loop->run(); } public function testResolveShouldNotCauseGarbageReferencesWhenUsingInvalidNameserver() From 8980bdd7772aee372abc6f90f630e9453946b272 Mon Sep 17 00:00:00 2001 From: chrisdowson Date: Sat, 24 Dec 2022 21:51:28 +0800 Subject: [PATCH 6/7] 1.fix bug --- tests/FunctionalResolverTest.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/FunctionalResolverTest.php b/tests/FunctionalResolverTest.php index 94c0df1b..7d110dbf 100644 --- a/tests/FunctionalResolverTest.php +++ b/tests/FunctionalResolverTest.php @@ -170,10 +170,11 @@ public function testResolveAllInvalidTypeRejects() public function testInvalidResolverDoesNotResolvebing() { $factory = new Factory(); - $this->resolver = $factory->create('255.255.255.255', $this->loop); + $this->resolver = $factory->create('255.255.255.255'); $promise = $this->resolver->resolve('bing.com'); $promise->then($this->expectCallableNever(), $this->expectCallableOnce()); + Loop::run(); } public function testResolveShouldNotCauseGarbageReferencesWhenUsingInvalidNameserver() From b3e980a2abd6c19b78539af78a2845ea8057c7ca Mon Sep 17 00:00:00 2001 From: chrisdowson Date: Mon, 2 Jan 2023 23:12:58 +0800 Subject: [PATCH 7/7] Optimize code --- src/Query/RetryExecutor.php | 2 +- src/Query/UdpTransportExecutor.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Query/RetryExecutor.php b/src/Query/RetryExecutor.php index bb956611..65cd5097 100644 --- a/src/Query/RetryExecutor.php +++ b/src/Query/RetryExecutor.php @@ -31,12 +31,12 @@ public function tryQuery(Query $query, $retries) } }); - $executor = $this->executor; $success = function ($value) use ($deferred, &$errorback) { $errorback = null; $deferred->resolve($value); }; + $executor = $this->executor; $errorback = function ($e) use ($deferred, &$promise, $query, $success, &$errorback, &$retries, $executor) { if (!$e instanceof TimeoutException) { $errorback = null; diff --git a/src/Query/UdpTransportExecutor.php b/src/Query/UdpTransportExecutor.php index 7a53145a..2765d7c3 100644 --- a/src/Query/UdpTransportExecutor.php +++ b/src/Query/UdpTransportExecutor.php @@ -209,7 +209,7 @@ public function query(Query $query) )); return; } - if($response->rcode==Message::RCODE_NAME_ERROR){ + if($response->rcode == Message::RCODE_NAME_ERROR){ $deferred->reject(new RecordNotFoundException( 'DNS query for ' . $query->describe() . ' returned an error response (Non-Existent Domain / NXDOMAIN)', $response->rcode