Parallel/Asynchronous DNS resolving in PHP

In PHP one key for a scalable and performant web application is parallelism, whenever and wherever possible, even if you use queues. The most popular usage of parallelism in PHP is probably curl_multi_*.
In this post I will show you how to do multiple DNS requests lightning fast with two different approaches / PHP extensions.

In both cases the DNS requests are done asynchronously, meaning even with multiple requests the whole process will only take as long as the longest request takes (in theory).

PHP’s internal DNS functions rely on resolv.conf and in most cases it is not heavily optimized, defaulting to a rather long timeout of 5 seconds.
So even if you are only needing single DNS lookups both extensions might still be interesting as you can dynamically change the behavior of that, which what PHP is all about, or?

pecl-ares

pecl-ares offers PHP bindings for the c-ares library (affiliated with cURL).
I was happy that Michael Wallner (you might know him for pecl-http) offered help to revive the code, as it has not had a release since 4 years. So to get it running with the current c-ares version and a modern system, you should have a look at its git.
pecl-ares also allows the usage of callbacks which might be useful for certain scenarios.

Installation (assuming php-fpm)
$ sudo apt-get install libc-ares-dev php5-dev
$ git clone https://git.php.net/repository/pecl/networking/ares.git php-ares
$ cd php-ares
$ phpize
$ ./configure
$ make
$ sudo make install
$ sudo echo "extension=ares.so" > /etc/php5/mods-available/ares.ini
$ sudo php5enmod ares
Usage

It does not offer yet any documentation, but the source code is easy to understand, so anyways here is an example:

<?php

$ares = ares_init([
    'timeoutms' => 2000,
    'tries'     => 1,
    //'udp_port'  => 53,
    //'tcp_port'  => 53,
    'servers'   => ['8.8.8.8'],
    'flags'     => ARES_FLAG_NOALIASES|ARES_FLAG_NOSEARCH,
]);

$q = [];
$q[] = ares_query($ares, null, 'www.lifeofguenter.de', ARES_T_A);
$q[] = ares_query($ares, null, 'lifeofguenter.de', ARES_T_A);

do {
    $n = ares_fds($ares, $r, $w);
    ares_select($r, $w, 100);
    ares_process($ares, $r, $w);
} while ($n);

foreach ($q as $query) {
    var_dump(ares_result($query, $errno, $errstr));
}

ares_destroy($ares);
unset($ares);

php-rdns

php-rdns offers OOP PHP bindings for librdns (same person behind rspamd) and uses libev for event looping. “We” recently developed it for a client of ours and released it as open source. It is highly simplified and some things might not yet be implemented or working correctly, but if you are interested we are always happy to see a pull request. Initial development was done by Alexander Solovets and later bug-fixing by Eduardo Silva (lead dev/founder of monkey webserver).

Installation (assuming php-fpm)
$ sudo apt-get install libev-dev php5-dev
$ wget https://github.com/weheartwebsites/php-rdns/releases/download/v0.1.1/rdns-0.1.1.tgz
$ tar xvfz rdns-0.1.1.tgz
$ cd rdns-0.1.1/
$ phpize
$ ./configure
$ make
$ sudo make install
$ sudo echo "extension=rdns.so" >> /etc/php5/mods-available/rdns.ini
$ sudo php5enmod rdns
$ /etc/init.d/php-fpm restart
Usage

(full documentation on GitHub)

<?php

$rdns = new RDNS;
$rdns->addServer('8.8.8.8');

$rdns->addRequest('www.lifeofguenter.de', RDNS_A, 2);
$rdns->addRequest('lifeofguenter.de', RDNS_A, 2);
$replies = $rdns->getReplies();
ksort($replies);

var_dump($replies);
unset($rdns);

You might also be interested in ReactPHP or swoole, which are event-driven solutions to this problem.


Leave a Reply

Your email address will not be published. Required fields are marked *