1# Dns 2 3[![CI status](https://github.com/reactphp/dns/workflows/CI/badge.svg)](https://github.com/reactphp/dns/actions) 4 5Async DNS resolver for [ReactPHP](https://reactphp.org/). 6 7The main point of the DNS component is to provide async DNS resolution. 8However, it is really a toolkit for working with DNS messages, and could 9easily be used to create a DNS server. 10 11**Table of contents** 12 13* [Basic usage](#basic-usage) 14* [Caching](#caching) 15 * [Custom cache adapter](#custom-cache-adapter) 16* [ResolverInterface](#resolverinterface) 17 * [resolve()](#resolve) 18 * [resolveAll()](#resolveall) 19* [Advanced usage](#advanced-usage) 20 * [UdpTransportExecutor](#udptransportexecutor) 21 * [TcpTransportExecutor](#tcptransportexecutor) 22 * [SelectiveTransportExecutor](#selectivetransportexecutor) 23 * [HostsFileExecutor](#hostsfileexecutor) 24* [Install](#install) 25* [Tests](#tests) 26* [License](#license) 27* [References](#references) 28 29## Basic usage 30 31The most basic usage is to just create a resolver through the resolver 32factory. All you need to give it is a nameserver, then you can start resolving 33names, baby! 34 35```php 36$loop = React\EventLoop\Factory::create(); 37 38$config = React\Dns\Config\Config::loadSystemConfigBlocking(); 39$server = $config->nameservers ? reset($config->nameservers) : '8.8.8.8'; 40 41$factory = new React\Dns\Resolver\Factory(); 42$dns = $factory->create($server, $loop); 43 44$dns->resolve('igor.io')->then(function ($ip) { 45 echo "Host: $ip\n"; 46}); 47 48$loop->run(); 49``` 50 51See also the [first example](examples). 52 53The `Config` class can be used to load the system default config. This is an 54operation that may access the filesystem and block. Ideally, this method should 55thus be executed only once before the loop starts and not repeatedly while it is 56running. 57Note that this class may return an *empty* configuration if the system config 58can not be loaded. As such, you'll likely want to apply a default nameserver 59as above if none can be found. 60 61> Note that the factory loads the hosts file from the filesystem once when 62 creating the resolver instance. 63 Ideally, this method should thus be executed only once before the loop starts 64 and not repeatedly while it is running. 65 66But there's more. 67 68## Caching 69 70You can cache results by configuring the resolver to use a `CachedExecutor`: 71 72```php 73$loop = React\EventLoop\Factory::create(); 74 75$config = React\Dns\Config\Config::loadSystemConfigBlocking(); 76$server = $config->nameservers ? reset($config->nameservers) : '8.8.8.8'; 77 78$factory = new React\Dns\Resolver\Factory(); 79$dns = $factory->createCached($server, $loop); 80 81$dns->resolve('igor.io')->then(function ($ip) { 82 echo "Host: $ip\n"; 83}); 84 85... 86 87$dns->resolve('igor.io')->then(function ($ip) { 88 echo "Host: $ip\n"; 89}); 90 91$loop->run(); 92``` 93 94If the first call returns before the second, only one query will be executed. 95The second result will be served from an in memory cache. 96This is particularly useful for long running scripts where the same hostnames 97have to be looked up multiple times. 98 99See also the [third example](examples). 100 101### Custom cache adapter 102 103By default, the above will use an in memory cache. 104 105You can also specify a custom cache implementing [`CacheInterface`](https://github.com/reactphp/cache) to handle the record cache instead: 106 107```php 108$cache = new React\Cache\ArrayCache(); 109$loop = React\EventLoop\Factory::create(); 110$factory = new React\Dns\Resolver\Factory(); 111$dns = $factory->createCached('8.8.8.8', $loop, $cache); 112``` 113 114See also the wiki for possible [cache implementations](https://github.com/reactphp/react/wiki/Users#cache-implementations). 115 116## ResolverInterface 117 118<a id="resolver"><!-- legacy reference --></a> 119 120### resolve() 121 122The `resolve(string $domain): PromiseInterface<string,Exception>` method can be used to 123resolve the given $domain name to a single IPv4 address (type `A` query). 124 125```php 126$resolver->resolve('reactphp.org')->then(function ($ip) { 127 echo 'IP for reactphp.org is ' . $ip . PHP_EOL; 128}); 129``` 130 131This is one of the main methods in this package. It sends a DNS query 132for the given $domain name to your DNS server and returns a single IP 133address on success. 134 135If the DNS server sends a DNS response message that contains more than 136one IP address for this query, it will randomly pick one of the IP 137addresses from the response. If you want the full list of IP addresses 138or want to send a different type of query, you should use the 139[`resolveAll()`](#resolveall) method instead. 140 141If the DNS server sends a DNS response message that indicates an error 142code, this method will reject with a `RecordNotFoundException`. Its 143message and code can be used to check for the response code. 144 145If the DNS communication fails and the server does not respond with a 146valid response message, this message will reject with an `Exception`. 147 148Pending DNS queries can be cancelled by cancelling its pending promise like so: 149 150```php 151$promise = $resolver->resolve('reactphp.org'); 152 153$promise->cancel(); 154``` 155 156### resolveAll() 157 158The `resolveAll(string $host, int $type): PromiseInterface<array,Exception>` method can be used to 159resolve all record values for the given $domain name and query $type. 160 161```php 162$resolver->resolveAll('reactphp.org', Message::TYPE_A)->then(function ($ips) { 163 echo 'IPv4 addresses for reactphp.org ' . implode(', ', $ips) . PHP_EOL; 164}); 165 166$resolver->resolveAll('reactphp.org', Message::TYPE_AAAA)->then(function ($ips) { 167 echo 'IPv6 addresses for reactphp.org ' . implode(', ', $ips) . PHP_EOL; 168}); 169``` 170 171This is one of the main methods in this package. It sends a DNS query 172for the given $domain name to your DNS server and returns a list with all 173record values on success. 174 175If the DNS server sends a DNS response message that contains one or more 176records for this query, it will return a list with all record values 177from the response. You can use the `Message::TYPE_*` constants to control 178which type of query will be sent. Note that this method always returns a 179list of record values, but each record value type depends on the query 180type. For example, it returns the IPv4 addresses for type `A` queries, 181the IPv6 addresses for type `AAAA` queries, the hostname for type `NS`, 182`CNAME` and `PTR` queries and structured data for other queries. See also 183the `Record` documentation for more details. 184 185If the DNS server sends a DNS response message that indicates an error 186code, this method will reject with a `RecordNotFoundException`. Its 187message and code can be used to check for the response code. 188 189If the DNS communication fails and the server does not respond with a 190valid response message, this message will reject with an `Exception`. 191 192Pending DNS queries can be cancelled by cancelling its pending promise like so: 193 194```php 195$promise = $resolver->resolveAll('reactphp.org', Message::TYPE_AAAA); 196 197$promise->cancel(); 198``` 199 200## Advanced Usage 201 202### UdpTransportExecutor 203 204The `UdpTransportExecutor` can be used to 205send DNS queries over a UDP transport. 206 207This is the main class that sends a DNS query to your DNS server and is used 208internally by the `Resolver` for the actual message transport. 209 210For more advanced usages one can utilize this class directly. 211The following example looks up the `IPv6` address for `igor.io`. 212 213```php 214$loop = Factory::create(); 215$executor = new UdpTransportExecutor('8.8.8.8:53', $loop); 216 217$executor->query( 218 new Query($name, Message::TYPE_AAAA, Message::CLASS_IN) 219)->then(function (Message $message) { 220 foreach ($message->answers as $answer) { 221 echo 'IPv6: ' . $answer->data . PHP_EOL; 222 } 223}, 'printf'); 224 225$loop->run(); 226``` 227 228See also the [fourth example](examples). 229 230Note that this executor does not implement a timeout, so you will very likely 231want to use this in combination with a `TimeoutExecutor` like this: 232 233```php 234$executor = new TimeoutExecutor( 235 new UdpTransportExecutor($nameserver, $loop), 236 3.0, 237 $loop 238); 239``` 240 241Also note that this executor uses an unreliable UDP transport and that it 242does not implement any retry logic, so you will likely want to use this in 243combination with a `RetryExecutor` like this: 244 245```php 246$executor = new RetryExecutor( 247 new TimeoutExecutor( 248 new UdpTransportExecutor($nameserver, $loop), 249 3.0, 250 $loop 251 ) 252); 253``` 254 255Note that this executor is entirely async and as such allows you to execute 256any number of queries concurrently. You should probably limit the number of 257concurrent queries in your application or you're very likely going to face 258rate limitations and bans on the resolver end. For many common applications, 259you may want to avoid sending the same query multiple times when the first 260one is still pending, so you will likely want to use this in combination with 261a `CoopExecutor` like this: 262 263```php 264$executor = new CoopExecutor( 265 new RetryExecutor( 266 new TimeoutExecutor( 267 new UdpTransportExecutor($nameserver, $loop), 268 3.0, 269 $loop 270 ) 271 ) 272); 273``` 274 275> Internally, this class uses PHP's UDP sockets and does not take advantage 276 of [react/datagram](https://github.com/reactphp/datagram) purely for 277 organizational reasons to avoid a cyclic dependency between the two 278 packages. Higher-level components should take advantage of the Datagram 279 component instead of reimplementing this socket logic from scratch. 280 281### TcpTransportExecutor 282 283The `TcpTransportExecutor` class can be used to 284send DNS queries over a TCP/IP stream transport. 285 286This is one of the main classes that send a DNS query to your DNS server. 287 288For more advanced usages one can utilize this class directly. 289The following example looks up the `IPv6` address for `reactphp.org`. 290 291```php 292$loop = Factory::create(); 293$executor = new TcpTransportExecutor('8.8.8.8:53', $loop); 294 295$executor->query( 296 new Query($name, Message::TYPE_AAAA, Message::CLASS_IN) 297)->then(function (Message $message) { 298 foreach ($message->answers as $answer) { 299 echo 'IPv6: ' . $answer->data . PHP_EOL; 300 } 301}, 'printf'); 302 303$loop->run(); 304``` 305 306See also [example #92](examples). 307 308Note that this executor does not implement a timeout, so you will very likely 309want to use this in combination with a `TimeoutExecutor` like this: 310 311```php 312$executor = new TimeoutExecutor( 313 new TcpTransportExecutor($nameserver, $loop), 314 3.0, 315 $loop 316); 317``` 318 319Unlike the `UdpTransportExecutor`, this class uses a reliable TCP/IP 320transport, so you do not necessarily have to implement any retry logic. 321 322Note that this executor is entirely async and as such allows you to execute 323queries concurrently. The first query will establish a TCP/IP socket 324connection to the DNS server which will be kept open for a short period. 325Additional queries will automatically reuse this existing socket connection 326to the DNS server, will pipeline multiple requests over this single 327connection and will keep an idle connection open for a short period. The 328initial TCP/IP connection overhead may incur a slight delay if you only send 329occasional queries – when sending a larger number of concurrent queries over 330an existing connection, it becomes increasingly more efficient and avoids 331creating many concurrent sockets like the UDP-based executor. You may still 332want to limit the number of (concurrent) queries in your application or you 333may be facing rate limitations and bans on the resolver end. For many common 334applications, you may want to avoid sending the same query multiple times 335when the first one is still pending, so you will likely want to use this in 336combination with a `CoopExecutor` like this: 337 338```php 339$executor = new CoopExecutor( 340 new TimeoutExecutor( 341 new TcpTransportExecutor($nameserver, $loop), 342 3.0, 343 $loop 344 ) 345); 346``` 347 348> Internally, this class uses PHP's TCP/IP sockets and does not take advantage 349 of [react/socket](https://github.com/reactphp/socket) purely for 350 organizational reasons to avoid a cyclic dependency between the two 351 packages. Higher-level components should take advantage of the Socket 352 component instead of reimplementing this socket logic from scratch. 353 354### SelectiveTransportExecutor 355 356The `SelectiveTransportExecutor` class can be used to 357Send DNS queries over a UDP or TCP/IP stream transport. 358 359This class will automatically choose the correct transport protocol to send 360a DNS query to your DNS server. It will always try to send it over the more 361efficient UDP transport first. If this query yields a size related issue 362(truncated messages), it will retry over a streaming TCP/IP transport. 363 364For more advanced usages one can utilize this class directly. 365The following example looks up the `IPv6` address for `reactphp.org`. 366 367```php 368$executor = new SelectiveTransportExecutor($udpExecutor, $tcpExecutor); 369 370$executor->query( 371 new Query($name, Message::TYPE_AAAA, Message::CLASS_IN) 372)->then(function (Message $message) { 373 foreach ($message->answers as $answer) { 374 echo 'IPv6: ' . $answer->data . PHP_EOL; 375 } 376}, 'printf'); 377``` 378 379Note that this executor only implements the logic to select the correct 380transport for the given DNS query. Implementing the correct transport logic, 381implementing timeouts and any retry logic is left up to the given executors, 382see also [`UdpTransportExecutor`](#udptransportexecutor) and 383[`TcpTransportExecutor`](#tcptransportexecutor) for more details. 384 385Note that this executor is entirely async and as such allows you to execute 386any number of queries concurrently. You should probably limit the number of 387concurrent queries in your application or you're very likely going to face 388rate limitations and bans on the resolver end. For many common applications, 389you may want to avoid sending the same query multiple times when the first 390one is still pending, so you will likely want to use this in combination with 391a `CoopExecutor` like this: 392 393```php 394$executor = new CoopExecutor( 395 new SelectiveTransportExecutor( 396 $datagramExecutor, 397 $streamExecutor 398 ) 399); 400``` 401 402### HostsFileExecutor 403 404Note that the above `UdpTransportExecutor` class always performs an actual DNS query. 405If you also want to take entries from your hosts file into account, you may 406use this code: 407 408```php 409$hosts = \React\Dns\Config\HostsFile::loadFromPathBlocking(); 410 411$executor = new UdpTransportExecutor('8.8.8.8:53', $loop); 412$executor = new HostsFileExecutor($hosts, $executor); 413 414$executor->query( 415 new Query('localhost', Message::TYPE_A, Message::CLASS_IN) 416); 417``` 418 419## Install 420 421The recommended way to install this library is [through Composer](https://getcomposer.org/). 422[New to Composer?](https://getcomposer.org/doc/00-intro.md) 423 424This project follows [SemVer](https://semver.org/). 425This will install the latest supported version: 426 427```bash 428$ composer require react/dns:^1.5 429``` 430 431See also the [CHANGELOG](CHANGELOG.md) for details about version upgrades. 432 433This project aims to run on any platform and thus does not require any PHP 434extensions and supports running on legacy PHP 5.3 through current PHP 8+ and 435HHVM. 436It's *highly recommended to use PHP 7+* for this project. 437 438## Tests 439 440To run the test suite, you first need to clone this repo and then install all 441dependencies [through Composer](https://getcomposer.org/): 442 443```bash 444$ composer install 445``` 446 447To run the test suite, go to the project root and run: 448 449```bash 450$ php vendor/bin/phpunit 451``` 452 453The test suite also contains a number of functional integration tests that rely 454on a stable internet connection. 455If you do not want to run these, they can simply be skipped like this: 456 457```bash 458$ php vendor/bin/phpunit --exclude-group internet 459``` 460 461## License 462 463MIT, see [LICENSE file](LICENSE). 464 465## References 466 467* [RFC 1034](https://tools.ietf.org/html/rfc1034) Domain Names - Concepts and Facilities 468* [RFC 1035](https://tools.ietf.org/html/rfc1035) Domain Names - Implementation and Specification 469