Factory.php 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214
  1. <?php
  2. namespace React\Dns\Resolver;
  3. use React\Cache\ArrayCache;
  4. use React\Cache\CacheInterface;
  5. use React\Dns\Config\Config;
  6. use React\Dns\Config\HostsFile;
  7. use React\Dns\Query\CachingExecutor;
  8. use React\Dns\Query\CoopExecutor;
  9. use React\Dns\Query\ExecutorInterface;
  10. use React\Dns\Query\FallbackExecutor;
  11. use React\Dns\Query\HostsFileExecutor;
  12. use React\Dns\Query\RetryExecutor;
  13. use React\Dns\Query\SelectiveTransportExecutor;
  14. use React\Dns\Query\TcpTransportExecutor;
  15. use React\Dns\Query\TimeoutExecutor;
  16. use React\Dns\Query\UdpTransportExecutor;
  17. use React\EventLoop\Loop;
  18. use React\EventLoop\LoopInterface;
  19. final class Factory
  20. {
  21. /**
  22. * Creates a DNS resolver instance for the given DNS config
  23. *
  24. * As of v1.7.0 it's recommended to pass a `Config` object instead of a
  25. * single nameserver address. If the given config contains more than one DNS
  26. * nameserver, all DNS nameservers will be used in order. The primary DNS
  27. * server will always be used first before falling back to the secondary or
  28. * tertiary DNS server.
  29. *
  30. * @param Config|string $config DNS Config object (recommended) or single nameserver address
  31. * @param ?LoopInterface $loop
  32. * @return \React\Dns\Resolver\ResolverInterface
  33. * @throws \InvalidArgumentException for invalid DNS server address
  34. * @throws \UnderflowException when given DNS Config object has an empty list of nameservers
  35. */
  36. public function create($config, LoopInterface $loop = null)
  37. {
  38. $executor = $this->decorateHostsFileExecutor($this->createExecutor($config, $loop ?: Loop::get()));
  39. return new Resolver($executor);
  40. }
  41. /**
  42. * Creates a cached DNS resolver instance for the given DNS config and cache
  43. *
  44. * As of v1.7.0 it's recommended to pass a `Config` object instead of a
  45. * single nameserver address. If the given config contains more than one DNS
  46. * nameserver, all DNS nameservers will be used in order. The primary DNS
  47. * server will always be used first before falling back to the secondary or
  48. * tertiary DNS server.
  49. *
  50. * @param Config|string $config DNS Config object (recommended) or single nameserver address
  51. * @param ?LoopInterface $loop
  52. * @param ?CacheInterface $cache
  53. * @return \React\Dns\Resolver\ResolverInterface
  54. * @throws \InvalidArgumentException for invalid DNS server address
  55. * @throws \UnderflowException when given DNS Config object has an empty list of nameservers
  56. */
  57. public function createCached($config, LoopInterface $loop = null, CacheInterface $cache = null)
  58. {
  59. // default to keeping maximum of 256 responses in cache unless explicitly given
  60. if (!($cache instanceof CacheInterface)) {
  61. $cache = new ArrayCache(256);
  62. }
  63. $executor = $this->createExecutor($config, $loop ?: Loop::get());
  64. $executor = new CachingExecutor($executor, $cache);
  65. $executor = $this->decorateHostsFileExecutor($executor);
  66. return new Resolver($executor);
  67. }
  68. /**
  69. * Tries to load the hosts file and decorates the given executor on success
  70. *
  71. * @param ExecutorInterface $executor
  72. * @return ExecutorInterface
  73. * @codeCoverageIgnore
  74. */
  75. private function decorateHostsFileExecutor(ExecutorInterface $executor)
  76. {
  77. try {
  78. $executor = new HostsFileExecutor(
  79. HostsFile::loadFromPathBlocking(),
  80. $executor
  81. );
  82. } catch (\RuntimeException $e) {
  83. // ignore this file if it can not be loaded
  84. }
  85. // Windows does not store localhost in hosts file by default but handles this internally
  86. // To compensate for this, we explicitly use hard-coded defaults for localhost
  87. if (DIRECTORY_SEPARATOR === '\\') {
  88. $executor = new HostsFileExecutor(
  89. new HostsFile("127.0.0.1 localhost\n::1 localhost"),
  90. $executor
  91. );
  92. }
  93. return $executor;
  94. }
  95. /**
  96. * @param Config|string $nameserver
  97. * @param LoopInterface $loop
  98. * @return CoopExecutor
  99. * @throws \InvalidArgumentException for invalid DNS server address
  100. * @throws \UnderflowException when given DNS Config object has an empty list of nameservers
  101. */
  102. private function createExecutor($nameserver, LoopInterface $loop)
  103. {
  104. if ($nameserver instanceof Config) {
  105. if (!$nameserver->nameservers) {
  106. throw new \UnderflowException('Empty config with no DNS servers');
  107. }
  108. // Hard-coded to check up to 3 DNS servers to match default limits in place in most systems (see MAXNS config).
  109. // Note to future self: Recursion isn't too hard, but how deep do we really want to go?
  110. $primary = reset($nameserver->nameservers);
  111. $secondary = next($nameserver->nameservers);
  112. $tertiary = next($nameserver->nameservers);
  113. if ($tertiary !== false) {
  114. // 3 DNS servers given => nest first with fallback for second and third
  115. return new CoopExecutor(
  116. new RetryExecutor(
  117. new FallbackExecutor(
  118. $this->createSingleExecutor($primary, $loop),
  119. new FallbackExecutor(
  120. $this->createSingleExecutor($secondary, $loop),
  121. $this->createSingleExecutor($tertiary, $loop)
  122. )
  123. )
  124. )
  125. );
  126. } elseif ($secondary !== false) {
  127. // 2 DNS servers given => fallback from first to second
  128. return new CoopExecutor(
  129. new RetryExecutor(
  130. new FallbackExecutor(
  131. $this->createSingleExecutor($primary, $loop),
  132. $this->createSingleExecutor($secondary, $loop)
  133. )
  134. )
  135. );
  136. } else {
  137. // 1 DNS server given => use single executor
  138. $nameserver = $primary;
  139. }
  140. }
  141. return new CoopExecutor(new RetryExecutor($this->createSingleExecutor($nameserver, $loop)));
  142. }
  143. /**
  144. * @param string $nameserver
  145. * @param LoopInterface $loop
  146. * @return ExecutorInterface
  147. * @throws \InvalidArgumentException for invalid DNS server address
  148. */
  149. private function createSingleExecutor($nameserver, LoopInterface $loop)
  150. {
  151. $parts = \parse_url($nameserver);
  152. if (isset($parts['scheme']) && $parts['scheme'] === 'tcp') {
  153. $executor = $this->createTcpExecutor($nameserver, $loop);
  154. } elseif (isset($parts['scheme']) && $parts['scheme'] === 'udp') {
  155. $executor = $this->createUdpExecutor($nameserver, $loop);
  156. } else {
  157. $executor = new SelectiveTransportExecutor(
  158. $this->createUdpExecutor($nameserver, $loop),
  159. $this->createTcpExecutor($nameserver, $loop)
  160. );
  161. }
  162. return $executor;
  163. }
  164. /**
  165. * @param string $nameserver
  166. * @param LoopInterface $loop
  167. * @return TimeoutExecutor
  168. * @throws \InvalidArgumentException for invalid DNS server address
  169. */
  170. private function createTcpExecutor($nameserver, LoopInterface $loop)
  171. {
  172. return new TimeoutExecutor(
  173. new TcpTransportExecutor($nameserver, $loop),
  174. 5.0,
  175. $loop
  176. );
  177. }
  178. /**
  179. * @param string $nameserver
  180. * @param LoopInterface $loop
  181. * @return TimeoutExecutor
  182. * @throws \InvalidArgumentException for invalid DNS server address
  183. */
  184. private function createUdpExecutor($nameserver, LoopInterface $loop)
  185. {
  186. return new TimeoutExecutor(
  187. new UdpTransportExecutor(
  188. $nameserver,
  189. $loop
  190. ),
  191. 5.0,
  192. $loop
  193. );
  194. }
  195. }