99use React \Dns \Query \CachingExecutor ;
1010use React \Dns \Query \CoopExecutor ;
1111use React \Dns \Query \ExecutorInterface ;
12+ use React \Dns \Query \FallbackExecutor ;
1213use React \Dns \Query \HostsFileExecutor ;
1314use React \Dns \Query \RetryExecutor ;
1415use 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 or
30+ * tertiary DNS 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 or
52+ * tertiary DNS server.
5053 *
5154 * @param Config|string $config DNS Config object (recommended) or single nameserver address
5255 * @param LoopInterface $loop
@@ -109,12 +112,56 @@ 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+ // 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?
121+ $ primary = reset ($ nameserver ->nameservers );
122+ $ secondary = next ($ nameserver ->nameservers );
123+ $ tertiary = next ($ nameserver ->nameservers );
124+
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
140+ return new CoopExecutor (
141+ new RetryExecutor (
142+ new FallbackExecutor (
143+ $ this ->createSingleExecutor ($ primary , $ loop ),
144+ $ this ->createSingleExecutor ($ secondary , $ loop )
145+ )
146+ )
147+ );
148+ } else {
149+ // 1 DNS server given => use single executor
150+ $ nameserver = $ primary ;
151+ }
116152 }
117153
154+ return new CoopExecutor (new RetryExecutor ($ this ->createSingleExecutor ($ nameserver , $ loop )));
155+ }
156+
157+ /**
158+ * @param string $nameserver
159+ * @param LoopInterface $loop
160+ * @return ExecutorInterface
161+ * @throws \InvalidArgumentException for invalid DNS server address
162+ */
163+ private function createSingleExecutor ($ nameserver , LoopInterface $ loop )
164+ {
118165 $ parts = \parse_url ($ nameserver );
119166
120167 if (isset ($ parts ['scheme ' ]) && $ parts ['scheme ' ] === 'tcp ' ) {
@@ -128,9 +175,15 @@ private function createExecutor($nameserver, LoopInterface $loop)
128175 );
129176 }
130177
131- return new CoopExecutor ( $ executor) ;
178+ return $ executor ;
132179 }
133180
181+ /**
182+ * @param string $nameserver
183+ * @param LoopInterface $loop
184+ * @return TimeoutExecutor
185+ * @throws \InvalidArgumentException for invalid DNS server address
186+ */
134187 private function createTcpExecutor ($ nameserver , LoopInterface $ loop )
135188 {
136189 return new TimeoutExecutor (
@@ -140,17 +193,21 @@ private function createTcpExecutor($nameserver, LoopInterface $loop)
140193 );
141194 }
142195
196+ /**
197+ * @param string $nameserver
198+ * @param LoopInterface $loop
199+ * @return TimeoutExecutor
200+ * @throws \InvalidArgumentException for invalid DNS server address
201+ */
143202 private function createUdpExecutor ($ nameserver , LoopInterface $ loop )
144203 {
145- return new RetryExecutor (
146- new TimeoutExecutor (
147- new UdpTransportExecutor (
148- $ nameserver ,
149- $ loop
150- ),
151- 5.0 ,
204+ return new TimeoutExecutor (
205+ new UdpTransportExecutor (
206+ $ nameserver ,
152207 $ loop
153- )
208+ ),
209+ 5.0 ,
210+ $ loop
154211 );
155212 }
156213}
0 commit comments