Skip to content

Commit 4dcac78

Browse files
committed
Use nested FallbackExecutor when config has tertiary DNS server
1 parent 5313d40 commit 4dcac78

File tree

2 files changed

+116
-6
lines changed

2 files changed

+116
-6
lines changed

src/Resolver/Factory.php

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,8 @@ final class Factory
2626
* As of v1.7.0 it's recommended to pass a `Config` object instead of a
2727
* single nameserver address. If the given config contains more than one DNS
2828
* nameserver, all DNS nameservers will be used in order. The primary DNS
29-
* server will always be used first before falling back to the secondary DNS
30-
* server.
29+
* server will always be used first before falling back to the secondary or
30+
* tertiary DNS server.
3131
*
3232
* @param Config|string $config DNS Config object (recommended) or single nameserver address
3333
* @param LoopInterface $loop
@@ -48,8 +48,8 @@ public function create($config, LoopInterface $loop)
4848
* As of v1.7.0 it's recommended to pass a `Config` object instead of a
4949
* single nameserver address. If the given config contains more than one DNS
5050
* nameserver, all DNS nameservers will be used in order. The primary DNS
51-
* server will always be used first before falling back to the secondary DNS
52-
* server.
51+
* server will always be used first before falling back to the secondary or
52+
* tertiary DNS server.
5353
*
5454
* @param Config|string $config DNS Config object (recommended) or single nameserver address
5555
* @param LoopInterface $loop
@@ -116,10 +116,27 @@ private function createExecutor($nameserver, LoopInterface $loop)
116116
throw new \UnderflowException('Empty config with no DNS servers');
117117
}
118118

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

122-
if ($secondary !== false) {
125+
if ($tertiary !== false) {
126+
// 3 DNS servers given => nest first with fallback for second and third
127+
return new CoopExecutor(
128+
new RetryExecutor(
129+
new FallbackExecutor(
130+
$this->createSingleExecutor($primary, $loop),
131+
new FallbackExecutor(
132+
$this->createSingleExecutor($secondary, $loop),
133+
$this->createSingleExecutor($tertiary, $loop)
134+
)
135+
)
136+
)
137+
);
138+
} elseif ($secondary !== false) {
139+
// 2 DNS servers given => fallback from first to second
123140
return new CoopExecutor(
124141
new RetryExecutor(
125142
new FallbackExecutor(
@@ -129,6 +146,7 @@ private function createExecutor($nameserver, LoopInterface $loop)
129146
)
130147
);
131148
} else {
149+
// 1 DNS server given => use single executor
132150
$nameserver = $primary;
133151
}
134152
}

tests/Resolver/FactoryTest.php

Lines changed: 93 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -178,7 +178,7 @@ public function createWithConfigWithTcpNameserverSchemeShouldCreateResolverWithT
178178
}
179179

180180
/** @test */
181-
public function createWithConfigWithMultipleWithTcpSchemeShouldCreateResolverWithTcpExecutorStack()
181+
public function createWithConfigWithTwoNameserversWithTcpSchemeShouldCreateResolverWithFallbackExecutorStack()
182182
{
183183
$loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
184184

@@ -244,6 +244,98 @@ public function createWithConfigWithMultipleWithTcpSchemeShouldCreateResolverWit
244244
$this->assertEquals('tcp://1.1.1.1:53', $nameserver);
245245
}
246246

