mirror of
https://github.com/danog/dns.git
synced 2024-11-26 20:14:51 +01:00
Massive refactor using amp/1.0.0
This commit is contained in:
parent
79be6e8c28
commit
a2fe7a5764
1
.coveralls.yml
Normal file
1
.coveralls.yml
Normal file
@ -0,0 +1 @@
|
||||
src_dir: lib
|
16
.editorconfig
Normal file
16
.editorconfig
Normal file
@ -0,0 +1,16 @@
|
||||
root = true
|
||||
|
||||
[*]
|
||||
end_of_line = lf
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
indent_style = spaces
|
||||
charset = utf-8
|
||||
|
||||
[{.travis.yml}]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
|
||||
[{composer.json}]
|
||||
indent_style = space
|
||||
indent_size = 2
|
9
.gitattributes
vendored
Normal file
9
.gitattributes
vendored
Normal file
@ -0,0 +1,9 @@
|
||||
/test export-ignore
|
||||
/examples export-ignore
|
||||
/.coveralls.yml export-ignore
|
||||
/.editorconfig export-ignore
|
||||
/.gitattributes export-ignore
|
||||
/.gitignore export-ignore
|
||||
/.php_cs export-ignore
|
||||
/.travis.yml export-ignore
|
||||
/phpunit.xml export-ignore
|
7
.gitignore
vendored
7
.gitignore
vendored
@ -1,3 +1,4 @@
|
||||
.idea
|
||||
vendor
|
||||
composer.lock
|
||||
/coverage/
|
||||
/composer.lock
|
||||
/vendor/
|
||||
/.idea/
|
||||
|
15
.php_cs
Normal file
15
.php_cs
Normal file
@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
return Symfony\CS\Config\Config::create()
|
||||
->level(Symfony\CS\FixerInterface::NONE_LEVEL)
|
||||
->fixers([
|
||||
"psr2",
|
||||
"-braces",
|
||||
"-psr0",
|
||||
])
|
||||
->finder(
|
||||
Symfony\CS\Finder\DefaultFinder::create()
|
||||
->in(__DIR__ . "/lib")
|
||||
->in(__DIR__ . "/test")
|
||||
)
|
||||
;
|
16
.travis.yml
16
.travis.yml
@ -1,13 +1,19 @@
|
||||
sudo: false
|
||||
|
||||
language: php
|
||||
|
||||
git:
|
||||
submodules: false
|
||||
|
||||
php:
|
||||
- 5.6
|
||||
- 5.5
|
||||
- 5.6
|
||||
- 7
|
||||
|
||||
before_script:
|
||||
- composer self-update
|
||||
- composer install
|
||||
|
||||
script: php ./vendor/bin/phpunit --configuration ./phpunit.xml --coverage-text
|
||||
script:
|
||||
- vendor/bin/phpunit --coverage-text --coverage-clover build/logs/clover.xml
|
||||
- php vendor/bin/php-cs-fixer --diff --dry-run -v fix
|
||||
|
||||
after_script:
|
||||
- php vendor/bin/coveralls -v
|
||||
|
30
CONTRIBUTING.md
Normal file
30
CONTRIBUTING.md
Normal file
@ -0,0 +1,30 @@
|
||||
## Submitting useful bug reports
|
||||
|
||||
Please search existing issues first to make sure this is not a duplicate.
|
||||
Every issue report has a cost for the developers required to field it; be
|
||||
respectful of others' time and ensure your report isn't spurious prior to
|
||||
submission. Please adhere to [sound bug reporting principles](http://www.chiark.greenend.org.uk/~sgtatham/bugs.html).
|
||||
|
||||
## Development ideology
|
||||
|
||||
Truths which we believe to be self-evident:
|
||||
|
||||
- **It's an asynchronous world.** Be wary of anything that undermines
|
||||
async principles.
|
||||
|
||||
- **The answer is not more options.** If you feel compelled to expose
|
||||
new preferences to the user it's very possible you've made a wrong
|
||||
turn somewhere.
|
||||
|
||||
- **There are no power users.** The idea that some users "understand"
|
||||
concepts better than others has proven to be, for the most part, false.
|
||||
If anything, "power users" are more dangerous than the rest, and we
|
||||
should avoid exposing dangerous functionality to them.
|
||||
|
||||
## Code style
|
||||
|
||||
The amphp project adheres to the PSR-2 style guide with the exception that
|
||||
opening braces for classes and methods must appear on the same line as
|
||||
the declaration:
|
||||
|
||||
https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-2-coding-style-guide.md
|
21
LICENSE
Normal file
21
LICENSE
Normal file
@ -0,0 +1,21 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2015 amphp
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
96
README.md
96
README.md
@ -1,93 +1,41 @@
|
||||
dns [![Build Status](https://travis-ci.org/amphp/dns.svg?branch=master)](https://travis-ci.org/amphp/dns)
|
||||
============
|
||||
# dns
|
||||
|
||||
Asynchronous DNS resolution built on the [Amp](https://github.com/amphp/amp) concurrency framework
|
||||
[![Build Status](https://img.shields.io/travis/amphp/dns/master.svg?style=flat-square)](https://travis-ci.org/amphp/dns)
|
||||
[![CoverageStatus](https://img.shields.io/coveralls/amphp/dns/master.svg?style=flat-square)](https://coveralls.io/github/amphp/dns?branch=master)
|
||||
![Unstable](https://img.shields.io/badge/api-unstable-orange.svg?style=flat-square)
|
||||
![License](https://img.shields.io/badge/license-MIT-blue.svg?style=flat-square)
|
||||
|
||||
|
||||
## Examples
|
||||
`amphp/dns` provides asynchronous DNS name resolution based on the [`amp`](https://github.com/amphp/amp)
|
||||
concurrency framework.
|
||||
|
||||
**Synchronous Resolution Via `Amp\wait()`**
|
||||
**Required PHP Version**
|
||||
|
||||
```php
|
||||
<?php
|
||||
require __DIR__ . '/vendor/autoload.php';
|
||||
- PHP 5.5+
|
||||
|
||||
$resolver = new Amp\Dns\Resolver;
|
||||
$name = 'google.com';
|
||||
list($address, $type) = Amp\wait($resolver->resolve($name));
|
||||
printf("%s resolved to %s\n", $name, $address);
|
||||
**Installation**
|
||||
|
||||
```bash
|
||||
$ composer require amphp/dns:dev-master
|
||||
```
|
||||
|
||||
**Concurrent Synchronous Resolution Via `wait()`**
|
||||
**Example**
|
||||
|
||||
```php
|
||||
<?php
|
||||
require __DIR__ . '/vendor/autoload.php';
|
||||
|
||||
$resolver = new Amp\Dns\Resolver;
|
||||
$names = [
|
||||
'github.com',
|
||||
'google.com',
|
||||
'stackoverflow.com',
|
||||
];
|
||||
$promises = [];
|
||||
foreach ($names as $name) {
|
||||
$promises[$name] = $resolver->resolve($name);
|
||||
}
|
||||
$comboPromise = Amp\all($promises);
|
||||
$results = Amp\wait($comboPromise);
|
||||
foreach ($results as $name => $resultArray) {
|
||||
list($addr, $type) = $resultArray;
|
||||
printf("%s => %s\n", $name, $addr);
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
**Event Loop Async**
|
||||
|
||||
```php
|
||||
<?php
|
||||
require __DIR__ . '/vendor/autoload.php';
|
||||
|
||||
Amp\run(function () {
|
||||
$resolver = new Amp\Dns\Resolver;
|
||||
$names = [
|
||||
'github.com',
|
||||
'google.com',
|
||||
'stackoverflow.com',
|
||||
'localhost',
|
||||
'192.168.0.1',
|
||||
'::1',
|
||||
];
|
||||
$githubIpv4 = (yield Amp\Dns\resolve("github.com"));
|
||||
var_dump($githubIpv4);
|
||||
|
||||
$promises = [];
|
||||
foreach ($names as $name) {
|
||||
$promise = $resolver->resolve($name);
|
||||
$promises[$name] = $promise;
|
||||
}
|
||||
$googleIpv4 = Amp\Dns\resolve("google.com");
|
||||
$googleIpv6 = Amp\Dns\resolve("google.com", $options = [
|
||||
"mode" => Amp\Dns\MODE_INET6
|
||||
]);
|
||||
|
||||
// Yield control until the combo promise resolves
|
||||
list($errors, $successes) = (yield 'some' => $promises);
|
||||
|
||||
foreach ($names as $name) {
|
||||
echo isset($errors[$name])
|
||||
? "FAILED: {$name}\n"
|
||||
: "{$name} => {$successes[$name][0]}\n";
|
||||
}
|
||||
|
||||
// Stop the event loop so we don't sit around forever
|
||||
Amp\stop();
|
||||
$firstGoogleResult = (yield Amp\first([$ipv4Result, $ipv6Result]));
|
||||
var_dump($firstGoogleResult);
|
||||
});
|
||||
```
|
||||
|
||||
## Tests
|
||||
|
||||
[![Build Status](https://travis-ci.org/amphp/dns.svg?branch=master)](https://travis-ci.org/amphp/dns)
|
||||
|
||||
Tests can be run from the command line using:
|
||||
|
||||
`php vendor/bin/phpunit -c phpunit.xml`
|
||||
|
||||
or to exclude tests that require a working internet connection:
|
||||
|
||||
`php vendor/bin/phpunit -c phpunit.xml --exclude-group internet`
|
||||
|
@ -1,36 +1,38 @@
|
||||
{
|
||||
"name": "amphp/dns",
|
||||
"homepage": "https://github.com/amphp/dns",
|
||||
"description": "Asynchronous DNS resolution built on the Amp concurrency framework",
|
||||
"description": "Async DNS resolution built on the amp concurrency framework",
|
||||
"keywords": ["dns", "resolve", "client", "async", "amp"],
|
||||
"license": "MIT",
|
||||
"authors": [
|
||||
{
|
||||
"name": "Chris Wright",
|
||||
"email": "addr@daverandom.com",
|
||||
"role": "Creator / Lead Developer"
|
||||
"email": "addr@daverandom.com"
|
||||
},
|
||||
{
|
||||
"name": "Daniel Lowrey",
|
||||
"email": "rdlowrey@php.net"
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"php": ">=5.5",
|
||||
"amphp/amp": "^1",
|
||||
"amphp/cache": "dev-master",
|
||||
"amphp/filesystem": "dev-master",
|
||||
"daverandom/libdns": "~1.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"mockery/mockery": ">=0.9.1",
|
||||
"phpunit/phpunit": "~4.1.0"
|
||||
"phpunit/phpunit": "~4.4.0",
|
||||
"fabpot/php-cs-fixer": "~1.9",
|
||||
"satooshi/php-coveralls": "dev-master"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Amp\\Dns\\": "lib"
|
||||
},
|
||||
"files": ["lib/functions.php"]
|
||||
},
|
||||
"autoload-dev": {
|
||||
"psr-4": {
|
||||
"Amp\\Dns\\Test\\": "test/"
|
||||
},
|
||||
"files": ["test/bootstrap.php"]
|
||||
"files": [
|
||||
"lib/constants.php",
|
||||
"lib/functions.php"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
@ -1,9 +0,0 @@
|
||||
<?php
|
||||
|
||||
require __DIR__ . '/../vendor/autoload.php';
|
||||
|
||||
$name = 'google.com';
|
||||
$resolver = new Amp\Dns\Resolver;
|
||||
$promise = $resolver->resolve($name);
|
||||
list($address, $type) = $promise->wait();
|
||||
printf("%s resolved to %s\n", $name, $address);
|
@ -1,34 +0,0 @@
|
||||
<?php
|
||||
|
||||
require __DIR__ . '/../vendor/autoload.php';
|
||||
|
||||
Amp\run(function() {
|
||||
$names = [
|
||||
'github.com',
|
||||
'google.com',
|
||||
'stackoverflow.com',
|
||||
'localhost',
|
||||
'192.168.0.1',
|
||||
'::1',
|
||||
];
|
||||
|
||||
$promises = [];
|
||||
$resolver = new Amp\Dns\Resolver;
|
||||
foreach ($names as $name) {
|
||||
$promise = $resolver->resolve($name);
|
||||
$promises[$name] = $promise;
|
||||
}
|
||||
|
||||
// Combine our multiple promises into a single promise
|
||||
$comboPromise = Amp\some($promises);
|
||||
|
||||
// Yield control until the combo promise resolves
|
||||
list($errors, $successes) = (yield $comboPromise);
|
||||
|
||||
foreach ($names as $name) {
|
||||
echo isset($errors[$name]) ? "FAILED: {$name}\n" : "{$name} => {$successes[$name][0]}\n";
|
||||
}
|
||||
|
||||
// Stop the event loop so we don't sit around forever
|
||||
Amp\stop();
|
||||
});
|
@ -1,12 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Amp\Dns;
|
||||
|
||||
class AddressModes {
|
||||
const INET4_ADDR = 0b0001;
|
||||
const INET6_ADDR = 0b0010;
|
||||
const PREFER_INET6 = 0b0100;
|
||||
const ANY_PREFER_INET4 = 0b0011;
|
||||
const ANY_PREFER_INET6 = 0b0111;
|
||||
const CNAME = 0b1000;
|
||||
}
|
382
lib/Client.php
382
lib/Client.php
@ -1,382 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Amp\Dns;
|
||||
|
||||
use Amp\Reactor;
|
||||
use Amp\Failure;
|
||||
use Amp\Deferred;
|
||||
|
||||
class Client {
|
||||
const OP_MS_REQUEST_TIMEOUT = 0b0001;
|
||||
const OP_SERVER_ADDRESS = 0b0010;
|
||||
const OP_SERVER_PORT = 0b0011;
|
||||
|
||||
/**
|
||||
* @var \Amp\Reactor
|
||||
*/
|
||||
private $reactor;
|
||||
|
||||
/**
|
||||
* @var \Amp\Dns\RequestBuilder
|
||||
*/
|
||||
private $requestBuilder;
|
||||
|
||||
/**
|
||||
* @var \Amp\Dns\ResponseInterpreter
|
||||
*/
|
||||
private $responseInterpreter;
|
||||
|
||||
/**
|
||||
* @var \Amp\Dns\Cache
|
||||
*/
|
||||
private $cache;
|
||||
|
||||
/**
|
||||
* @var resource
|
||||
*/
|
||||
private $socket;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
private $msRequestTimeout = 2000;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
private $readWatcherId;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private $pendingLookups = [];
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private $pendingRequestsByNameAndType = [];
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private $pendingRequestsById = [];
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
private $requestIdCounter = 0;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
private $lookupIdCounter = 0;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $serverAddress = '8.8.8.8';
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
private $serverPort = 53;
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
private $isReadWatcherEnabled = false;
|
||||
|
||||
/**
|
||||
* @param \Amp\Reactor $reactor
|
||||
* @param \Amp\Dns\RequestBuilder $requestBuilder
|
||||
* @param \Amp\Dns\ResponseInterpreter $responseInterpreter
|
||||
* @param \Amp\Dns\Cache $cache
|
||||
*/
|
||||
public function __construct(
|
||||
Reactor $reactor = null,
|
||||
RequestBuilder $requestBuilder = null,
|
||||
ResponseInterpreter $responseInterpreter = null,
|
||||
Cache $cache = null
|
||||
) {
|
||||
$this->reactor = $reactor ?: \Amp\reactor();
|
||||
$this->requestBuilder = $requestBuilder ?: new RequestBuilder;
|
||||
$this->responseInterpreter = $responseInterpreter ?: new ResponseInterpreter;
|
||||
$this->cache = $cache ?: (new CacheFactory)->select();
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve a name from a DNS server
|
||||
*
|
||||
* @param string $name
|
||||
* @param int $mode
|
||||
* @return \Amp\Promise
|
||||
*/
|
||||
public function resolve($name, $mode) {
|
||||
// Defer UDP server connect until needed to allow custom address/port option assignment
|
||||
// after object instantiation.
|
||||
if (empty($this->socket) && !$this->connect()) {
|
||||
return new Failure(new ResolutionException(
|
||||
sprintf(
|
||||
"Failed connecting to DNS server at %s:%d",
|
||||
$this->serverAddress,
|
||||
$this->serverPort
|
||||
)
|
||||
));
|
||||
}
|
||||
|
||||
if (!$this->isReadWatcherEnabled) {
|
||||
$this->isReadWatcherEnabled = true;
|
||||
$this->reactor->enable($this->readWatcherId);
|
||||
}
|
||||
|
||||
$promisor = new Deferred;
|
||||
$id = $this->getNextFreeLookupId();
|
||||
$this->pendingLookups[$id] = [
|
||||
'name' => $name,
|
||||
'requests' => $this->getRequestList($mode),
|
||||
'last_type' => null,
|
||||
'future' => $promisor,
|
||||
];
|
||||
|
||||
$this->processPendingLookup($id);
|
||||
|
||||
return $promisor->promise();
|
||||
}
|
||||
|
||||
private function connect() {
|
||||
$address = sprintf('udp://%s:%d', $this->serverAddress, $this->serverPort);
|
||||
if (!$this->socket = @stream_socket_client($address, $errNo, $errStr)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
stream_set_blocking($this->socket, 0);
|
||||
|
||||
$this->readWatcherId = $this->reactor->onReadable($this->socket, function() {
|
||||
$this->onReadableSocket();
|
||||
}, ["enable" => false]);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private function getNextFreeLookupId() {
|
||||
do {
|
||||
$result = $this->lookupIdCounter++;
|
||||
|
||||
if ($this->lookupIdCounter >= PHP_INT_MAX) {
|
||||
$this->lookupIdCounter = 0;
|
||||
}
|
||||
} while(isset($this->pendingLookups[$result]));
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
private function getRequestList($mode) {
|
||||
$result = [];
|
||||
|
||||
if ($mode & AddressModes::PREFER_INET6) {
|
||||
if ($mode & AddressModes::INET6_ADDR) {
|
||||
$result[] = AddressModes::INET6_ADDR;
|
||||
}
|
||||
if ($mode & AddressModes::INET4_ADDR) {
|
||||
$result[] = AddressModes::INET4_ADDR;
|
||||
}
|
||||
} else {
|
||||
if ($mode & AddressModes::INET4_ADDR) {
|
||||
$result[] = AddressModes::INET4_ADDR;
|
||||
}
|
||||
if ($mode & AddressModes::INET6_ADDR) {
|
||||
$result[] = AddressModes::INET6_ADDR;
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
private function getNextFreeRequestId() {
|
||||
do {
|
||||
$result = $this->requestIdCounter++;
|
||||
|
||||
if ($this->requestIdCounter >= 65536) {
|
||||
$this->requestIdCounter = 0;
|
||||
}
|
||||
} while (isset($this->pendingRequestsById[$result]));
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
private function sendRequest($request) {
|
||||
$packet = $this->requestBuilder->buildRequest($request['id'], $request['name'], $request['type']);
|
||||
|
||||
$bytesWritten = fwrite($this->socket, $packet);
|
||||
if ($bytesWritten < strlen($packet)) {
|
||||
$this->completeRequest($request, null, ResolutionErrors::ERR_REQUEST_SEND_FAILED);
|
||||
return;
|
||||
}
|
||||
|
||||
$request['timeout_id'] = $this->reactor->once(function() use($request) {
|
||||
unset($this->pendingRequestsByNameAndType[$request['name']][$request['type']]);
|
||||
$this->completeRequest($request, null, ResolutionErrors::ERR_SERVER_TIMEOUT);
|
||||
}, $this->msRequestTimeout);
|
||||
|
||||
$this->pendingRequestsById[$request['id']] = $request;
|
||||
$this->pendingRequestsByNameAndType[$request['name']][$request['type']] = &$this->pendingRequestsById[$request['id']];
|
||||
}
|
||||
|
||||
private function onReadableSocket() {
|
||||
$packet = fread($this->socket, 512);
|
||||
|
||||
// Decode the response and clean up the pending requests list
|
||||
$decoded = $this->responseInterpreter->decode($packet);
|
||||
if ($decoded === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
list($id, $response) = $decoded;
|
||||
$request = $this->pendingRequestsById[$id];
|
||||
$name = $request['name'];
|
||||
|
||||
$this->reactor->cancel($request['timeout_id']);
|
||||
unset(
|
||||
$this->pendingRequestsById[$id],
|
||||
$this->pendingRequestsByNameAndType[$name][$request['type']]
|
||||
);
|
||||
|
||||
// Interpret the response and make sure we have at least one resource record
|
||||
$interpreted = $this->responseInterpreter->interpret($response, $request['type']);
|
||||
if ($interpreted === null) {
|
||||
foreach ($request['lookups'] as $id => $lookup) {
|
||||
$this->processPendingLookup($id);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Distribute the result to the appropriate lookup routine
|
||||
list($type, $addr, $ttl) = $interpreted;
|
||||
if ($type === AddressModes::CNAME) {
|
||||
foreach ($request['lookups'] as $id => $lookup) {
|
||||
$this->redirectPendingLookup($id, $addr);
|
||||
}
|
||||
} else if ($addr !== null) {
|
||||
$this->cache->set($key, $addr, $ttl);
|
||||
$this->completeRequest($request, $addr, $type);
|
||||
} else {
|
||||
foreach ($request['lookups'] as $id => $lookup) {
|
||||
$this->processPendingLookup($id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function completePendingLookup($id, $addr, $type) {
|
||||
if (!isset($this->pendingLookups[$id])) {
|
||||
return;
|
||||
}
|
||||
|
||||
$lookupStruct = $this->pendingLookups[$id];
|
||||
$future = $lookupStruct['future'];
|
||||
unset($this->pendingLookups[$id]);
|
||||
|
||||
if ($addr) {
|
||||
$future->succeed([$addr, $type]);
|
||||
} else {
|
||||
$future->fail(new ResolutionException(
|
||||
$msg = sprintf('DNS resolution failed: %s', $lookupStruct['name']),
|
||||
$code = $type
|
||||
));
|
||||
}
|
||||
|
||||
if (empty($this->pendingLookups)) {
|
||||
$this->isReadWatcherEnabled = false;
|
||||
$this->reactor->disable($this->readWatcherId);
|
||||
}
|
||||
}
|
||||
|
||||
private function completeRequest($request, $addr, $type) {
|
||||
foreach ($request['lookups'] as $id => $lookup) {
|
||||
$this->completePendingLookup($id, $addr, $type);
|
||||
}
|
||||
}
|
||||
|
||||
private function processPendingLookup($id) {
|
||||
if (!$this->pendingLookups[$id]['requests']) {
|
||||
$this->completePendingLookup($id, null, ResolutionErrors::ERR_NO_RECORD);
|
||||
return;
|
||||
}
|
||||
|
||||
$name = $this->pendingLookups[$id]['name'];
|
||||
$type = array_shift($this->pendingLookups[$id]['requests']);
|
||||
$key = $name . $type;
|
||||
|
||||
if (yield $this->cache->has($key)) {
|
||||
$this->completePendingLookup($id, $addr, $type);
|
||||
} else {
|
||||
$this->dispatchRequest($id, $name, $type);
|
||||
}
|
||||
}
|
||||
|
||||
private function dispatchRequest($id, $name, $type) {
|
||||
$this->pendingLookups[$id]['last_type'] = $type;
|
||||
$this->pendingRequestsByNameAndType[$name][$type]['lookups'][$id] = $this->pendingLookups[$id];
|
||||
|
||||
if (count($this->pendingRequestsByNameAndType[$name][$type]) === 1) {
|
||||
$request = [
|
||||
'id' => $this->getNextFreeRequestId(),
|
||||
'name' => $name,
|
||||
'type' => $type,
|
||||
'lookups' => [$id => $this->pendingLookups[$id]],
|
||||
'timeout_id' => null,
|
||||
];
|
||||
|
||||
$this->sendRequest($request);
|
||||
}
|
||||
}
|
||||
|
||||
private function redirectPendingLookup($id, $name) {
|
||||
array_unshift($this->pendingLookups[$id]['requests'], $this->pendingLookups[$id]['last_type']);
|
||||
$this->pendingLookups[$id]['last_type'] = null;
|
||||
$this->pendingLookups[$id]['name'] = $name;
|
||||
|
||||
$this->processPendingLookup($id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the Client options
|
||||
*
|
||||
* @param int $option
|
||||
* @param mixed $value
|
||||
* @throws \RuntimeException If modifying server address/port once connected
|
||||
* @throws \DomainException On unknown option key
|
||||
* @return self
|
||||
*/
|
||||
public function setOption($option, $value) {
|
||||
switch ($option) {
|
||||
case self::OP_MS_REQUEST_TIMEOUT:
|
||||
$this->msRequestTimeout = (int) $value;
|
||||
break;
|
||||
case self::OP_SERVER_ADDRESS:
|
||||
if ($this->socket) {
|
||||
throw new \RuntimeException(
|
||||
'Server address cannot be modified once connected'
|
||||
);
|
||||
} else {
|
||||
$this->serverAddress = $value;
|
||||
}
|
||||
break;
|
||||
case self::OP_SERVER_PORT:
|
||||
if ($this->socket) {
|
||||
throw new \RuntimeException(
|
||||
'Server port cannot be modified once connected'
|
||||
);
|
||||
} else {
|
||||
$this->serverPort = $value;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
throw new \DomainException(
|
||||
sprintf("Unkown option: %s", $option)
|
||||
);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
@ -1,120 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Amp\Dns;
|
||||
|
||||
class HostsFile {
|
||||
/**
|
||||
* Path to hosts file
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $path;
|
||||
|
||||
/**
|
||||
* Mapped names from hosts file
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $data;
|
||||
|
||||
/**
|
||||
* The file modification time when the last reload was performed
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
private $mtime = 0;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param string $path
|
||||
* @throws \LogicException
|
||||
*/
|
||||
public function __construct($path = null) {
|
||||
if ($path === null) {
|
||||
$path = \stripos(PHP_OS, "win") === 0
|
||||
? "C:\Windows\system32\drivers\etc\hosts"
|
||||
: "/etc/hosts"
|
||||
;
|
||||
}
|
||||
|
||||
if (!file_exists($path)) {
|
||||
throw new \LogicException($path . ' does not exist');
|
||||
} else if (!is_file($path)) {
|
||||
throw new \LogicException($path . ' is not a file');
|
||||
} else if (!is_readable($path)) {
|
||||
throw new \LogicException($path . ' is not readable');
|
||||
}
|
||||
|
||||
$this->path = $path;
|
||||
}
|
||||
|
||||
private function reload() {
|
||||
$this->data = [
|
||||
AddressModes::INET4_ADDR => [],
|
||||
AddressModes::INET6_ADDR => [],
|
||||
];
|
||||
$key = null;
|
||||
|
||||
foreach (file($this->path) as $line) {
|
||||
$line = trim($line);
|
||||
if ($line !== '' && $line[0] === '#') {
|
||||
continue;
|
||||
}
|
||||
|
||||
$parts = preg_split('/\s+/', $line);
|
||||
if (!($ip = @inet_pton($parts[0]))) {
|
||||
continue;
|
||||
} elseif (isset($ip[4])) {
|
||||
$key = AddressModes::INET6_ADDR;
|
||||
} else {
|
||||
$key = AddressModes::INET4_ADDR;
|
||||
}
|
||||
|
||||
for ($i = 1, $l = count($parts); $i < $l; $i++) {
|
||||
if (validateHostName($parts[$i])) {
|
||||
$this->data[$key][$parts[$i]] = $parts[0];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function ensureDataIsCurrent() {
|
||||
clearstatcache(true, $this->path);
|
||||
$modTime = filemtime($this->path);
|
||||
|
||||
if ($modTime > $this->mtime) {
|
||||
$this->reload();
|
||||
$this->mtime = $modTime;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Look up a name in the hosts file
|
||||
*
|
||||
* @param string $name
|
||||
* @param int $mode
|
||||
* @return array|null
|
||||
*/
|
||||
public function resolve($name, $mode) {
|
||||
$this->ensureDataIsCurrent();
|
||||
|
||||
$have4 = isset($this->data[AddressModes::INET4_ADDR][$name]);
|
||||
$have6 = isset($this->data[AddressModes::INET6_ADDR][$name]);
|
||||
$want4 = (bool)($mode & AddressModes::INET4_ADDR);
|
||||
$want6 = (bool)($mode & AddressModes::INET6_ADDR);
|
||||
$pref6 = (bool)($mode & AddressModes::PREFER_INET6);
|
||||
|
||||
if ($have6 && $want6 && (!$want4 || !$have4 || $pref6)) {
|
||||
return [$this->data[AddressModes::INET6_ADDR][$name], AddressModes::INET6_ADDR];
|
||||
} else if ($have4 && $want4) {
|
||||
return [$this->data[AddressModes::INET4_ADDR][$name], AddressModes::INET4_ADDR];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public function getPath() {
|
||||
return $this->path;
|
||||
}
|
||||
}
|
5
lib/NoRecordException.php
Normal file
5
lib/NoRecordException.php
Normal file
@ -0,0 +1,5 @@
|
||||
<?php
|
||||
|
||||
namespace Amp\Dns;
|
||||
|
||||
class NoRecordException extends ResolutionException {}
|
@ -1,66 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Amp\Dns;
|
||||
|
||||
use LibDNS\Messages\MessageFactory;
|
||||
use LibDNS\Messages\MessageTypes;
|
||||
use LibDNS\Records\QuestionFactory;
|
||||
use LibDNS\Records\ResourceQTypes;
|
||||
use LibDNS\Encoder\EncoderFactory;
|
||||
use LibDNS\Encoder\Encoder;
|
||||
|
||||
class RequestBuilder {
|
||||
/**
|
||||
* @var \LibDNS\Messages\MessageFactory
|
||||
*/
|
||||
private $messageFactory;
|
||||
|
||||
/**
|
||||
* @var \LibDNS\Records\QuestionFactory
|
||||
*/
|
||||
private $questionFactory;
|
||||
|
||||
/**
|
||||
* @var \LibDNS\Encoder\Encoder
|
||||
*/
|
||||
private $encoder;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param MessageFactory $messageFactory
|
||||
* @param QuestionFactory $questionFactory
|
||||
* @param Encoder $encoder
|
||||
*/
|
||||
public function __construct(
|
||||
MessageFactory $messageFactory = null,
|
||||
QuestionFactory $questionFactory = null,
|
||||
Encoder $encoder = null
|
||||
) {
|
||||
$this->messageFactory = $messageFactory ?: new MessageFactory;
|
||||
$this->questionFactory = $questionFactory ?: new QuestionFactory;
|
||||
$this->encoder = $encoder ?: (new EncoderFactory)->create();
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a request packet for a name and record type
|
||||
*
|
||||
* @param int $id
|
||||
* @param string $name
|
||||
* @param int $type
|
||||
* @return string
|
||||
*/
|
||||
public function buildRequest($id, $name, $type) {
|
||||
$qType = $type === AddressModes::INET4_ADDR ? ResourceQTypes::A : ResourceQTypes::AAAA;
|
||||
|
||||
$question = $this->questionFactory->create($qType);
|
||||
$question->setName($name);
|
||||
|
||||
$request = $this->messageFactory->create(MessageTypes::QUERY);
|
||||
$request->setID($id);
|
||||
$request->getQuestionRecords()->add($question);
|
||||
$request->isRecursionDesired(true);
|
||||
|
||||
return $this->encoder->encode($request);
|
||||
}
|
||||
}
|
@ -1,10 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Amp\Dns;
|
||||
|
||||
class ResolutionErrors {
|
||||
const ERR_INVALID_NAME = 1;
|
||||
const ERR_NO_RECORD = 2;
|
||||
const ERR_SERVER_TIMEOUT = 3;
|
||||
const ERR_REQUEST_SEND_FAILED = 4;
|
||||
}
|
@ -1,77 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Amp\Dns;
|
||||
|
||||
use Amp\Success;
|
||||
use Amp\Failure;
|
||||
|
||||
class Resolver {
|
||||
/**
|
||||
* @var \Amp\Dns\Client
|
||||
*/
|
||||
private $client;
|
||||
|
||||
/**
|
||||
* @var \Amp\Dns\HostsFile
|
||||
*/
|
||||
private $hostsFile;
|
||||
|
||||
/**
|
||||
* @var \Amp\Dns\NameValidator
|
||||
*/
|
||||
private $nameValidator;
|
||||
|
||||
/**
|
||||
* @param \Amp\Dns\Client $client
|
||||
* @param \Amp\Dns\HostsFile $hostsFile
|
||||
* @param \Amp\Dns\NameValidator $nameValidator
|
||||
*/
|
||||
public function __construct(
|
||||
Client $client = null,
|
||||
HostsFile $hostsFile = null,
|
||||
NameValidator $nameValidator = null
|
||||
) {
|
||||
$this->client = $client ?: new Client;
|
||||
$this->nameValidator = $nameValidator ?: new NameValidator;
|
||||
$this->hostsFile = $hostsFile ?: new HostsFile($this->nameValidator);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve a host name to an IP address
|
||||
*
|
||||
* @param string $name
|
||||
* @param int $mode
|
||||
* @return \Amp\Promise
|
||||
*/
|
||||
public function resolve($name, $mode = AddressModes::ANY_PREFER_INET4) {
|
||||
if (strcasecmp($name, 'localhost') === 0) {
|
||||
return new Success($this->resolveLocalhost($mode));
|
||||
} elseif ($addrStruct = $this->resolveFromIp($name)) {
|
||||
return new Success($addrStruct);
|
||||
} elseif (!$this->nameValidator->validate($name)) {
|
||||
return new Failure(new ResolutionException(
|
||||
sprintf('Invalid DNS name format: %s', $name)
|
||||
));
|
||||
} elseif ($this->hostsFile && ($addrStruct = $this->hostsFile->resolve($name, $mode))) {
|
||||
return new Success($addrStruct);
|
||||
} else {
|
||||
return $this->client->resolve($name, $mode);
|
||||
}
|
||||
}
|
||||
|
||||
private function resolveLocalhost($mode) {
|
||||
return ($mode & AddressModes::PREFER_INET6)
|
||||
? ['::1', AddressModes::INET6_ADDR]
|
||||
: ['127.0.0.1', AddressModes::INET4_ADDR];
|
||||
}
|
||||
|
||||
private function resolveFromIp($name) {
|
||||
if (!$inAddr = @inet_pton($name)) {
|
||||
return [];
|
||||
} elseif (isset($inAddr['15'])) {
|
||||
return [$name, AddressModes::INET6_ADDR];
|
||||
} else {
|
||||
return [$name, AddressModes::INET4_ADDR];
|
||||
}
|
||||
}
|
||||
}
|
@ -1,83 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Amp\Dns;
|
||||
|
||||
use LibDNS\Decoder\Decoder;
|
||||
use LibDNS\Decoder\DecoderFactory;
|
||||
use LibDNS\Messages\Message;
|
||||
use LibDNS\Messages\MessageTypes;
|
||||
use LibDNS\Records\ResourceTypes;
|
||||
|
||||
class ResponseInterpreter {
|
||||
/**
|
||||
* @var Decoder
|
||||
*/
|
||||
private $decoder;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param Decoder $decoder
|
||||
*/
|
||||
public function __construct(Decoder $decoder = null) {
|
||||
$this->decoder = $decoder ?: (new DecoderFactory)->create();
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempt to decode a data packet to a DNS response message
|
||||
*
|
||||
* @param string $packet
|
||||
* @return Message|null
|
||||
*/
|
||||
public function decode($packet) {
|
||||
try {
|
||||
$message = $this->decoder->decode($packet);
|
||||
} catch (\Exception $e) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if ($message->getType() !== MessageTypes::RESPONSE || $message->getResponseCode() !== 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return [$message->getID(), $message];
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract the message ID and response data from a DNS response packet
|
||||
*
|
||||
* @param Message $message
|
||||
* @param int $expectedType
|
||||
* @return array|null
|
||||
*/
|
||||
public function interpret($message, $expectedType) {
|
||||
static $typeMap = [
|
||||
AddressModes::INET4_ADDR => ResourceTypes::A,
|
||||
AddressModes::INET6_ADDR => ResourceTypes::AAAA,
|
||||
];
|
||||
|
||||
$answers = $message->getAnswerRecords();
|
||||
if (!count($answers)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
/** @var \LibDNS\Records\Resource $record */
|
||||
$cname = null;
|
||||
foreach ($answers as $record) {
|
||||
switch ($record->getType()) {
|
||||
case $typeMap[$expectedType]:
|
||||
return [$expectedType, (string)$record->getData(), $record->getTTL()];
|
||||
|
||||
case ResourceTypes::CNAME:
|
||||
$cname = (string)$record->getData();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ($cname) {
|
||||
return [AddressModes::CNAME, $cname, null];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
5
lib/TimeoutException.php
Normal file
5
lib/TimeoutException.php
Normal file
@ -0,0 +1,5 @@
|
||||
<?php
|
||||
|
||||
namespace Amp\Dns;
|
||||
|
||||
class TimeoutException extends ResolutionException {}
|
14
lib/constants.php
Normal file
14
lib/constants.php
Normal file
@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
namespace Amp\Dns;
|
||||
|
||||
// @codeCoverageIgnoreStart
|
||||
const DEFAULT_SERVER = "8.8.8.8";
|
||||
const DEFAULT_PORT = 53;
|
||||
const DEFAULT_TIMEOUT = 5000;
|
||||
const MAX_REQUEST_ID = 65536;
|
||||
const IDLE_TIMEOUT = 15000;
|
||||
const MODE_INET4 = 1;
|
||||
const MODE_INET6 = 2;
|
||||
const MODE_CNAME = 3;
|
||||
// @codeCoverageIgnoreEnd
|
@ -2,125 +2,423 @@
|
||||
|
||||
namespace Amp\Dns;
|
||||
|
||||
use Amp\Reactor;
|
||||
use \LibDNS\Messages\MessageFactory;
|
||||
use \LibDNS\Messages\MessageTypes;
|
||||
use \LibDNS\Records\QuestionFactory;
|
||||
use \LibDNS\Records\ResourceTypes;
|
||||
use \LibDNS\Records\ResourceQTypes;
|
||||
use \LibDNS\Encoder\EncoderFactory;
|
||||
use \LibDNS\Decoder\DecoderFactory;
|
||||
|
||||
function validateHostName($name) {
|
||||
if (isset($name[253])) {
|
||||
return false;
|
||||
/**
|
||||
* Resolve a DNS name to an IP address
|
||||
*
|
||||
* Upon success the returned promise resolves to an indexed array of the form:
|
||||
*
|
||||
* [string $resolvedIp, int $mode, int $ttl]
|
||||
*
|
||||
* The $mode parameter at index 1 corresponds to one of two constant values to
|
||||
* indicate if the resulting IP is IPv4 or IPv6:
|
||||
*
|
||||
* - Amp\Dns\MODE_INET4
|
||||
* - Amp\Dns\MODE_INET6
|
||||
*
|
||||
* A null $ttl value indicates the DNS name was resolved from the cache or the
|
||||
* local hosts file.
|
||||
*
|
||||
* Options:
|
||||
*
|
||||
* - "server" | string Custom DNS server address in ip or ip:port format
|
||||
* - "timeout" | int Default: 3000ms
|
||||
* - "mode" | int Either Amp\Dns\MODE_INET4 or Amp\Dns\MODE_INET6
|
||||
* - "no_hosts" | bool Ignore entries in the hosts file
|
||||
* - "no_cache" | bool Ignore cached DNS response entries
|
||||
*
|
||||
* If the custom per-request "server" option is not present the resolver will
|
||||
* use the default from the following built-in constant:
|
||||
*
|
||||
* - Amp\Dns\DEFAULT_SERVER
|
||||
*
|
||||
* @param string $name The hostname to resolve
|
||||
* @param array $options
|
||||
* @return \Amp\Promise
|
||||
* @TODO add boolean "clear_cache" option flag
|
||||
* @TODO add boolean "reload_hosts" option flag
|
||||
*/
|
||||
function resolve($name, array $options = []) {
|
||||
$mode = isset($options["mode"]) ? $options["mode"] : MODE_INET4;
|
||||
switch ($mode) {
|
||||
case MODE_INET4:
|
||||
case MODE_INET6:
|
||||
break;
|
||||
default:
|
||||
return new \Amp\Failure(new ResolutionException(
|
||||
"Invalid request mode option; Amp\Dns\MODE_INET4 or Amp\Dns\MODE_INET6 required"
|
||||
));
|
||||
}
|
||||
if (\strcasecmp($name, "localhost") === 0) {
|
||||
return new \Amp\Success(($mode === MODE_INET6)
|
||||
? ["::1", MODE_INET6, $ttl = null]
|
||||
: ["127.0.0.1", MODE_INET4, $ttl = null]
|
||||
);
|
||||
} elseif (!$inAddr = @\inet_pton($name)) {
|
||||
return __isValidHostName($name)
|
||||
? \Amp\resolve(__doResolve($name, $mode, $options))
|
||||
: new \Amp\Failure(new ResolutionException(
|
||||
"Cannot resolve; invalid host name"
|
||||
))
|
||||
;
|
||||
} elseif (isset($inAddr[15])) {
|
||||
return new \Amp\Success([$name, MODE_INET6, $ttl = null]);
|
||||
} else {
|
||||
return new \Amp\Success([$name, MODE_INET4, $ttl = null]);
|
||||
}
|
||||
}
|
||||
|
||||
function __isValidHostName($name) {
|
||||
$pattern = "/^(?:[a-z0-9](?:[a-z0-9\-]{0,61}[a-z0-9]){0,1})(?:\.[a-z0-9][a-z0-9\-]{0,61}[a-z0-9])*$/i";
|
||||
|
||||
return (bool) \preg_match($pattern, $name);
|
||||
return isset($name[253]) ? false : (bool) \preg_match($pattern, $name);
|
||||
}
|
||||
|
||||
function __doResolve($name, $mode, $options) {
|
||||
static $state;
|
||||
$state = $state ?: (yield \Amp\resolve(__init()));
|
||||
|
||||
$name = \strtolower($name);
|
||||
|
||||
|
||||
class DnsServer {
|
||||
private $addr;
|
||||
private static $addresses = [];
|
||||
|
||||
public function __construct($addr, $port = 53) {
|
||||
if (!$inAddr = @\inet_pton($addr)) {
|
||||
throw new \DomainException(
|
||||
"Invalid IP address: {$addr}"
|
||||
);
|
||||
}
|
||||
$addr = \inet_ntop($inAddr);
|
||||
if (isset($inAddr[15])) {
|
||||
$addr = "[{$addr}]";
|
||||
}
|
||||
$port = (int) $port;
|
||||
$this->addr = "udp://{$addr}:{$port}";
|
||||
// Check for cache hits
|
||||
$cacheKey = "{$mode}#{$name}";
|
||||
if (empty($options["no_cache"])) {
|
||||
if (yield $state->arrayCache->has($cacheKey)) {
|
||||
$result = (yield $state->arrayCache->get($cacheKey));
|
||||
yield new \Amp\CoroutineResult([$result, $mode, $ttl = null]);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
function initServer()
|
||||
|
||||
function resolver(Reactor $reactor
|
||||
|
||||
class Resolver {
|
||||
private $
|
||||
}
|
||||
|
||||
function resolve($name, array $options = []) {
|
||||
$generator = __doResolve($name, $options);
|
||||
|
||||
return \Amp\resolve($generator);
|
||||
}
|
||||
|
||||
function __doResolve($name, $options) {
|
||||
static $lookupIdCounter;
|
||||
static $cache;
|
||||
|
||||
if (empty($cache)) {
|
||||
$cache = new \Amp\Cache\ArrayCache;
|
||||
}
|
||||
|
||||
$mode = isset($options["mode"]) ? $options["mode"] : AddressModes::ANY_PREFER_INET4;
|
||||
if ($mode & AddressModes::PREFER_INET6) {
|
||||
if ($mode & AddressModes::INET6_ADDR) {
|
||||
$requests[] = AddressModes::INET6_ADDR;
|
||||
}
|
||||
if ($mode & AddressModes::INET4_ADDR) {
|
||||
$requests[] = AddressModes::INET4_ADDR;
|
||||
}
|
||||
// Check for hosts file matches
|
||||
if (empty($options["no_hosts"])) {
|
||||
$have4 = isset($state->hostsFile[MODE_INET4][$name]);
|
||||
$have6 = isset($state->hostsFile[MODE_INET6][$name]);
|
||||
$want4 = (bool)($mode & MODE_INET4);
|
||||
$want6 = (bool)($mode & MODE_INET6);
|
||||
if ($have6 && $want6) {
|
||||
$result = [$state->hostsFile[MODE_INET6][$name], MODE_INET6, $ttl = null];
|
||||
} elseif ($have4 && $want4) {
|
||||
$result = [$state->hostsFile[MODE_INET4][$name], MODE_INET4, $ttl = null];
|
||||
} else {
|
||||
if ($mode & AddressModes::INET4_ADDR) {
|
||||
$requests[] = AddressModes::INET4_ADDR;
|
||||
$result = null;
|
||||
}
|
||||
if ($mode & AddressModes::INET6_ADDR) {
|
||||
$requests[] = AddressModes::INET6_ADDR;
|
||||
}
|
||||
}
|
||||
|
||||
$type = array_shift($requests);
|
||||
$cacheKey = $name . $type;
|
||||
|
||||
if (yield $cache->has($cacheKey)) {
|
||||
$result = yield $cache->get($cacheKey);
|
||||
if ($result) {
|
||||
yield new \Amp\CoroutineResult($result);
|
||||
return;
|
||||
}
|
||||
|
||||
do {
|
||||
$lookupId = $lookupIdCounter++;
|
||||
if ($lookupIdCounter >= PHP_INT_MAX) {
|
||||
$lookupIdCounter = 0;
|
||||
}
|
||||
} while (isset($pendingLookups[$lookupId]));
|
||||
|
||||
$remote = isset($options["server_addr"])
|
||||
? "udp://" . $options["server_addr"]
|
||||
: "udp://8.8.8.8:53"
|
||||
$timeout = empty($options["timeout"]) ? DEFAULT_TIMEOUT : (int) $options["timeout"];
|
||||
|
||||
$uri = empty($options["server"])
|
||||
? "udp://" . DEFAULT_SERVER . ":" . DEFAULT_PORT
|
||||
: __parseCustomServerUri($options["server"])
|
||||
;
|
||||
$server = __loadExistingServer($state, $uri) ?: __loadNewServer($state, $uri);
|
||||
|
||||
if (!$socket = @\stream_socket_client($remote, $errno, $errstr)) {
|
||||
throw new ConnectException(sprintf(
|
||||
// Get the next available request ID
|
||||
do {
|
||||
$requestId = $state->requestIdCounter++;
|
||||
if ($state->requestIdCounter >= MAX_REQUEST_ID) {
|
||||
$state->requestIdCounter = 1;
|
||||
}
|
||||
} while (isset($state->pendingRequests[$requestId]));
|
||||
|
||||
// Create question record
|
||||
$questionType = ($mode === MODE_INET4) ? ResourceQTypes::A : ResourceQTypes::AAAA;
|
||||
$question = $state->questionFactory->create($questionType);
|
||||
$question->setName($name);
|
||||
|
||||
// Create request message
|
||||
$request = $state->messageFactory->create(MessageTypes::QUERY);
|
||||
$request->getQuestionRecords()->add($question);
|
||||
$request->isRecursionDesired(true);
|
||||
$request->setID($requestId);
|
||||
|
||||
// Encode request message
|
||||
$requestPacket = $state->encoder->encode($request);
|
||||
|
||||
// Send request
|
||||
$bytesWritten = \fwrite($server->socket, $requestPacket);
|
||||
if ($bytesWritten === false || isset($packet[$bytesWritten])) {
|
||||
throw new ResolutionException(
|
||||
"Request send failed"
|
||||
);
|
||||
}
|
||||
|
||||
$promisor = new \Amp\Deferred;
|
||||
$server->pendingRequests[$requestId] = true;
|
||||
$state->pendingRequests[$requestId] = [$promisor, $name, $mode];
|
||||
|
||||
try {
|
||||
$resultArr = (yield \Amp\timeout($promisor->promise(), $timeout));
|
||||
} catch (\Amp\TimeoutException $e) {
|
||||
throw new TimeoutException(
|
||||
"Name resolution timed out for {$name}"
|
||||
);
|
||||
}
|
||||
|
||||
list($resultIp, $resultMode, $resultTtl) = $resultArr;
|
||||
|
||||
if ($resultMode === MODE_CNAME) {
|
||||
$result = (yield resolve($resultIp, $mode, $options));
|
||||
list($resultIp, $resultMode, $resultTtl) = $result;
|
||||
}
|
||||
|
||||
yield $state->arrayCache->set($cacheKey, $resultIp, $resultTtl);
|
||||
yield new \Amp\CoroutineResult($resultArr);
|
||||
}
|
||||
|
||||
function __init() {
|
||||
$state = new \StdClass;
|
||||
$state->messageFactory = new MessageFactory;
|
||||
$state->questionFactory = new QuestionFactory;
|
||||
$state->encoder = (new EncoderFactory)->create();
|
||||
$state->decoder = (new DecoderFactory)->create();
|
||||
$state->arrayCache = new \Amp\Cache\ArrayCache;
|
||||
$state->hostsFile = (yield \Amp\resolve(__loadHostsFile()));
|
||||
$state->requestIdCounter = 1;
|
||||
$state->pendingRequests = [];
|
||||
$state->serverIdMap = [];
|
||||
$state->serverUriMap = [];
|
||||
$state->serverIdTimeoutMap = [];
|
||||
$state->now = \time();
|
||||
$state->serverTimeoutWatcher = \Amp\repeat(function ($watcherId) use ($state) {
|
||||
$state->now = $now = \time();
|
||||
foreach ($state->serverIdTimeoutMap as $id => $expiry) {
|
||||
if ($now > $expiry) {
|
||||
__unloadServer($state, $id);
|
||||
}
|
||||
}
|
||||
if (empty($state->serverIdMap)) {
|
||||
\Amp\disable($watcherId);
|
||||
}
|
||||
}, 1000, $options = [
|
||||
"enable" => true,
|
||||
"keep_alive" => false,
|
||||
]);
|
||||
|
||||
yield new \Amp\CoroutineResult($state);
|
||||
}
|
||||
|
||||
function __loadHostsFile($path = null) {
|
||||
$data = [
|
||||
MODE_INET4 => [],
|
||||
MODE_INET6 => [],
|
||||
];
|
||||
if (empty($path)) {
|
||||
$path = \stripos(PHP_OS, "win") === 0
|
||||
? "C:\\Windows\\system32\\drivers\\etc\\hosts"
|
||||
: "/etc/hosts"
|
||||
;
|
||||
}
|
||||
try {
|
||||
$contents = (yield \Amp\Filesystem\get($path));
|
||||
$key = null;
|
||||
$lines = \array_filter(\array_map("trim", \explode("\n", $contents)));
|
||||
foreach ($lines as $line) {
|
||||
if ($line[0] === "#") {
|
||||
continue;
|
||||
}
|
||||
$parts = \preg_split('/\s+/', $line);
|
||||
if (!($ip = @\inet_pton($parts[0]))) {
|
||||
continue;
|
||||
} elseif (isset($ip[4])) {
|
||||
$key = MODE_INET6;
|
||||
} else {
|
||||
$key = MODE_INET4;
|
||||
}
|
||||
for ($i = 1, $l = \count($parts); $i < $l; $i++) {
|
||||
if (__isValidHostName($parts[$i])) {
|
||||
$data[$key][$parts[$i]] = $parts[0];
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
// hosts file doesn't exist
|
||||
}
|
||||
|
||||
yield new \Amp\CoroutineResult($data);
|
||||
}
|
||||
|
||||
function __parseCustomServerUri($uri) {
|
||||
if (!\is_string($uri)) {
|
||||
throw new ResolutionException(
|
||||
"Invalid server address (". gettype($uri) ."); string IP required"
|
||||
);
|
||||
}
|
||||
if (($colonPos = strrpos(":", $uri)) !== false) {
|
||||
$addr = \substr($uri, 0, $colonPos);
|
||||
$port = \substr($uri, $colonPos);
|
||||
} else {
|
||||
$addr = $uri;
|
||||
$port = DEFAULT_PORT;
|
||||
}
|
||||
$addr = trim($addr, "[]");
|
||||
if (!$inAddr = @\inet_pton($addr)) {
|
||||
throw new ResolutionException(
|
||||
"Invalid server URI; IP address required"
|
||||
);
|
||||
}
|
||||
|
||||
return isset($inAddr[15]) ? "udp://[{$addr}]:{$port}" : "udp://{$addr}:{$port}";
|
||||
}
|
||||
|
||||
function __loadExistingServer($state, $uri) {
|
||||
if (empty($state->serverUriMap[$uri])) {
|
||||
return;
|
||||
}
|
||||
$server = $state->serverUriMap[$uri];
|
||||
if (\is_resource($server->socket) && !@\feof($server->socket)) {
|
||||
unset($state->serverIdTimeoutMap[$server->id]);
|
||||
\Amp\enable($server->watcherId);
|
||||
return $server;
|
||||
}
|
||||
__unloadServer($state, $server->id);
|
||||
}
|
||||
|
||||
function __loadNewServer($state, $uri) {
|
||||
if (!$socket = @\stream_socket_client($uri, $errno, $errstr)) {
|
||||
throw new ResolutionException(sprintf(
|
||||
"Connection to %s failed: [Error #%d] %s",
|
||||
$uri,
|
||||
$errno,
|
||||
$errstr
|
||||
));
|
||||
}
|
||||
|
||||
\stream_set_blocking($socket, false);
|
||||
$id = (int) $socket;
|
||||
$server = new \StdClass;
|
||||
$server->id = $id;
|
||||
$server->uri = $uri;
|
||||
$server->socket = $socket;
|
||||
$server->pendingRequests = [];
|
||||
$server->watcherId = \Amp\onReadable($socket, "Amp\Dns\__onReadable", [
|
||||
"enable" => true,
|
||||
"keep_alive" => true,
|
||||
"cb_data" => $state,
|
||||
]);
|
||||
$state->serverIdMap[$id] = $server;
|
||||
$state->serverUriMap[$uri] = $server;
|
||||
|
||||
return $server;
|
||||
}
|
||||
|
||||
function __unloadServer($state, $serverId, $error = null) {
|
||||
$server = $state->serverIdMap[$serverId];
|
||||
\Amp\cancel($server->watcherId);
|
||||
unset(
|
||||
$state->serverIdMap[$serverId],
|
||||
$state->serverUriMap[$server->uri]
|
||||
);
|
||||
if (\is_resource($server->socket)) {
|
||||
@\fclose($server->socket);
|
||||
}
|
||||
if ($error && $server->pendingRequests) {
|
||||
foreach (array_keys($server->pendingRequests) as $requestId) {
|
||||
list($promisor) = $state->pendingRequests[$requestId];
|
||||
$promisor->fail($error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (yield $this->cache->has($cacheKey)) {
|
||||
$this->completePendingLookup($id, $addr, $type);
|
||||
function __onReadable($watcherId, $socket, $state) {
|
||||
$serverId = (int) $socket;
|
||||
$packet = @\fread($socket, 512);
|
||||
if ($packet != "") {
|
||||
__decodeResponsePacket($state, $serverId, $packet);
|
||||
} else {
|
||||
$this->dispatchRequest($id, $name, $type);
|
||||
__unloadServer($state, $serverId, new ResolutionException(
|
||||
"Server connection failed"
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
$promisor = new Deferred;
|
||||
$reactor->onReadable($socket, "Amp\Dns\__onReadable", $promisor);
|
||||
yield $promisor->promise();
|
||||
|
||||
|
||||
|
||||
function __decodeResponsePacket($state, $serverId, $packet) {
|
||||
try {
|
||||
$response = $state->decoder->decode($packet);
|
||||
$requestId = $response->getID();
|
||||
$responseCode = $response->getResponseCode();
|
||||
$responseType = $response->getType();
|
||||
|
||||
if ($responseCode !== 0) {
|
||||
__finalizeResult($state, $serverId, $requestId, new ResolutionException(
|
||||
"Server returned error code: {$responseCode}"
|
||||
));
|
||||
} elseif ($responseType !== MessageTypes::RESPONSE) {
|
||||
__unloadServer($state, $serverId, new ResolutionException(
|
||||
"Invalid server reply; expected RESPONSE but received QUERY"
|
||||
));
|
||||
} else {
|
||||
__processDecodedResponse($state, $serverId, $requestId, $response);
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
__unloadServer($state, $serverId, new ResolutionException(
|
||||
"Response decode error",
|
||||
0,
|
||||
$e
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
function __getRequestModesFrom
|
||||
function __processDecodedResponse($state, $serverId, $requestId, $response) {
|
||||
static $typeMap = [
|
||||
MODE_INET4 => ResourceTypes::A,
|
||||
MODE_INET6 => ResourceTypes::AAAA,
|
||||
];
|
||||
|
||||
list($promisor, $name, $mode) = $state->pendingRequests[$requestId];
|
||||
$answers = $response->getAnswerRecords();
|
||||
foreach ($answers as $record) {
|
||||
switch ($record->getType()) {
|
||||
case $typeMap[$mode]:
|
||||
$result = [(string) $record->getData(), $mode, $record->getTTL()];
|
||||
break 2;
|
||||
case ResourceTypes::CNAME:
|
||||
// CNAME should only be used if no A records exist so we only
|
||||
// break out of the switch (and not the foreach loop) here.
|
||||
$result = [(string) $record->getData(), MODE_CNAME, $record->getTTL()];
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (empty($result)) {
|
||||
$recordType = ($mode === MODE_INET4) ? "A" : "AAAA";
|
||||
__finalizeResult($state, $serverId, $requestId, new NoRecordException(
|
||||
"No {$recordType} records returned for {$name}"
|
||||
));
|
||||
} else {
|
||||
__finalizeResult($state, $serverId, $requestId, $error = null, $result);
|
||||
}
|
||||
}
|
||||
|
||||
function __finalizeResult($state, $serverId, $requestId, $error = null, $result = null) {
|
||||
if (empty($state->pendingRequests[$requestId])) {
|
||||
return;
|
||||
}
|
||||
|
||||
list($promisor) = $state->pendingRequests[$requestId];
|
||||
$server = $state->serverIdMap[$serverId];
|
||||
unset(
|
||||
$state->pendingRequests[$requestId],
|
||||
$server->pendingRequests[$requestId]
|
||||
);
|
||||
if (empty($server->pendingRequests)) {
|
||||
$state->serverIdTimeoutMap[$server->id] = $state->now + IDLE_TIMEOUT;
|
||||
\Amp\disable($server->watcherId);
|
||||
\Amp\enable($state->serverTimeoutWatcher);
|
||||
}
|
||||
if ($error) {
|
||||
$promisor->fail($error);
|
||||
} else {
|
||||
$promisor->succeed($result);
|
||||
}
|
||||
}
|
||||
|
46
phpunit.xml
46
phpunit.xml
@ -1,45 +1,13 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<phpunit bootstrap="./vendor/autoload.php">
|
||||
|
||||
<groups>
|
||||
<exclude>
|
||||
<group>SQL</group>
|
||||
</exclude>
|
||||
</groups>
|
||||
|
||||
<filter>
|
||||
<blacklist>
|
||||
<phpunit bootstrap="./test/bootstrap.php" colors="true">
|
||||
<testsuites>
|
||||
<testsuite name="Tests">
|
||||
<directory>./test</directory>
|
||||
<directory>./vendor</directory>
|
||||
</blacklist>
|
||||
<whitelist>
|
||||
</testsuite>
|
||||
</testsuites>
|
||||
<filter>
|
||||
<whitelist addUncoveredFilesFromWhitelist="true">
|
||||
<directory>./lib</directory>
|
||||
</whitelist>
|
||||
</filter>
|
||||
|
||||
<testsuites>
|
||||
<testsuite name="Addr Test Suite">
|
||||
<directory suffix=".php">./test</directory>
|
||||
</testsuite>
|
||||
</testsuites>
|
||||
|
||||
|
||||
<listeners>
|
||||
<listener class="\Mockery\Adapter\Phpunit\TestListener"/>
|
||||
</listeners>
|
||||
|
||||
<!-- <log type="coverage-text" target="php://stdout" showUncoveredFiles="false"/> -->
|
||||
|
||||
<logging>
|
||||
|
||||
<!-- <log type="junit" target="./var/logfile.xml" logIncompleteSkipped="false"/> -->
|
||||
|
||||
<log type="coverage-text" target="php://stdout" showUncoveredFiles="false"/>
|
||||
<!-- Enabling coverage makes the tests take three times as long, as well
|
||||
as take a bit of time to generate the HTML pages. -->
|
||||
<log type="coverage-html" target="./coverage/" charset="UTF-8"
|
||||
highlight="false" lowUpperBound="35" highLowerBound="70"/>
|
||||
</logging>
|
||||
|
||||
</phpunit>
|
||||
|
@ -1,102 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Amp\Dns\Test;
|
||||
|
||||
use Amp\NativeReactor;
|
||||
use Amp\Dns\AddressModes;
|
||||
use Amp\Dns\Client;
|
||||
|
||||
class ClientTest extends \PHPUnit_Framework_TestCase {
|
||||
|
||||
/**
|
||||
* @expectedException \Amp\Dns\ResolutionException
|
||||
*/
|
||||
public function testFailToConnect() {
|
||||
$reactor = new NativeReactor;
|
||||
$client = new Client($reactor);
|
||||
$client->setOption(Client::OP_SERVER_ADDRESS, '260.260.260.260');
|
||||
$promise = $client->resolve('example.com', AddressModes::INET4_ADDR);
|
||||
$result = \Amp\wait($promise, $reactor);
|
||||
}
|
||||
|
||||
/**
|
||||
* This is just for coverage - which is not worthwhile in itself,
|
||||
* but it makes it easier to detect missing important coverage.
|
||||
*/
|
||||
public function testSetRequestTime() {
|
||||
$reactor = new NativeReactor;
|
||||
$client = new Client($reactor);
|
||||
$client->setOption(Client::OP_MS_REQUEST_TIMEOUT, 1000);
|
||||
$client->setOption(Client::OP_SERVER_PORT, 53);
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException \DomainException
|
||||
*/
|
||||
public function testUnknownOptionThrowsException() {
|
||||
$reactor = new NativeReactor;
|
||||
$client = new Client($reactor);
|
||||
$client->setOption('foo', 1000);
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException \RuntimeException
|
||||
* @group internet
|
||||
*/
|
||||
public function testSetAddressAfterConnectException() {
|
||||
$reactor = new NativeReactor;
|
||||
$client = new Client($reactor);
|
||||
$promise = $client->resolve('google.com', AddressModes::INET4_ADDR);
|
||||
$result = \Amp\wait($promise, $reactor);
|
||||
$client->setOption(Client::OP_SERVER_ADDRESS, '260.260.260.260');
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException \RuntimeException
|
||||
* @group internet
|
||||
*/
|
||||
public function testSetPortAfterConnectException() {
|
||||
$reactor = new NativeReactor;
|
||||
$client = new Client($reactor);
|
||||
$promise = $client->resolve('google.com', AddressModes::INET4_ADDR);
|
||||
$result = \Amp\wait($promise, $reactor);
|
||||
$client->setOption(Client::OP_SERVER_PORT, 53);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @expectedException \Amp\Dns\ResolutionException
|
||||
*/
|
||||
public function testNoAnswers() {
|
||||
$reactor = new NativeReactor;
|
||||
$client = new Client($reactor);
|
||||
$promise = $client->resolve('googleaiusdisuhdihas.apsidjpasjdisjdajsoidaugiug.com', AddressModes::INET4_ADDR);
|
||||
$result = \Amp\wait($promise, $reactor);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that the overflow of lookupIdCounter and requestIdCounter to
|
||||
* zero occurs.
|
||||
*/
|
||||
public function testPendingIdOverflow() {
|
||||
$reactor = new NativeReactor;
|
||||
$class = new \ReflectionClass('Amp\Dns\Client');
|
||||
$lookupIdProperty = $class->getProperty("lookupIdCounter");
|
||||
$requestIdCounterProperty = $class->getProperty("requestIdCounter");
|
||||
|
||||
/** @var Client $client */
|
||||
$client = $class->newInstance($reactor);
|
||||
$lookupIdProperty->setAccessible(true);
|
||||
$lookupIdProperty->setValue($client, PHP_INT_MAX);
|
||||
$requestIdCounterProperty->setAccessible(true);
|
||||
$requestIdCounterProperty->setValue($client, 65535);
|
||||
|
||||
$promise = $client->resolve('google.com', AddressModes::INET4_ADDR);
|
||||
$result = \Amp\wait($promise, $reactor);
|
||||
$lookupIdCounter = $lookupIdProperty->getValue($client);
|
||||
$this->assertEquals(0, $lookupIdCounter);
|
||||
|
||||
$requestIdCounter = $lookupIdProperty->getValue($client);
|
||||
$this->assertEquals(0, $requestIdCounter);
|
||||
}
|
||||
}
|
@ -1,133 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Amp\Dns\Test;
|
||||
|
||||
use Amp\Dns\HostsFile;
|
||||
use Amp\Dns\NameValidator;
|
||||
use Amp\Dns\AddressModes as Mode;
|
||||
|
||||
/**
|
||||
* Class HostsFileTest
|
||||
* @group hosts
|
||||
*/
|
||||
class HostsFileTest extends \PHPUnit_Framework_TestCase {
|
||||
|
||||
public function testBasicIPV4() {
|
||||
$tests = [
|
||||
['192.168.1.1', 'host1.example.com', Mode::INET4_ADDR, Mode::INET4_ADDR],
|
||||
['192.168.1.2', 'host2.example.com', Mode::INET4_ADDR, Mode::INET4_ADDR],
|
||||
|
||||
//Check that ipv4 is returned when both v4 and v6 are is set
|
||||
['192.168.1.1', 'host1.example.com', Mode::INET4_ADDR | Mode::INET6_ADDR, Mode::INET4_ADDR],
|
||||
['192.168.1.2', 'host2.example.com', Mode::INET4_ADDR | Mode::INET6_ADDR, Mode::INET4_ADDR],
|
||||
|
||||
//Check that ipv4 is returned when ANY_* is set
|
||||
['192.168.1.1', 'host1.example.com', Mode::ANY_PREFER_INET4, Mode::INET4_ADDR],
|
||||
['192.168.1.2', 'host2.example.com', Mode::ANY_PREFER_INET4, Mode::INET4_ADDR],
|
||||
['192.168.1.1', 'host1.example.com', Mode::ANY_PREFER_INET6, Mode::INET4_ADDR],
|
||||
['192.168.1.2', 'host2.example.com', Mode::ANY_PREFER_INET6, Mode::INET4_ADDR],
|
||||
|
||||
//Check request for ipv6 returns null
|
||||
[null, 'host1.example.com', Mode::INET6_ADDR, null],
|
||||
|
||||
//Check non-existant domains return null
|
||||
[null, 'host4.example.com', Mode::INET4_ADDR, null],
|
||||
];
|
||||
|
||||
$this->runHostsFileTests($tests, __DIR__ . '/fixtures/ipv4Hosts.txt');
|
||||
}
|
||||
|
||||
public function testBasicIPV6() {
|
||||
$tests = [
|
||||
//Examples taken from http://en.wikipedia.org/wiki/IPv6_address
|
||||
['2001:db8::2:1', 'host1.example.com', Mode::INET6_ADDR, Mode::INET6_ADDR],
|
||||
['2001:db8:0:1:1:1:1:1', 'host2.example.com', Mode::INET6_ADDR, Mode::INET6_ADDR],
|
||||
['2001:db8::1:0:0:1', 'host3.example.com', Mode::INET6_ADDR, Mode::INET6_ADDR],
|
||||
|
||||
//Check that ipv6 is returned when both v4 and v6 are is set
|
||||
['2001:db8::2:1', 'host1.example.com', Mode::INET6_ADDR | Mode::INET4_ADDR, Mode::INET6_ADDR],
|
||||
['2001:db8:0:1:1:1:1:1', 'host2.example.com', Mode::INET6_ADDR | Mode::INET4_ADDR, Mode::INET6_ADDR],
|
||||
['2001:db8::1:0:0:1', 'host3.example.com', Mode::INET6_ADDR | Mode::INET4_ADDR, Mode::INET6_ADDR],
|
||||
|
||||
//Check that ipv6 is returned when ANY_* is set
|
||||
['2001:db8::2:1', 'host1.example.com', Mode::ANY_PREFER_INET4, Mode::INET6_ADDR],
|
||||
['2001:db8:0:1:1:1:1:1', 'host2.example.com', Mode::ANY_PREFER_INET4, Mode::INET6_ADDR],
|
||||
['2001:db8::1:0:0:1', 'host3.example.com', Mode::ANY_PREFER_INET4, Mode::INET6_ADDR],
|
||||
['2001:db8::2:1', 'host1.example.com', Mode::ANY_PREFER_INET6, Mode::INET6_ADDR],
|
||||
['2001:db8:0:1:1:1:1:1', 'host2.example.com', Mode::ANY_PREFER_INET6, Mode::INET6_ADDR],
|
||||
['2001:db8::1:0:0:1', 'host3.example.com', Mode::ANY_PREFER_INET6, Mode::INET6_ADDR],
|
||||
|
||||
//Check request for ipv4 returns null
|
||||
[null, 'host1.example.com', Mode::INET4_ADDR, null],
|
||||
|
||||
//Check non-existant domains return null
|
||||
[null, 'host4.example.com', Mode::INET6_ADDR, null],
|
||||
];
|
||||
|
||||
$this->runHostsFileTests($tests, __DIR__ . '/fixtures/ipv6Hosts.txt');
|
||||
}
|
||||
|
||||
public function testMixedIPVersions() {
|
||||
$tests = [
|
||||
//Examples taken from http://en.wikipedia.org/wiki/IPv6_address
|
||||
['2001:db8::2:1', 'host1.example.com', Mode::INET6_ADDR, Mode::INET6_ADDR],
|
||||
['2001:db8:0:1:1:1:1:1', 'host2.example.com', Mode::INET6_ADDR, Mode::INET6_ADDR],
|
||||
['2001:db8::1:0:0:1', 'host3.example.com', Mode::INET6_ADDR, Mode::INET6_ADDR],
|
||||
['192.168.1.1', 'host1.example.com', Mode::INET4_ADDR, Mode::INET4_ADDR],
|
||||
['192.168.1.2', 'host2.example.com', Mode::INET4_ADDR, Mode::INET4_ADDR],
|
||||
['192.168.1.4', 'host4.example.com', Mode::INET4_ADDR, Mode::INET4_ADDR],
|
||||
|
||||
//Check that v4 is returned by default
|
||||
['192.168.1.1', 'host1.example.com', Mode::INET4_ADDR | Mode::INET6_ADDR, Mode::INET4_ADDR],
|
||||
|
||||
//Check that the prefer inet6 works
|
||||
['2001:db8::2:1', 'host1.example.com', Mode::INET4_ADDR | Mode::INET6_ADDR | Mode::PREFER_INET6, Mode::INET6_ADDR],
|
||||
|
||||
//Check that the ANY_* works
|
||||
['192.168.1.1', 'host1.example.com', Mode::ANY_PREFER_INET4, Mode::INET4_ADDR],
|
||||
['2001:db8::2:1', 'host1.example.com', Mode::ANY_PREFER_INET6, Mode::INET6_ADDR],
|
||||
|
||||
//Check that a host that is only listed as ipv4 does not return a result for ipv6
|
||||
[null, 'host4.example.com', Mode::INET6_ADDR, null],
|
||||
|
||||
//Check that a host that is only listed as ipv6 does not return a result for ipv4
|
||||
[null, 'host3.example.com', Mode::INET4_ADDR, null],
|
||||
];
|
||||
|
||||
$this->runHostsFileTests($tests, __DIR__ . '/fixtures/mixedVersionHosts.txt');
|
||||
}
|
||||
|
||||
public function runHostsFileTests($tests, $hostsFile) {
|
||||
$nameValidator = new NameValidator;
|
||||
$hostsFile = new HostsFile($nameValidator, $hostsFile);
|
||||
|
||||
foreach ($tests as $i => $test) {
|
||||
list($expectedResult, $hostname, $inputAddrMode, $expectedAddrMode) = $test;
|
||||
|
||||
$result = $hostsFile->resolve($hostname, $inputAddrMode);
|
||||
|
||||
if ($expectedResult === null) {
|
||||
$this->assertNull($result);
|
||||
} else {
|
||||
list($resolvedAddr, $resolvedMode) = $result;
|
||||
|
||||
$this->assertEquals(
|
||||
$expectedAddrMode,
|
||||
$resolvedMode,
|
||||
"Failed to resolve $hostname to $expectedResult. " .
|
||||
"Expected `" . var_export($expectedAddrMode, true) . "` " .
|
||||
"but got `" . var_export($resolvedAddr, true) . "` " .
|
||||
" when running test #" . $i
|
||||
);
|
||||
|
||||
$this->assertEquals(
|
||||
$expectedResult,
|
||||
$resolvedAddr,
|
||||
"Failed to resolve $hostname to $expectedResult. " .
|
||||
"Expected `$expectedResult` but got `$resolvedAddr` " .
|
||||
" when running test #" . $i
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -2,170 +2,46 @@
|
||||
|
||||
namespace Amp\Dns\Test;
|
||||
|
||||
use Amp\NativeReactor;
|
||||
use Amp\Dns\Cache;
|
||||
use Amp\Dns\Client;
|
||||
use Amp\Dns\Resolver;
|
||||
use Amp\Dns\Cache\APCCache;
|
||||
use Amp\Dns\Cache\MemoryCache;
|
||||
use Amp\Dns\Cache\RedisCache;
|
||||
use Predis\Client as RedisClient;
|
||||
use Predis\Connection\ConnectionException as RedisConnectionException;
|
||||
|
||||
class IntegrationTest extends \PHPUnit_Framework_TestCase {
|
||||
private static $redisEnabled = true;
|
||||
private static $redisParameters = [
|
||||
'connection_timeout' => 2,
|
||||
'read_write_timeout' => 2,
|
||||
];
|
||||
|
||||
public static function setUpBeforeClass() {
|
||||
try {
|
||||
$predisClient = new RedisClient(self::$redisParameters, []);
|
||||
$predisClient->ping();
|
||||
//It's connected
|
||||
} catch (RedisConnectionException $rce) {
|
||||
self::$redisEnabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
public function testWithNullCache() {
|
||||
$this->basicRun(null);
|
||||
}
|
||||
|
||||
public function testWithMemoryCache() {
|
||||
$memoryCache = new MemoryCache();
|
||||
$this->basicRun($memoryCache);
|
||||
}
|
||||
|
||||
/**
|
||||
* @requires extension APC
|
||||
*/
|
||||
public function testWithApcCache() {
|
||||
$prefix = time().uniqid('CacheTest');
|
||||
$apcCache = new APCCache($prefix);
|
||||
$this->basicRun($apcCache);
|
||||
}
|
||||
|
||||
public function testWithRedisCache() {
|
||||
if (self::$redisEnabled != true) {
|
||||
$this->markTestSkipped("Could not connect to Redis, skipping test.");
|
||||
return;
|
||||
}
|
||||
|
||||
$prefix = time().'_'.uniqid('CacheTest');
|
||||
try {
|
||||
$redisClient = new RedisClient(self::$redisParameters, []);
|
||||
}
|
||||
catch (RedisConnectionException $rce) {
|
||||
$this->markTestIncomplete("Could not connect to Redis server, cannot test redis cache.");
|
||||
return;
|
||||
}
|
||||
|
||||
$redisCache = new RedisCache($redisClient, $prefix);
|
||||
$this->basicRun($redisCache);
|
||||
protected function setUp() {
|
||||
\Amp\reactor(\Amp\driver());
|
||||
}
|
||||
|
||||
/**
|
||||
* @group internet
|
||||
*/
|
||||
public function basicRun(Cache $cache = null) {
|
||||
public function testResolve() {
|
||||
\Amp\run(function () {
|
||||
$names = [
|
||||
'google.com',
|
||||
'github.com',
|
||||
'stackoverflow.com',
|
||||
'localhost',
|
||||
'192.168.0.1',
|
||||
'::1',
|
||||
"google.com",
|
||||
"github.com",
|
||||
"stackoverflow.com",
|
||||
"localhost",
|
||||
"192.168.0.1",
|
||||
"::1",
|
||||
];
|
||||
|
||||
$reactor = new NativeReactor;
|
||||
$client = new Client($reactor, null, null, $cache);
|
||||
$resolver = new Resolver($client);
|
||||
|
||||
$promises = [];
|
||||
foreach ($names as $name) {
|
||||
$promises[$name] = $resolver->resolve($name);
|
||||
}
|
||||
|
||||
$comboPromise = \Amp\all($promises);
|
||||
$results = \Amp\wait($comboPromise, $reactor);
|
||||
|
||||
foreach ($results as $name => $addrStruct) {
|
||||
list($addr, $type) = $addrStruct;
|
||||
$validIP = @inet_pton($addr);
|
||||
list($addr, $mode) = (yield \Amp\Dns\resolve($name));
|
||||
$inAddr = @\inet_pton($addr);
|
||||
$this->assertNotFalse(
|
||||
$validIP,
|
||||
$inAddr,
|
||||
"Server name $name did not resolve to a valid IP address"
|
||||
);
|
||||
if (isset($inAddr[15])) {
|
||||
$this->assertSame(
|
||||
\Amp\Dns\MODE_INET6,
|
||||
$mode,
|
||||
"Returned mode parameter did not match expected MODE_INET6"
|
||||
);
|
||||
} else {
|
||||
$this->assertSame(
|
||||
\Amp\Dns\MODE_INET4,
|
||||
$mode,
|
||||
"Returned mode parameter did not match expected MODE_INET4"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check that caches do actually cache results.
|
||||
*/
|
||||
function testCachingOfResults() {
|
||||
$memoryCache = new MemoryCache();
|
||||
|
||||
$namesFirstRun = [
|
||||
'google.com',
|
||||
'github.com',
|
||||
'google.com',
|
||||
'github.com',
|
||||
];
|
||||
|
||||
$namesSecondRun = [
|
||||
'google.com',
|
||||
'github.com',
|
||||
];
|
||||
|
||||
$setCount = count(array_unique(array_merge($namesFirstRun, $namesSecondRun)));
|
||||
$getCount = count($namesFirstRun) + count($namesSecondRun);
|
||||
|
||||
$mockedCache = \Mockery::mock($memoryCache);
|
||||
|
||||
/** @var $mockedCache \Mockery\Mock */
|
||||
|
||||
$mockedCache->shouldReceive('store')->times($setCount)->passthru();
|
||||
$mockedCache->shouldReceive('get')->times($getCount)->passthru();
|
||||
|
||||
$mockedCache->makePartial();
|
||||
|
||||
$reactor = new NativeReactor;
|
||||
$client = new Client($reactor, null, null, $mockedCache);
|
||||
$resolver = new Resolver($client);
|
||||
|
||||
$promises = [];
|
||||
foreach ($namesFirstRun as $name) {
|
||||
$promises[$name] = $resolver->resolve($name);
|
||||
}
|
||||
|
||||
$comboPromise = \Amp\all($promises);
|
||||
$results = \Amp\wait($comboPromise, $reactor);
|
||||
|
||||
foreach ($results as $name => $addrStruct) {
|
||||
list($addr, $type) = $addrStruct;
|
||||
$validIP = @inet_pton($addr);
|
||||
$this->assertNotFalse(
|
||||
$validIP,
|
||||
"Server name $name did not resolve to a valid IP address"
|
||||
);
|
||||
}
|
||||
|
||||
$promises = [];
|
||||
foreach ($namesSecondRun as $name) {
|
||||
$promises[$name] = $resolver->resolve($name);
|
||||
}
|
||||
|
||||
$comboPromise = \Amp\all($promises);
|
||||
$results = \Amp\wait($comboPromise, $reactor);
|
||||
foreach ($results as $name => $addrStruct) {
|
||||
list($addr, $type) = $addrStruct;
|
||||
$validIP = @inet_pton($addr);
|
||||
$this->assertNotFalse(
|
||||
$validIP,
|
||||
"Server name $name did not resolve to a valid IP address"
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -1,80 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Amp\Dns\Test;
|
||||
|
||||
use Amp\NativeReactor;
|
||||
use Amp\Dns\AddressModes;
|
||||
use Amp\Dns\Client;
|
||||
use Amp\Dns\HostsFile;
|
||||
use Amp\Dns\Resolver;
|
||||
use Amp\Dns\ResolutionErrors;
|
||||
|
||||
class ResolverTest extends \PHPUnit_Framework_TestCase {
|
||||
|
||||
private function createResolver($hostsFile = null) {
|
||||
$reactor = new NativeReactor;
|
||||
$hostsFile = new HostsFile(null, $hostsFile);
|
||||
$client = new Client($reactor);
|
||||
$resolver = new Resolver($client, $hostsFile);
|
||||
|
||||
return [$reactor, $resolver];
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException Amp\Dns\ResolutionException
|
||||
* @expectedErrorCode Amp\Dns\ResolutionErrors::ERR_INVALID_NAME
|
||||
*/
|
||||
public function testInvalidName() {
|
||||
list($reactor, $resolver) = $this->createResolver();
|
||||
|
||||
$alphabet = implode(range('a', 'z'));
|
||||
$tooLongName = $alphabet.$alphabet; //52
|
||||
$tooLongName = $tooLongName.$tooLongName; //104
|
||||
$tooLongName = $tooLongName.$tooLongName; //208
|
||||
$tooLongName = $tooLongName.$alphabet; //234
|
||||
$tooLongName = $tooLongName.$alphabet; //260
|
||||
|
||||
$promise = $resolver->resolve($tooLongName, AddressModes::PREFER_INET6);
|
||||
$addrStruct = \Amp\wait($promise, $reactor);
|
||||
}
|
||||
|
||||
/**
|
||||
* @group internet
|
||||
* @expectedException Amp\Dns\ResolutionException
|
||||
* @expectedErrorCode Amp\Dns\ResolutionErrors::ERR_NO_RECORD
|
||||
*/
|
||||
public function testUnknownName() {
|
||||
list($reactor, $resolver) = $this->createResolver();
|
||||
$promise = $resolver->resolve("doesntexist", AddressModes::PREFER_INET6);
|
||||
$addrStruct = \Amp\wait($promise, $reactor);
|
||||
}
|
||||
|
||||
public function testLocalHostResolution() {
|
||||
list($reactor, $resolver) = $this->createResolver();
|
||||
|
||||
$promise = $resolver->resolve("localhost", AddressModes::INET4_ADDR);
|
||||
list($addr, $type) = \Amp\wait($promise, $reactor);
|
||||
$this->assertSame('127.0.0.1', $addr);
|
||||
$this->assertSame(AddressModes::INET4_ADDR, $type, "Wrong result type - should be INET4_ADDR but got $type");
|
||||
|
||||
$promise = $resolver->resolve("localhost", AddressModes::PREFER_INET6);
|
||||
list($addr, $type) = \Amp\wait($promise, $reactor);
|
||||
$this->assertSame('::1', $addr);
|
||||
$this->assertSame(AddressModes::INET6_ADDR, $type, "Wrong result type - should be INET6_ADDR but got $type");
|
||||
}
|
||||
|
||||
public function testHostsFileResolution() {
|
||||
$hostsFile = __DIR__ . '/fixtures/resolverTest.txt';
|
||||
list($reactor, $resolver) = $this->createResolver($hostsFile);
|
||||
|
||||
$promise = $resolver->resolve("host1.example.com", AddressModes::INET4_ADDR);
|
||||
list($addr, $type) = \Amp\wait($promise, $reactor);
|
||||
$this->assertSame('192.168.1.1', $addr);
|
||||
$this->assertSame(AddressModes::INET4_ADDR, $type);
|
||||
|
||||
$promise = $resolver->resolve("resolvertest", AddressModes::INET4_ADDR);
|
||||
list($addr, $type) = \Amp\wait($promise, $reactor);
|
||||
$this->assertSame('192.168.1.3', $addr);
|
||||
$this->assertSame(AddressModes::INET4_ADDR, $type);
|
||||
}
|
||||
}
|
@ -1,300 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Amp\Dns\Test;
|
||||
|
||||
use Amp\Dns\AddressModes;
|
||||
use Amp\Dns\ResponseInterpreter;
|
||||
use LibDNS\Decoder\DecoderFactory;
|
||||
|
||||
class ResponseInterpreterTest extends \PHPUnit_Framework_TestCase {
|
||||
//Example packets below are taken from http://wiki.wireshark.org/SampleCaptures:
|
||||
|
||||
//"Standard query response","DNS","70","domain","32795","6"
|
||||
static private $standardQueryResponse = [
|
||||
0x49, 0xa1, 0x81, 0x80, 0x00, 0x01, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x06, 0x67, 0x6f, 0x6f,
|
||||
0x67, 0x6c, 0x65, 0x03, 0x63, 0x6f, 0x6d, 0x00,
|
||||
0x00, 0x1d, 0x00, 0x01
|
||||
];
|
||||
|
||||
//"Standard query response PTR 66-192-9-104.gen.twtelecom.net","DNS","129","domain","32795","8"
|
||||
static private $standardQueryResponsePTR = [
|
||||
0x9b, 0xbb, 0x81, 0x80, 0x00, 0x01, 0x00, 0x01,
|
||||
0x00, 0x00, 0x00, 0x00, 0x03, 0x31, 0x30, 0x34,
|
||||
0x01, 0x39, 0x03, 0x31, 0x39, 0x32, 0x02, 0x36,
|
||||
0x36, 0x07, 0x69, 0x6e, 0x2d, 0x61, 0x64, 0x64,
|
||||
0x72, 0x04, 0x61, 0x72, 0x70, 0x61, 0x00, 0x00,
|
||||
0x0c, 0x00, 0x01, 0xc0, 0x0c, 0x00, 0x0c, 0x00,
|
||||
0x01, 0x00, 0x01, 0x51, 0x25, 0x00, 0x20, 0x0c,
|
||||
0x36, 0x36, 0x2d, 0x31, 0x39, 0x32, 0x2d, 0x39,
|
||||
0x2d, 0x31, 0x30, 0x34, 0x03, 0x67, 0x65, 0x6e,
|
||||
0x09, 0x74, 0x77, 0x74, 0x65, 0x6c, 0x65, 0x63,
|
||||
0x6f, 0x6d, 0x03, 0x6e, 0x65, 0x74, 0x00
|
||||
];
|
||||
|
||||
//"Standard query response A 204.152.190.12","DNS","90","domain","32795","10"
|
||||
static private $standardQueryResponseA = [
|
||||
0x75, 0xc0, 0x81, 0x80, 0x00, 0x01, 0x00, 0x01,
|
||||
0x00, 0x00, 0x00, 0x00, 0x03, 0x77, 0x77, 0x77,
|
||||
0x06, 0x6e, 0x65, 0x74, 0x62, 0x73, 0x64, 0x03,
|
||||
0x6f, 0x72, 0x67, 0x00, 0x00, 0x01, 0x00, 0x01,
|
||||
0xc0, 0x0c, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01,
|
||||
0x40, 0xef, 0x00, 0x04, 0xcc, 0x98, 0xbe, 0x0c
|
||||
];
|
||||
|
||||
//"Standard query response AAAA 2001:4f8:4:7:2e0:81ff:fe52:9a6b","DNS","102","domain","32795","14"
|
||||
static private $standardQueryResponseIPV6 = [
|
||||
0x7f, 0x39, 0x81, 0x80, 0x00, 0x01, 0x00, 0x01,
|
||||
0x00, 0x00, 0x00, 0x00, 0x03, 0x77, 0x77, 0x77,
|
||||
0x06, 0x6e, 0x65, 0x74, 0x62, 0x73, 0x64, 0x03,
|
||||
0x6f, 0x72, 0x67, 0x00, 0x00, 0x1c, 0x00, 0x01,
|
||||
0xc0, 0x0c, 0x00, 0x1c, 0x00, 0x01, 0x00, 0x01,
|
||||
0x51, 0x44, 0x00, 0x10, 0x20, 0x01, 0x04, 0xf8,
|
||||
0x00, 0x04, 0x00, 0x07, 0x02, 0xe0, 0x81, 0xff,
|
||||
0xfe, 0x52, 0x9a, 0x6b
|
||||
];
|
||||
|
||||
//"Standard query response CNAME www.l.google.com","DNS","94","domain","32795","16"
|
||||
static private $standardQueryResponseCNAME = [
|
||||
0x8d, 0xb3, 0x81, 0x80, 0x00, 0x01, 0x00, 0x01,
|
||||
0x00, 0x00, 0x00, 0x00, 0x03, 0x77, 0x77, 0x77,
|
||||
0x06, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x03,
|
||||
0x63, 0x6f, 0x6d, 0x00, 0x00, 0x1c, 0x00, 0x01,
|
||||
0xc0, 0x0c, 0x00, 0x05, 0x00, 0x01, 0x00, 0x00,
|
||||
0x02, 0x79, 0x00, 0x08, 0x03, 0x77, 0x77, 0x77,
|
||||
0x01, 0x6c, 0xc0, 0x10
|
||||
];
|
||||
|
||||
//Standard query response, No such name DNS 79 domain 32795 22
|
||||
static private $standardQueryResponseNoSuchName = [
|
||||
0x26, 0x6d, 0x85, 0x83, 0x00, 0x01, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x03, 0x77, 0x77, 0x77,
|
||||
0x07, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65,
|
||||
0x07, 0x6e, 0x6f, 0x74, 0x67, 0x69, 0x6e, 0x68,
|
||||
0x00, 0x00, 0x1c, 0x00, 0x01
|
||||
];
|
||||
|
||||
//"Standard query response AAAA 2001:4f8:0:2::d A 204.152.184.88","DNS","115","domain","32795","24
|
||||
static private $standardQueryResponseMixed = [
|
||||
0xfe, 0xe3, 0x81, 0x80, 0x00, 0x01, 0x00, 0x02,
|
||||
0x00, 0x00, 0x00, 0x00, 0x03, 0x77, 0x77, 0x77,
|
||||
0x03, 0x69, 0x73, 0x63, 0x03, 0x6f, 0x72, 0x67,
|
||||
0x00, 0x00, 0xff, 0x00, 0x01, 0xc0, 0x0c, 0x00,
|
||||
0x1c, 0x00, 0x01, 0x00, 0x00, 0x02, 0x58, 0x00,
|
||||
0x10, 0x20, 0x01, 0x04, 0xf8, 0x00, 0x00, 0x00,
|
||||
0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x0d, 0xc0, 0x0c, 0x00, 0x01, 0x00, 0x01, 0x00,
|
||||
0x00, 0x02, 0x58, 0x00, 0x04, 0xcc, 0x98, 0xb8,
|
||||
0x58
|
||||
];
|
||||
|
||||
//Standard query response AAAA 2001:4f8:0:2::d A 204.152.184.88 DNS 115 domain 32795 24
|
||||
static private $multipleResponse = [
|
||||
0xfe, 0xe3, 0x81, 0x80, 0x00, 0x01, 0x00, 0x02,
|
||||
0x00, 0x00, 0x00, 0x00, 0x03, 0x77, 0x77, 0x77,
|
||||
0x03, 0x69, 0x73, 0x63, 0x03, 0x6f, 0x72, 0x67,
|
||||
0x00, 0x00, 0xff, 0x00, 0x01, 0xc0, 0x0c, 0x00,
|
||||
0x1c, 0x00, 0x01, 0x00, 0x00, 0x02, 0x58, 0x00,
|
||||
0x10, 0x20, 0x01, 0x04, 0xf8, 0x00, 0x00, 0x00,
|
||||
0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x0d, 0xc0, 0x0c, 0x00, 0x01, 0x00, 0x01, 0x00,
|
||||
0x00, 0x02, 0x58, 0x00, 0x04, 0xcc, 0x98, 0xb8,
|
||||
0x58
|
||||
];
|
||||
|
||||
static private $bbcNews = [
|
||||
//This should be equivalent to
|
||||
// ;; ANSWER SECTION:
|
||||
// news.bbc.co.uk. 655 IN CNAME newswww.bbc.net.uk.
|
||||
// newswww.bbc.net.uk. 174 IN A 212.58.246.82
|
||||
// newswww.bbc.net.uk. 174 IN A 212.58.246.83
|
||||
0xe1, 0x16, 0x81, 0x80, 0x00, 0x01, 0x00, 0x03,
|
||||
0x00, 0x00, 0x00, 0x00, 0x04, 0x6e, 0x65, 0x77,
|
||||
0x73, 0x03, 0x62, 0x62, 0x63, 0x02, 0x63, 0x6f,
|
||||
0x02, 0x75, 0x6b, 0x00, 0x00, 0x01, 0x00, 0x01,
|
||||
0xc0, 0x0c, 0x00, 0x05, 0x00, 0x01, 0x00, 0x00,
|
||||
0x02, 0x8f, 0x00, 0x12, 0x07, 0x6e, 0x65, 0x77,
|
||||
0x73, 0x77, 0x77, 0x77, 0x03, 0x62, 0x62, 0x63,
|
||||
0x03, 0x6e, 0x65, 0x74, 0xc0, 0x18, 0xc0, 0x2c,
|
||||
0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0xae,
|
||||
0x00, 0x04, 0xd4, 0x3a, 0xf6, 0x52, 0xc0, 0x2c,
|
||||
0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0xae,
|
||||
0x00, 0x04, 0xd4, 0x3a, 0xf6, 0x53
|
||||
];
|
||||
|
||||
public function getPacketString(array $bytes) {
|
||||
$packet = '';
|
||||
|
||||
foreach ($bytes as $byte) {
|
||||
$packet .= chr($byte);
|
||||
}
|
||||
|
||||
return $packet;
|
||||
}
|
||||
|
||||
public function testCatchesExceptionAndReturnsNull() {
|
||||
$decoder = \Mockery::mock('LibDNS\Decoder\Decoder');
|
||||
$decoder->shouldReceive('decode')->withAnyArgs()->andThrow("Exception", "Testing bad packet");
|
||||
$responseInterpreter = new ResponseInterpreter($decoder);
|
||||
$result = $responseInterpreter->decode("SomePacket");
|
||||
$this->assertNull($result);
|
||||
}
|
||||
|
||||
public function testInvalidMessage() {
|
||||
$message = \Mockery::mock('LibDNS\Messages\Message');
|
||||
$message->shouldReceive('getType')->once()->andReturn(\LibDNS\Messages\MessageTypes::QUERY);
|
||||
|
||||
$decoder = \Mockery::mock('LibDNS\Decoder\Decoder');
|
||||
$decoder->shouldReceive('decode')->once()->andReturn($message);
|
||||
|
||||
$responseInterpreter = new ResponseInterpreter($decoder);
|
||||
$result = $responseInterpreter->decode("SomePacket");
|
||||
$this->assertNull($result);
|
||||
}
|
||||
|
||||
public function testInvalidResponseCode() {
|
||||
$message = \Mockery::mock('LibDNS\Messages\Message');
|
||||
$message->shouldReceive('getType')->once()->andReturn(\LibDNS\Messages\MessageTypes::RESPONSE);
|
||||
$message->shouldReceive('getResponseCode')->once()->andReturn(42);
|
||||
|
||||
$decoder = \Mockery::mock('LibDNS\Decoder\Decoder');
|
||||
$decoder->shouldReceive('decode')->once()->andReturn($message);
|
||||
|
||||
$responseInterpreter = new ResponseInterpreter($decoder);
|
||||
$result = $responseInterpreter->decode("SomePacket");
|
||||
$this->assertNull($result);
|
||||
}
|
||||
|
||||
/**
|
||||
* @group CNAME
|
||||
*/
|
||||
public function testNewsBBC() {
|
||||
$testPacket = $this->getPacketString(self::$bbcNews);
|
||||
$decoder = (new DecoderFactory)->create();
|
||||
|
||||
$responseInterpreter = new ResponseInterpreter($decoder);
|
||||
$decoded = $responseInterpreter->decode($testPacket);
|
||||
|
||||
list($id, $response) = $decoded;
|
||||
|
||||
//Check the IPV4 result
|
||||
$interpreted = $responseInterpreter->interpret($response, AddressModes::INET4_ADDR);
|
||||
list($type, $addr, $ttl) = $interpreted;
|
||||
$this->assertEquals(AddressModes::INET4_ADDR, $type);
|
||||
//@TODO - this should be multiple - 212.58.246.82 and 212.58.246.83
|
||||
$this->assertSame("212.58.246.82", $addr);
|
||||
$this->assertSame(174, $ttl);
|
||||
|
||||
$interpreted = $responseInterpreter->interpret($response, AddressModes::INET6_ADDR);
|
||||
list($type, $addr, $ttl) = $interpreted;
|
||||
$this->assertEquals("newswww.bbc.net.uk", $addr);
|
||||
$this->assertEquals(AddressModes::CNAME, $type);
|
||||
$this->assertNull($ttl);
|
||||
}
|
||||
|
||||
|
||||
public function createResponseInterpreter() {
|
||||
$decoder = (new DecoderFactory)->create();
|
||||
$responseInterpreter = new ResponseInterpreter($decoder);
|
||||
|
||||
return $responseInterpreter;
|
||||
}
|
||||
|
||||
public function testNoResults() {
|
||||
$responseInterpreter = $this->createResponseInterpreter();
|
||||
$packet = $this->getPacketString(self::$standardQueryResponse);
|
||||
$decoded = $responseInterpreter->decode($packet);
|
||||
list($id, $message) = $decoded;
|
||||
$interpreted = $responseInterpreter->interpret($message, AddressModes::INET4_ADDR);
|
||||
$this->assertNull($interpreted);
|
||||
|
||||
$interpreted = $responseInterpreter->interpret($message, AddressModes::INET6_ADDR);
|
||||
$this->assertNull($interpreted);
|
||||
}
|
||||
|
||||
public function testNoSuchName() {
|
||||
$responseInterpreter = $this->createResponseInterpreter();
|
||||
$packet = $this->getPacketString(self::$standardQueryResponseNoSuchName);
|
||||
$decoded = $responseInterpreter->decode($packet);
|
||||
list($id, $message) = $decoded;
|
||||
$interpreted = $responseInterpreter->interpret($message, AddressModes::INET4_ADDR);
|
||||
$this->assertNull($interpreted, "Response with 'no such name' was not interpreted to null.");
|
||||
}
|
||||
|
||||
public function testMixed() {
|
||||
$responseInterpreter = $this->createResponseInterpreter();
|
||||
$packet = $this->getPacketString(self::$standardQueryResponseMixed);
|
||||
$decoded = $responseInterpreter->decode($packet);
|
||||
list($id, $message) = $decoded;
|
||||
|
||||
//Get the IPv4 part
|
||||
$interpreted = $responseInterpreter->interpret($message, AddressModes::INET4_ADDR);
|
||||
list($type, $addr, $ttl) = $interpreted;
|
||||
$this->assertEquals(AddressModes::INET4_ADDR, $type);
|
||||
$this->assertEquals('204.152.184.88', $addr);
|
||||
$this->assertEquals(600, $ttl);
|
||||
|
||||
//Get the IPv6 part
|
||||
$interpreted = $responseInterpreter->interpret($message, AddressModes::INET6_ADDR);
|
||||
list($type, $addr, $ttl) = $interpreted;
|
||||
$this->assertEquals(AddressModes::INET6_ADDR, $type);
|
||||
$this->assertEquals('2001:4f8:0:2::d', $addr);
|
||||
$this->assertEquals(600, $ttl);
|
||||
}
|
||||
|
||||
public function testIPv4() {
|
||||
$responseInterpreter = $this->createResponseInterpreter();
|
||||
$packet = $this->getPacketString(self::$standardQueryResponseA);
|
||||
$decoded = $responseInterpreter->decode($packet);
|
||||
list($id, $message) = $decoded;
|
||||
|
||||
//Get the IPv4 part
|
||||
$interpreted = $responseInterpreter->interpret($message, AddressModes::INET4_ADDR);
|
||||
list($type, $addr, $ttl) = $interpreted;
|
||||
|
||||
$this->assertEquals(AddressModes::INET4_ADDR, $type);
|
||||
$this->assertEquals('204.152.190.12', $addr);
|
||||
$this->assertEquals(82159, $ttl);
|
||||
|
||||
//Get the IPv6 part
|
||||
$interpreted = $responseInterpreter->interpret($message, AddressModes::INET6_ADDR);
|
||||
$this->assertNull($interpreted);
|
||||
}
|
||||
|
||||
public function testIPv6() {
|
||||
$responseInterpreter = $this->createResponseInterpreter();
|
||||
$packet = $this->getPacketString(self::$standardQueryResponseIPV6);
|
||||
$decoded = $responseInterpreter->decode($packet);
|
||||
list($id, $message) = $decoded;
|
||||
|
||||
//Get the IPv4 part
|
||||
$interpreted = $responseInterpreter->interpret($message, AddressModes::INET4_ADDR);
|
||||
$this->assertNull($interpreted);
|
||||
|
||||
//Get the IPv6 part
|
||||
$interpreted = $responseInterpreter->interpret($message, AddressModes::INET6_ADDR);
|
||||
list($type, $addr, $ttl) = $interpreted;
|
||||
$this->assertEquals(AddressModes::INET6_ADDR, $type);
|
||||
$this->assertEquals('2001:4f8:4:7:2e0:81ff:fe52:9a6b', $addr);
|
||||
$this->assertEquals(86340, $ttl);
|
||||
}
|
||||
|
||||
public function testCnameResponse() {
|
||||
$responseInterpreter = $this->createResponseInterpreter();
|
||||
$packet = $this->getPacketString(self::$standardQueryResponseCNAME);
|
||||
$decoded = $responseInterpreter->decode($packet);
|
||||
list($id, $message) = $decoded;
|
||||
|
||||
//Try to get an IPv4 answer - but actually get a CNAME
|
||||
$interpreted = $responseInterpreter->interpret($message, AddressModes::INET4_ADDR);
|
||||
list($type, $addr, $ttl) = $interpreted;
|
||||
$this->assertEquals(AddressModes::CNAME, $type);
|
||||
$this->assertEquals('www.l.google.com', $addr);
|
||||
|
||||
//Try to get an IPv6 answer - but actually get a CNAME
|
||||
$interpreted = $responseInterpreter->interpret($message, AddressModes::INET6_ADDR);
|
||||
list($type, $addr, $ttl) = $interpreted;
|
||||
$this->assertEquals(AddressModes::CNAME, $type);
|
||||
$this->assertEquals('www.l.google.com', $addr);
|
||||
}
|
||||
}
|
@ -2,8 +2,8 @@
|
||||
|
||||
error_reporting(E_ALL);
|
||||
|
||||
if (ini_get('opcache.enable') == true &&
|
||||
ini_get('opcache.save_comments') == false) {
|
||||
if (ini_get("opcache.enable") == true &&
|
||||
ini_get("opcache.save_comments") == false) {
|
||||
echo "Cannot run tests. OPCache is enabled and is stripping comments, which are required by PHPUnit to provide data for the tests.\n";
|
||||
exit(-1);
|
||||
}
|
||||
|
2
test/fixtures/ipv4Hosts.txt
vendored
2
test/fixtures/ipv4Hosts.txt
vendored
@ -1,2 +0,0 @@
|
||||
192.168.1.1 host1.example.com
|
||||
192.168.1.2 host2.example.com
|
3
test/fixtures/ipv6Hosts.txt
vendored
3
test/fixtures/ipv6Hosts.txt
vendored
@ -1,3 +0,0 @@
|
||||
2001:db8::2:1 host1.example.com
|
||||
2001:db8:0:1:1:1:1:1 host2.example.com
|
||||
2001:db8::1:0:0:1 host3.example.com
|
14
test/fixtures/mixedVersionHosts.txt
vendored
14
test/fixtures/mixedVersionHosts.txt
vendored
@ -1,14 +0,0 @@
|
||||
192.168.1.1 host1.example.com
|
||||
192.168.1.2 host2.example.com
|
||||
192.168.1.4 host4.example.com
|
||||
2001:db8::2:1 host1.example.com
|
||||
2001:db8:0:1:1:1:1:1 host2.example.com
|
||||
2001:db8::1:0:0:1 host3.example.com
|
||||
|
||||
#next line is empty for coverage
|
||||
|
||||
//empty_line_for_coverage
|
||||
|
||||
|
||||
#next line has invalid address for coverage
|
||||
1.2.300 doesntexist.com
|
8
test/fixtures/resolverTest.txt
vendored
8
test/fixtures/resolverTest.txt
vendored
@ -1,8 +0,0 @@
|
||||
192.168.1.1 host1.example.com
|
||||
192.168.1.2 host2.example.com
|
||||
192.168.1.3 resolvertest
|
||||
2001:db8::2:1 host1.example.com
|
||||
2001:db8:0:1:1:1:1:1 host2.example.com
|
||||
2001:db8::1:0:0:1 host3.example.com
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user