Skip to content

Commit 2c1c8cf

Browse files
committed
Use FallbackExecutor when secondary DNS server is given in Config
1 parent a6d23c8 commit 2c1c8cf

File tree

2 files changed

+115
-7
lines changed

2 files changed

+115
-7
lines changed

src/Resolver/Factory.php

Lines changed: 48 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
use React\Dns\Query\CachingExecutor;
1010
use React\Dns\Query\CoopExecutor;
1111
use React\Dns\Query\ExecutorInterface;
12+
use React\Dns\Query\FallbackExecutor;
1213
use React\Dns\Query\HostsFileExecutor;
1314
use React\Dns\Query\RetryExecutor;
1415
use React\Dns\Query\SelectiveTransportExecutor;
@@ -24,8 +25,9 @@ final class Factory
2425
*
2526
* As of v1.7.0 it's recommended to pass a `Config` object instead of a
2627
* single nameserver address. If the given config contains more than one DNS
27-
* nameserver, only the primary will be used at the moment. A future version
28-
* may take advantage of fallback DNS servers.
28+
* 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.
2931
*
3032
* @param Config|string $config DNS Config object (recommended) or single nameserver address
3133
* @param LoopInterface $loop
@@ -45,8 +47,9 @@ public function create($config, LoopInterface $loop)
4547
*
4648
* As of v1.7.0 it's recommended to pass a `Config` object instead of a
4749
* single nameserver address. If the given config contains more than one DNS
48-
* nameserver, only the primary will be used at the moment. A future version
49-
* may take advantage of fallback DNS servers.
50+
* 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.
5053
*
5154
* @param Config|string $config DNS Config object (recommended) or single nameserver address
5255
* @param LoopInterface $loop
@@ -109,12 +112,38 @@ private function decorateHostsFileExecutor(ExecutorInterface $executor)
109112
private function createExecutor($nameserver, LoopInterface $loop)
110113
{
111114
if ($nameserver instanceof Config) {
112-
$nameserver = \reset($nameserver->nameservers);
113-
if ($nameserver === false) {
115+
if (!$nameserver->nameservers) {
114116
throw new \UnderflowException('Empty config with no DNS servers');
115117
}
118+
119+
$primary = reset($nameserver->nameservers);
120+
$secondary = next($nameserver->nameservers);
121+
122+
if ($secondary !== false) {
123+
return new CoopExecutor(
124+
new RetryExecutor(
125+
new FallbackExecutor(
126+
$this->createSingleExecutor($primary, $loop),
127+
$this->createSingleExecutor($secondary, $loop)
128+
)
129+
)
130+
);
131+
} else {
132+
$nameserver = $primary;
133+
}
116134
}
117135

136+
return new CoopExecutor(new RetryExecutor($this->createSingleExecutor($nameserver, $loop)));
137+
}
138+
139+
/**
140+
* @param string $nameserver
141+
* @param LoopInterface $loop
142+
* @return ExecutorInterface
143+
* @throws \InvalidArgumentException for invalid DNS server address
144+
*/
145+
private function createSingleExecutor($nameserver, LoopInterface $loop)
146+
{
118147
$parts = \parse_url($nameserver);
119148

120149
if (isset($parts['scheme']) && $parts['scheme'] === 'tcp') {
@@ -128,9 +157,15 @@ private function createExecutor($nameserver, LoopInterface $loop)
128157
);
129158
}
130159

131-
return new CoopExecutor(new RetryExecutor($executor));
160+
return $executor;
132161
}
133162

163+
/**
164+
* @param string $nameserver
165+
* @param LoopInterface $loop
166+
* @return TimeoutExecutor
167+
* @throws \InvalidArgumentException for invalid DNS server address
168+
*/
134169
private function createTcpExecutor($nameserver, LoopInterface $loop)
135170
{
136171
return new TimeoutExecutor(
@@ -140,6 +175,12 @@ private function createTcpExecutor($nameserver, LoopInterface $loop)
140175
);
141176
}
142177

