Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 49 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ easily be used to create a DNS server.
* [Custom cache adapter](#custom-cache-adapter)
* [Resolver](#resolver)
* [resolve()](#resolve)
* [resolveAll()](#resolveall)
* [Advanced usage](#advanced-usage)
* [UdpTransportExecutor](#udptransportexecutor)
* [HostsFileExecutor](#hostsfileexecutor)
Expand Down Expand Up @@ -129,9 +130,11 @@ address on success.

If the DNS server sends a DNS response message that contains more than
one IP address for this query, it will randomly pick one of the IP
addresses from the response.
addresses from the response. If you want the full list of IP addresses
or want to send a different type of query, you should use the
[`resolveAll()`](#resolveall) method instead.

If the DNS server sends a DNS response message that indicated an error
If the DNS server sends a DNS response message that indicates an error
code, this method will reject with a `RecordNotFoundException`. Its
message and code can be used to check for the response code.

Expand All @@ -146,6 +149,50 @@ $promise = $resolver->resolve('reactphp.org');
$promise->cancel();
```

### resolveAll()

The `resolveAll(string $host, int $type): PromiseInterface<array,Exception>` method can be used to
resolve all record values for the given $domain name and query $type.

```php
$resolver->resolveAll('reactphp.org', Message::TYPE_A)->then(function ($ips) {
echo 'IPv4 addresses for reactphp.org ' . implode(', ', $ips) . PHP_EOL;
});

$resolver->resolveAll('reactphp.org', Message::TYPE_AAAA)->then(function ($ips) {
echo 'IPv6 addresses for reactphp.org ' . implode(', ', $ips) . PHP_EOL;
});
```

This is one of the main methods in this package. It sends a DNS query
for the given $domain name to your DNS server and returns a list with all
record values on success.

If the DNS server sends a DNS response message that contains one or more
records for this query, it will return a list with all record values
from the response. You can use the `Message::TYPE_*` constants to control
which type of query will be sent. Note that this method always returns a
list of record values, but each record value type depends on the query
type. For example, it returns the IPv4 addresses for type `A` queries,
the IPv6 addresses for type `AAAA` queries, the hostname for type `NS`,
`CNAME` and `PTR` queries and structured data for other queries. See also
the `Record` documentation for more details.

If the DNS server sends a DNS response message that indicates an error
code, this method will reject with a `RecordNotFoundException`. Its
message and code can be used to check for the response code.

If the DNS communication fails and the server does not respond with a
valid response message, this message will reject with an `Exception`.

Pending DNS queries can be cancelled by cancelling its pending promise like so:

```php
$promise = $resolver->resolveAll('reactphp.org', Message::TYPE_AAAA);

$promise->cancel();
```

## Advanced Usage

### UdpTransportExecutor
Expand Down
31 changes: 31 additions & 0 deletions examples/11-all-ips.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<?php

use React\Dns\Config\Config;
use React\Dns\Resolver\Factory;
use React\Dns\Model\Message;

require __DIR__ . '/../vendor/autoload.php';

$loop = React\EventLoop\Factory::create();

$config = Config::loadSystemConfigBlocking();
$server = $config->nameservers ? reset($config->nameservers) : '8.8.8.8';

$factory = new Factory();
$resolver = $factory->create($server, $loop);

$name = isset($argv[1]) ? $argv[1] : 'www.google.com';

$resolver->resolveAll($name, Message::TYPE_A)->then(function (array $ips) use ($name) {
echo 'IPv4 addresses for ' . $name . ': ' . implode(', ', $ips) . PHP_EOL;
}, function (Exception $e) use ($name) {
echo 'No IPv4 addresses for ' . $name . ': ' . $e->getMessage() . PHP_EOL;
});

$resolver->resolveAll($name, Message::TYPE_AAAA)->then(function (array $ips) use ($name) {
echo 'IPv6 addresses for ' . $name . ': ' . implode(', ', $ips) . PHP_EOL;
}, function (Exception $e) use ($name) {
echo 'No IPv6 addresses for ' . $name . ': ' . $e->getMessage() . PHP_EOL;
});

$loop->run();
25 changes: 25 additions & 0 deletions examples/12-all-types.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<?php

use React\Dns\Config\Config;
use React\Dns\Resolver\Factory;

require __DIR__ . '/../vendor/autoload.php';

$loop = React\EventLoop\Factory::create();

$config = Config::loadSystemConfigBlocking();
$server = $config->nameservers ? reset($config->nameservers) : '8.8.8.8';

$factory = new Factory();
$resolver = $factory->create($server, $loop);

$name = isset($argv[1]) ? $argv[1] : 'google.com';
$type = constant('React\Dns\Model\Message::TYPE_' . (isset($argv[2]) ? $argv[2] : 'TXT'));

$resolver->resolveAll($name, $type)->then(function (array $values) {
var_dump($values);
}, function (Exception $e) {
echo $e->getMessage() . PHP_EOL;
});

$loop->run();
File renamed without changes.
File renamed without changes.
136 changes: 110 additions & 26 deletions src/Resolver/Resolver.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@

namespace React\Dns\Resolver;

use React\Dns\Model\Message;
use React\Dns\Query\ExecutorInterface;
use React\Dns\Query\Query;
use React\Dns\RecordNotFoundException;
use React\Dns\Model\Message;
use React\Promise\PromiseInterface;

class Resolver
Expand Down Expand Up @@ -34,9 +34,11 @@ public function __construct($nameserver, ExecutorInterface $executor)
*
* If the DNS server sends a DNS response message that contains more than
* one IP address for this query, it will randomly pick one of the IP
* addresses from the response.
* addresses from the response. If you want the full list of IP addresses
* or want to send a different type of query, you should use the
* [`resolveAll()`](#resolveall) method instead.
*
* If the DNS server sends a DNS response message that indicated an error
* If the DNS server sends a DNS response message that indicates an error
* code, this method will reject with a `RecordNotFoundException`. Its
* message and code can be used to check for the response code.
*
Expand All @@ -57,17 +59,90 @@ public function __construct($nameserver, ExecutorInterface $executor)
*/
public function resolve($domain)
{
$query = new Query($domain, Message::TYPE_A, Message::CLASS_IN);
return $this->resolveAll($domain, Message::TYPE_A)->then(function (array $ips) {
return $ips[array_rand($ips)];
});
}

/**
* Resolves all record values for the given $domain name and query $type.
*
* ```php
* $resolver->resolveAll('reactphp.org', Message::TYPE_A)->then(function ($ips) {
* echo 'IPv4 addresses for reactphp.org ' . implode(', ', $ips) . PHP_EOL;
* });
*
* $resolver->resolveAll('reactphp.org', Message::TYPE_AAAA)->then(function ($ips) {
* echo 'IPv6 addresses for reactphp.org ' . implode(', ', $ips) . PHP_EOL;
* });
* ```
*
* This is one of the main methods in this package. It sends a DNS query
* for the given $domain name to your DNS server and returns a list with all
* record values on success.
*
* If the DNS server sends a DNS response message that contains one or more
* records for this query, it will return a list with all record values
* from the response. You can use the `Message::TYPE_*` constants to control
* which type of query will be sent. Note that this method always returns a
* list of record values, but each record value type depends on the query
* type. For example, it returns the IPv4 addresses for type `A` queries,
* the IPv6 addresses for type `AAAA` queries, the hostname for type `NS`,
* `CNAME` and `PTR` queries and structured data for other queries. See also
* the `Record` documentation for more details.
*
* If the DNS server sends a DNS response message that indicates an error
* code, this method will reject with a `RecordNotFoundException`. Its
* message and code can be used to check for the response code.
*
* If the DNS communication fails and the server does not respond with a
* valid response message, this message will reject with an `Exception`.
*
* Pending DNS queries can be cancelled by cancelling its pending promise like so:
*
* ```php
* $promise = $resolver->resolveAll('reactphp.org', Message::TYPE_AAAA);
*
* $promise->cancel();
* ```
*
* @param string $domain
* @return PromiseInterface Returns a promise which resolves with all record values on success or
* rejects with an Exception on error.
*/
public function resolveAll($domain, $type)
{
$query = new Query($domain, $type, Message::CLASS_IN);
$that = $this;

return $this->executor
->query($this->nameserver, $query)
->then(function (Message $response) use ($query, $that) {
return $that->extractAddress($query, $response);
});
return $this->executor->query(
$this->nameserver,
$query
)->then(function (Message $response) use ($query, $that) {
return $that->extractValues($query, $response);
});
}

/**
* @deprecated unused, exists for BC only
*/
public function extractAddress(Query $query, Message $response)
{
$addresses = $this->extractValues($query, $response);

return $addresses[array_rand($addresses)];
}

/**
* [Internal] extract all resource record values from response for this query
*
* @param Query $query
* @param Message $response
* @return array
* @throws RecordNotFoundException when response indicates an error or contains no data
* @internal
*/
public function extractValues(Query $query, Message $response)
{
// reject if response code indicates this is an error response message
$code = $response->getResponseCode();
Expand Down Expand Up @@ -98,7 +173,7 @@ public function extractAddress(Query $query, Message $response)
}

$answers = $response->answers;
$addresses = $this->resolveAliases($answers, $query->name);
$addresses = $this->valuesByNameAndType($answers, $query->name, $query->type);

// reject if we did not receive a valid answer (domain is valid, but no record for this type could be found)
if (0 === count($addresses)) {
Expand All @@ -107,36 +182,45 @@ public function extractAddress(Query $query, Message $response)
);
}

$address = $addresses[array_rand($addresses)];
return $address;
return array_values($addresses);
}

/**
* @deprecated unused, exists for BC only
*/
public function resolveAliases(array $answers, $name)
{
$named = $this->filterByName($answers, $name);
$aRecords = $this->filterByType($named, Message::TYPE_A);
$cnameRecords = $this->filterByType($named, Message::TYPE_CNAME);
return $this->valuesByNameAndType($answers, $name, Message::TYPE_A);
}

if ($aRecords) {
return $this->mapRecordData($aRecords);
/**
* @param \React\Dns\Model\Record[] $answers
* @param string $name
* @param int $type
* @return array
*/
private function valuesByNameAndType(array $answers, $name, $type)
{
// return all record values for this name and type (if any)
$named = $this->filterByName($answers, $name);
$records = $this->filterByType($named, $type);
if ($records) {
return $this->mapRecordData($records);
}

// no matching records found? check if there are any matching CNAMEs instead
$cnameRecords = $this->filterByType($named, Message::TYPE_CNAME);
if ($cnameRecords) {
$aRecords = array();

$cnames = $this->mapRecordData($cnameRecords);
foreach ($cnames as $cname) {
$targets = $this->filterByName($answers, $cname);
$aRecords = array_merge(
$aRecords,
$this->resolveAliases($answers, $cname)
$records = array_merge(
$records,
$this->valuesByNameAndType($answers, $cname, $type)
);
}

return $aRecords;
}

return array();
return $records;
}

private function filterByName(array $answers, $name)
Expand Down
8 changes: 8 additions & 0 deletions tests/FunctionalResolverTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,14 @@ public function testResolveLocalhostResolves()
$this->loop->run();
}

public function testResolveAllLocalhostResolvesWithArray()
{
$promise = $this->resolver->resolveAll('localhost', Message::TYPE_A);
$promise->then($this->expectCallableOnceWith($this->isType('array')), $this->expectCallableNever());

$this->loop->run();
}

/**
* @group internet
*/
Expand Down
1 change: 1 addition & 0 deletions tests/Resolver/ResolveAliasesTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ class ResolveAliasesTest extends TestCase
{
/**
* @covers React\Dns\Resolver\Resolver::resolveAliases
* @covers React\Dns\Resolver\Resolver::valuesByNameAndType
* @dataProvider provideAliasedAnswers
*/
public function testResolveAliases(array $expectedAnswers, array $answers, $name)
Expand Down
Loading