Skip to content

Commit c85119c

Browse files
authored
Merge pull request #110 from clue-labs/resolveall
Add resolveAll() method to support custom query types in Resolver
2 parents 28756a3 + f67ad97 commit c85119c

File tree

9 files changed

+307
-28
lines changed

9 files changed

+307
-28
lines changed

README.md

Lines changed: 49 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ easily be used to create a DNS server.
1515
* [Custom cache adapter](#custom-cache-adapter)
1616
* [Resolver](#resolver)
1717
* [resolve()](#resolve)
18+
* [resolveAll()](#resolveall)
1819
* [Advanced usage](#advanced-usage)
1920
* [UdpTransportExecutor](#udptransportexecutor)
2021
* [HostsFileExecutor](#hostsfileexecutor)
@@ -129,9 +130,11 @@ address on success.
129130

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

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

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

152+
### resolveAll()
153+
154+
The `resolveAll(string $host, int $type): PromiseInterface<array,Exception>` method can be used to
155+
resolve all record values for the given $domain name and query $type.
156+
157+
```php
158+
$resolver->resolveAll('reactphp.org', Message::TYPE_A)->then(function ($ips) {
159+
echo 'IPv4 addresses for reactphp.org ' . implode(', ', $ips) . PHP_EOL;
160+
});
161+
162+
$resolver->resolveAll('reactphp.org', Message::TYPE_AAAA)->then(function ($ips) {
163+
echo 'IPv6 addresses for reactphp.org ' . implode(', ', $ips) . PHP_EOL;
164+
});
165+
```
166+
167+
This is one of the main methods in this package. It sends a DNS query
168+
for the given $domain name to your DNS server and returns a list with all
169+
record values on success.
170+
171+
If the DNS server sends a DNS response message that contains one or more
172+
records for this query, it will return a list with all record values
173+
from the response. You can use the `Message::TYPE_*` constants to control
174+
which type of query will be sent. Note that this method always returns a
175+
list of record values, but each record value type depends on the query
176+
type. For example, it returns the IPv4 addresses for type `A` queries,
177+
the IPv6 addresses for type `AAAA` queries, the hostname for type `NS`,
178+
`CNAME` and `PTR` queries and structured data for other queries. See also
179+
the `Record` documentation for more details.
180+
181+
If the DNS server sends a DNS response message that indicates an error
182+
code, this method will reject with a `RecordNotFoundException`. Its
183+
message and code can be used to check for the response code.
184+
185+
If the DNS communication fails and the server does not respond with a
186+
valid response message, this message will reject with an `Exception`.
187+
188+
Pending DNS queries can be cancelled by cancelling its pending promise like so:
189+
190+
```php
191+
$promise = $resolver->resolveAll('reactphp.org', Message::TYPE_AAAA);
192+
193+
$promise->cancel();
194+
```
195+
149196
## Advanced Usage
150197

151198
### UdpTransportExecutor

examples/11-all-ips.php

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
<?php
2+
3+
use React\Dns\Config\Config;
4+
use React\Dns\Resolver\Factory;
5+
use React\Dns\Model\Message;
6+
7+
require __DIR__ . '/../vendor/autoload.php';
8+
9+
$loop = React\EventLoop\Factory::create();
10+
11+
$config = Config::loadSystemConfigBlocking();
12+
$server = $config->nameservers ? reset($config->nameservers) : '8.8.8.8';
13+
14+
$factory = new Factory();
15+
$resolver = $factory->create($server, $loop);
16+
17+
$name = isset($argv[1]) ? $argv[1] : 'www.google.com';
18+
19+
$resolver->resolveAll($name, Message::TYPE_A)->then(function (array $ips) use ($name) {
20+
echo 'IPv4 addresses for ' . $name . ': ' . implode(', ', $ips) . PHP_EOL;
21+
}, function (Exception $e) use ($name) {
22+
echo 'No IPv4 addresses for ' . $name . ': ' . $e->getMessage() . PHP_EOL;
23+
});
24+
25+
$resolver->resolveAll($name, Message::TYPE_AAAA)->then(function (array $ips) use ($name) {
26+
echo 'IPv6 addresses for ' . $name . ': ' . implode(', ', $ips) . PHP_EOL;
27+
}, function (Exception $e) use ($name) {
28+
echo 'No IPv6 addresses for ' . $name . ': ' . $e->getMessage() . PHP_EOL;
29+
});
30+
31+
$loop->run();

examples/12-all-types.php

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
<?php
2+
3+
use React\Dns\Config\Config;
4+
use React\Dns\Resolver\Factory;
5+
6+
require __DIR__ . '/../vendor/autoload.php';
7+
8+
$loop = React\EventLoop\Factory::create();
9+
10+
$config = Config::loadSystemConfigBlocking();
11+
$server = $config->nameservers ? reset($config->nameservers) : '8.8.8.8';
12+
13+
$factory = new Factory();
14+
$resolver = $factory->create($server, $loop);
15+
16+
$name = isset($argv[1]) ? $argv[1] : 'google.com';
17+
$type = constant('React\Dns\Model\Message::TYPE_' . (isset($argv[2]) ? $argv[2] : 'TXT'));
18+
19+
$resolver->resolveAll($name, $type)->then(function (array $values) {
20+
var_dump($values);
21+
}, function (Exception $e) {
22+
echo $e->getMessage() . PHP_EOL;
23+
});
24+
25+
$loop->run();

src/Resolver/Resolver.php

Lines changed: 110 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,10 @@
22

33
namespace React\Dns\Resolver;
44

5+
use React\Dns\Model\Message;
56
use React\Dns\Query\ExecutorInterface;
67
use React\Dns\Query\Query;
78
use React\Dns\RecordNotFoundException;
8-
use React\Dns\Model\Message;
99
use React\Promise\PromiseInterface;
1010

1111
class Resolver
@@ -34,9 +34,11 @@ public function __construct($nameserver, ExecutorInterface $executor)
3434
*
3535
* If the DNS server sends a DNS response message that contains more than
3636
* one IP address for this query, it will randomly pick one of the IP
37-
* addresses from the response.
37+
* addresses from the response. If you want the full list of IP addresses
38+
* or want to send a different type of query, you should use the
39+
* [`resolveAll()`](#resolveall) method instead.
3840
*
39-
* If the DNS server sends a DNS response message that indicated an error
41+
* If the DNS server sends a DNS response message that indicates an error
4042
* code, this method will reject with a `RecordNotFoundException`. Its
4143
* message and code can be used to check for the response code.
4244
*
@@ -57,17 +59,90 @@ public function __construct($nameserver, ExecutorInterface $executor)
5759
*/
5860
public function resolve($domain)
5961
{
60-
$query = new Query($domain, Message::TYPE_A, Message::CLASS_IN);
62+
return $this->resolveAll($domain, Message::TYPE_A)->then(function (array $ips) {
63+
return $ips[array_rand($ips)];
64+
});
65+
}
66+
67+
/**
68+
* Resolves all record values for the given $domain name and query $type.
69+
*
70+
* ```php
71+
* $resolver->resolveAll('reactphp.org', Message::TYPE_A)->then(function ($ips) {
72+
* echo 'IPv4 addresses for reactphp.org ' . implode(', ', $ips) . PHP_EOL;
73+
* });
74+
*
75+
* $resolver->resolveAll('reactphp.org', Message::TYPE_AAAA)->then(function ($ips) {
76+
* echo 'IPv6 addresses for reactphp.org ' . implode(', ', $ips) . PHP_EOL;
77+
* });
78+
* ```
79+
*
80+
* This is one of the main methods in this package. It sends a DNS query
81+
* for the given $domain name to your DNS server and returns a list with all
82+
* record values on success.
83+
*
84+
* If the DNS server sends a DNS response message that contains one or more
85+
* records for this query, it will return a list with all record values
86+
* from the response. You can use the `Message::TYPE_*` constants to control
87+
* which type of query will be sent. Note that this method always returns a
88+
* list of record values, but each record value type depends on the query
89+
* type. For example, it returns the IPv4 addresses for type `A` queries,
90+
* the IPv6 addresses for type `AAAA` queries, the hostname for type `NS`,
91+
* `CNAME` and `PTR` queries and structured data for other queries. See also
92+
* the `Record` documentation for more details.
93+
*
94+
* If the DNS server sends a DNS response message that indicates an error
95+
* code, this method will reject with a `RecordNotFoundException`. Its
96+
* message and code can be used to check for the response code.
97+
*
98+
* If the DNS communication fails and the server does not respond with a
99+
* valid response message, this message will reject with an `Exception`.
100+
*
101+
* Pending DNS queries can be cancelled by cancelling its pending promise like so:
102+
*
103+
* ```php
104+
* $promise = $resolver->resolveAll('reactphp.org', Message::TYPE_AAAA);
105+
*
106+
* $promise->cancel();
107+
* ```
108+
*
109+
* @param string $domain
110+
* @return PromiseInterface Returns a promise which resolves with all record values on success or
111+
* rejects with an Exception on error.
112+
*/
113+
public function resolveAll($domain, $type)
114+
{
115+
$query = new Query($domain, $type, Message::CLASS_IN);
61116
$that = $this;
62117

63-
return $this->executor
64-
->query($this->nameserver, $query)
65-
->then(function (Message $response) use ($query, $that) {
66-
return $that->extractAddress($query, $response);
67-
});
118+
return $this->executor->query(
119+
$this->nameserver,
120+
$query
121+
)->then(function (Message $response) use ($query, $that) {
122+
return $that->extractValues($query, $response);
123+
});
68124
}
69125

126+
/**
127+
* @deprecated unused, exists for BC only
128+
*/
70129
public function extractAddress(Query $query, Message $response)
130+
{
131+
$addresses = $this->extractValues($query, $response);
132+
133+
return $addresses[array_rand($addresses)];
134+
}
135+
136+
/**
137+
* [Internal] extract all resource record values from response for this query
138+
*
139+
* @param Query $query
140+
* @param Message $response
141+
* @return array
142+
* @throws RecordNotFoundException when response indicates an error or contains no data
143+
* @internal
144+
*/
145+
public function extractValues(Query $query, Message $response)
71146
{
72147
// reject if response code indicates this is an error response message
73148
$code = $response->getResponseCode();
@@ -98,7 +173,7 @@ public function extractAddress(Query $query, Message $response)
98173
}
99174

100175
$answers = $response->answers;
101-
$addresses = $this->resolveAliases($answers, $query->name);
176+
$addresses = $this->valuesByNameAndType($answers, $query->name, $query->type);
102177

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

110-
$address = $addresses[array_rand($addresses)];
111-
return $address;
185+
return array_values($addresses);
112186
}
113187

188+
/**
189+
* @deprecated unused, exists for BC only
190+
*/
114191
public function resolveAliases(array $answers, $name)
115192
{
116-
$named = $this->filterByName($answers, $name);
117-
$aRecords = $this->filterByType($named, Message::TYPE_A);
118-
$cnameRecords = $this->filterByType($named, Message::TYPE_CNAME);
193+
return $this->valuesByNameAndType($answers, $name, Message::TYPE_A);
194+
}
119195

120-
if ($aRecords) {
121-
return $this->mapRecordData($aRecords);
196+
/**
197+
* @param \React\Dns\Model\Record[] $answers
198+
* @param string $name
199+
* @param int $type
200+
* @return array
201+
*/
202+
private function valuesByNameAndType(array $answers, $name, $type)
203+
{
204+
// return all record values for this name and type (if any)
205+
$named = $this->filterByName($answers, $name);
206+
$records = $this->filterByType($named, $type);
207+
if ($records) {
208+
return $this->mapRecordData($records);
122209
}
123210

211+
// no matching records found? check if there are any matching CNAMEs instead
212+
$cnameRecords = $this->filterByType($named, Message::TYPE_CNAME);
124213
if ($cnameRecords) {
125-
$aRecords = array();
126-
127214
$cnames = $this->mapRecordData($cnameRecords);
128215
foreach ($cnames as $cname) {
129-
$targets = $this->filterByName($answers, $cname);
130-
$aRecords = array_merge(
131-
$aRecords,
132-
$this->resolveAliases($answers, $cname)
216+
$records = array_merge(
217+
$records,
218+
$this->valuesByNameAndType($answers, $cname, $type)
133219
);
134220
}
135-
136-
return $aRecords;
137221
}
138222

139-
return array();
223+
return $records;
140224
}
141225

142226
private function filterByName(array $answers, $name)

tests/FunctionalResolverTest.php

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,14 @@ public function testResolveLocalhostResolves()
2525
$this->loop->run();
2626
}
2727

28+
public function testResolveAllLocalhostResolvesWithArray()
29+
{
30+
$promise = $this->resolver->resolveAll('localhost', Message::TYPE_A);
31+
$promise->then($this->expectCallableOnceWith($this->isType('array')), $this->expectCallableNever());
32+
33+
$this->loop->run();
34+
}
35+
2836
/**
2937
* @group internet
3038
*/

tests/Resolver/ResolveAliasesTest.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ class ResolveAliasesTest extends TestCase
1212
{
1313
/**
1414
* @covers React\Dns\Resolver\Resolver::resolveAliases
15+
* @covers React\Dns\Resolver\Resolver::valuesByNameAndType
1516
* @dataProvider provideAliasedAnswers
1617
*/
1718
public function testResolveAliases(array $expectedAnswers, array $answers, $name)

0 commit comments

Comments
 (0)