178+
/**
179+
* @param string $nameserver
180+
* @param LoopInterface $loop
181+
* @return TimeoutExecutor
182+
* @throws \InvalidArgumentException for invalid DNS server address
183+
*/
143184
private function createUdpExecutor($nameserver, LoopInterface $loop)
144185
{
145186
return new TimeoutExecutor(

tests/Resolver/FactoryTest.php

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,73 @@ public function createWithConfigWithTcpNameserverSchemeShouldCreateResolverWithT
177177
$this->assertInstanceOf('React\Dns\Query\TcpTransportExecutor', $tcpExecutor);
178178
}
179179

180+
/** @test */
181+
public function createWithConfigWithMultipleWithTcpSchemeShouldCreateResolverWithTcpExecutorStack()
182+
{
183+
$loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
184+
185+
$config = new Config();
186+
$config->nameservers[] = 'tcp://8.8.8.8:53';
187+
$config->nameservers[] = 'tcp://1.1.1.1:53';
188+
189+
$factory = new Factory();
190+
$resolver = $factory->create($config, $loop);
191+
192+
$this->assertInstanceOf('React\Dns\Resolver\Resolver', $resolver);
193+
194+
$coopExecutor = $this->getResolverPrivateExecutor($resolver);
195+
196+
$this->assertInstanceOf('React\Dns\Query\CoopExecutor', $coopExecutor);
197+
198+
$ref = new \ReflectionProperty($coopExecutor, 'executor');
199+
$ref->setAccessible(true);
200+
$retryExecutor = $ref->getValue($coopExecutor);
201+
202+
$this->assertInstanceOf('React\Dns\Query\RetryExecutor', $retryExecutor);
203+
204+
$ref = new \ReflectionProperty($retryExecutor, 'executor');
205+
$ref->setAccessible(true);
206+
$fallbackExecutor = $ref->getValue($retryExecutor);
207+
208+
$this->assertInstanceOf('React\Dns\Query\FallbackExecutor', $fallbackExecutor);
209+
210+
$ref = new \ReflectionProperty($fallbackExecutor, 'executor');
211+
$ref->setAccessible(true);
212+
$timeoutExecutor = $ref->getValue($fallbackExecutor);
213+
214+
$this->assertInstanceOf('React\Dns\Query\TimeoutExecutor', $timeoutExecutor);
215+
216+
$ref = new \ReflectionProperty($timeoutExecutor, 'executor');
217+
$ref->setAccessible(true);
218+
$tcpExecutor = $ref->getValue($timeoutExecutor);
219+
220+
$this->assertInstanceOf('React\Dns\Query\TcpTransportExecutor', $tcpExecutor);
221+
222+
$ref = new \ReflectionProperty($tcpExecutor, 'nameserver');
223+
$ref->setAccessible(true);
224+
$nameserver = $ref->getValue($tcpExecutor);
225+
226+
$this->assertEquals('tcp://8.8.8.8:53', $nameserver);
227+
228+
$ref = new \ReflectionProperty($fallbackExecutor, 'fallback');
229+
$ref->setAccessible(true);
230+
$timeoutExecutor = $ref->getValue($fallbackExecutor);
231+
232+
$this->assertInstanceOf('React\Dns\Query\TimeoutExecutor', $timeoutExecutor);
233+
234+
$ref = new \ReflectionProperty($timeoutExecutor, 'executor');
235+
$ref->setAccessible(true);
236+
$tcpExecutor = $ref->getValue($timeoutExecutor);
237+
238+
$this->assertInstanceOf('React\Dns\Query\TcpTransportExecutor', $tcpExecutor);
239+
240+
$ref = new \ReflectionProperty($tcpExecutor, 'nameserver');
241+
$ref->setAccessible(true);
242+
$nameserver = $ref->getValue($tcpExecutor);
243+
244+
$this->assertEquals('tcp://1.1.1.1:53', $nameserver);
245+
}
246+
180247
/** @test */
181248
public function createShouldThrowWhenNameserverIsInvalid()
182249
{

0 commit comments

Comments
 (0)