247+
/** @test */
248+
public function createWithConfigWithThreeNameserversWithTcpSchemeShouldCreateResolverWithNestedFallbackExecutorStack()
249+
{
250+
$loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
251+
252+
$config = new Config();
253+
$config->nameservers[] = 'tcp://8.8.8.8:53';
254+
$config->nameservers[] = 'tcp://1.1.1.1:53';
255+
$config->nameservers[] = 'tcp://9.9.9.9:53';
256+
257+
$factory = new Factory();
258+
$resolver = $factory->create($config, $loop);
259+
260+
$this->assertInstanceOf('React\Dns\Resolver\Resolver', $resolver);
261+
262+
$coopExecutor = $this->getResolverPrivateExecutor($resolver);
263+
264+
$this->assertInstanceOf('React\Dns\Query\CoopExecutor', $coopExecutor);
265+
266+
$ref = new \ReflectionProperty($coopExecutor, 'executor');
267+
$ref->setAccessible(true);
268+
$retryExecutor = $ref->getValue($coopExecutor);
269+
270+
$this->assertInstanceOf('React\Dns\Query\RetryExecutor', $retryExecutor);
271+
272+
$ref = new \ReflectionProperty($retryExecutor, 'executor');
273+
$ref->setAccessible(true);
274+
$fallbackExecutor = $ref->getValue($retryExecutor);
275+
276+
$this->assertInstanceOf('React\Dns\Query\FallbackExecutor', $fallbackExecutor);
277+
278+
$ref = new \ReflectionProperty($fallbackExecutor, 'executor');
279+
$ref->setAccessible(true);
280+
$timeoutExecutor = $ref->getValue($fallbackExecutor);
281+
282+
$this->assertInstanceOf('React\Dns\Query\TimeoutExecutor', $timeoutExecutor);
283+
284+
$ref = new \ReflectionProperty($timeoutExecutor, 'executor');
285+
$ref->setAccessible(true);
286+
$tcpExecutor = $ref->getValue($timeoutExecutor);
287+
288+
$this->assertInstanceOf('React\Dns\Query\TcpTransportExecutor', $tcpExecutor);
289+
290+
$ref = new \ReflectionProperty($tcpExecutor, 'nameserver');
291+
$ref->setAccessible(true);
292+
$nameserver = $ref->getValue($tcpExecutor);
293+
294+
$this->assertEquals('tcp://8.8.8.8:53', $nameserver);
295+
296+
$ref = new \ReflectionProperty($fallbackExecutor, 'fallback');
297+
$ref->setAccessible(true);
298+
$fallbackExecutor = $ref->getValue($fallbackExecutor);
299+
300+
$this->assertInstanceOf('React\Dns\Query\FallbackExecutor', $fallbackExecutor);
301+
302+
$ref = new \ReflectionProperty($fallbackExecutor, 'executor');
303+
$ref->setAccessible(true);
304+
$timeoutExecutor = $ref->getValue($fallbackExecutor);
305+
306+
$this->assertInstanceOf('React\Dns\Query\TimeoutExecutor', $timeoutExecutor);
307+
308+
$ref = new \ReflectionProperty($timeoutExecutor, 'executor');
309+
$ref->setAccessible(true);
310+
$tcpExecutor = $ref->getValue($timeoutExecutor);
311+
312+
$this->assertInstanceOf('React\Dns\Query\TcpTransportExecutor', $tcpExecutor);
313+
314+
$ref = new \ReflectionProperty($tcpExecutor, 'nameserver');
315+
$ref->setAccessible(true);
316+
$nameserver = $ref->getValue($tcpExecutor);
317+
318+
$this->assertEquals('tcp://1.1.1.1:53', $nameserver);
319+
320+
$ref = new \ReflectionProperty($fallbackExecutor, 'fallback');
321+
$ref->setAccessible(true);
322+
$timeoutExecutor = $ref->getValue($fallbackExecutor);
323+
324+
$this->assertInstanceOf('React\Dns\Query\TimeoutExecutor', $timeoutExecutor);
325+
326+
$ref = new \ReflectionProperty($timeoutExecutor, 'executor');
327+
$ref->setAccessible(true);
328+
$tcpExecutor = $ref->getValue($timeoutExecutor);
329+
330+
$this->assertInstanceOf('React\Dns\Query\TcpTransportExecutor', $tcpExecutor);
331+
332+
$ref = new \ReflectionProperty($tcpExecutor, 'nameserver');
333+
$ref->setAccessible(true);
334+
$nameserver = $ref->getValue($tcpExecutor);
335+
336+
$this->assertEquals('tcp://9.9.9.9:53', $nameserver);
337+
}
338+
247339
/** @test */
248340
public function createShouldThrowWhenNameserverIsInvalid()
249341
{

0 commit comments

Comments
 (0)