2017-06-23 07:34:11 +02:00
< ? php
namespace Amp\Dns ;
2017-06-23 09:24:26 +02:00
use Amp\Cache\ArrayCache ;
use Amp\Cache\Cache ;
2017-06-27 18:30:28 +02:00
use Amp\Dns\Internal\Socket ;
use Amp\Dns\Internal\TcpSocket ;
use Amp\Dns\Internal\UdpSocket ;
2017-06-23 19:13:28 +02:00
use Amp\Loop ;
2017-06-23 13:14:51 +02:00
use Amp\MultiReasonException ;
2017-06-23 07:34:11 +02:00
use Amp\Promise ;
2017-06-23 19:13:28 +02:00
use Amp\Success ;
2017-06-23 12:49:16 +02:00
use LibDNS\Messages\Message ;
2017-06-23 07:34:11 +02:00
use LibDNS\Records\Question ;
use LibDNS\Records\QuestionFactory ;
2017-06-23 13:14:51 +02:00
use function Amp\call ;
2017-06-23 07:34:11 +02:00
2019-03-13 17:24:25 +01:00
final class Rfc1035StubResolver implements Resolver
2019-01-25 02:27:47 +01:00
{
2017-06-23 09:24:26 +02:00
const CACHE_PREFIX = " amphp.dns. " ;
2019-07-08 21:58:53 +02:00
const CONFIG_NOT_LOADED = 0 ;
const CONFIG_LOADED = 1 ;
const CONFIG_FAILED = 2 ;
2017-06-23 09:24:26 +02:00
2017-06-23 07:34:11 +02:00
/** @var \Amp\Dns\ConfigLoader */
private $configLoader ;
/** @var \LibDNS\Records\QuestionFactory */
private $questionFactory ;
/** @var \Amp\Dns\Config|null */
private $config ;
2019-07-08 21:58:53 +02:00
/** @var int */
private $configStatus = self :: CONFIG_NOT_LOADED ;
2017-07-13 21:02:59 +02:00
/** @var Promise|null */
private $pendingConfig ;
2017-06-23 09:24:26 +02:00
/** @var Cache */
private $cache ;
2017-06-27 17:47:57 +02:00
/** @var Socket[] */
private $sockets = [];
2017-06-23 12:49:16 +02:00
2017-06-23 19:13:28 +02:00
/** @var Promise[] */
2017-06-27 17:47:57 +02:00
private $pendingSockets = [];
2017-06-23 19:13:28 +02:00
/** @var Promise[] */
2017-06-23 17:48:03 +02:00
private $pendingQueries = [];
2017-06-23 19:13:28 +02:00
/** @var string */
private $gcWatcher ;
2019-01-25 02:27:47 +01:00
public function __construct ( Cache $cache = null , ConfigLoader $configLoader = null )
{
2017-12-16 19:59:47 +01:00
$this -> cache = $cache ? ? new ArrayCache ( 5000 /* default gc interval */ , 256 /* size */ );
2017-08-09 18:27:17 +02:00
$this -> configLoader = $configLoader ? ? ( \stripos ( PHP_OS , " win " ) === 0
2017-06-23 07:34:11 +02:00
? new WindowsConfigLoader
2017-08-09 18:27:17 +02:00
: new UnixConfigLoader );
2017-06-23 07:34:11 +02:00
$this -> questionFactory = new QuestionFactory ;
2017-06-23 19:13:28 +02:00
2017-11-07 16:18:49 +01:00
$sockets = & $this -> sockets ;
$this -> gcWatcher = Loop :: repeat ( 5000 , static function () use ( & $sockets ) {
if ( ! $sockets ) {
2017-06-27 17:35:53 +02:00
return ;
}
2017-06-23 19:13:28 +02:00
$now = \time ();
2017-11-07 16:18:49 +01:00
foreach ( $sockets as $key => $server ) {
2017-06-23 19:13:28 +02:00
if ( $server -> getLastActivity () < $now - 60 ) {
2017-06-23 21:07:59 +02:00
$server -> close ();
2017-11-07 16:18:49 +01:00
unset ( $sockets [ $key ]);
2017-06-23 19:13:28 +02:00
}
}
});
Loop :: unreference ( $this -> gcWatcher );
}
2019-01-25 02:27:47 +01:00
public function __destruct ()
{
2017-06-23 19:13:28 +02:00
Loop :: cancel ( $this -> gcWatcher );
2017-06-23 07:34:11 +02:00
}
2017-06-23 09:24:26 +02:00
/** @inheritdoc */
2019-01-25 02:27:47 +01:00
public function resolve ( string $name , int $typeRestriction = null ) : Promise
{
2017-06-23 13:14:51 +02:00
if ( $typeRestriction !== null && $typeRestriction !== Record :: A && $typeRestriction !== Record :: AAAA ) {
throw new \Error ( " Invalid value for parameter 2: null|Record::A|Record::AAAA expected " );
}
return call ( function () use ( $name , $typeRestriction ) {
2019-07-08 21:58:53 +02:00
if ( $this -> configStatus === self :: CONFIG_NOT_LOADED ) {
2017-07-13 21:02:59 +02:00
yield $this -> reloadConfig ();
2017-06-23 13:14:51 +02:00
}
2019-07-08 22:13:21 +02:00
2019-07-08 21:58:53 +02:00
if ( $this -> configStatus === self :: CONFIG_FAILED ) {
2019-07-08 22:13:21 +02:00
if ( ! \in_array ( $typeRestriction , [ Record :: A , null ], true )) {
throw new DnsException ( " Query for ' { $name } ' failed, because loading the system's DNS configuration failed and querying records other than A records isn't supported in blocking fallback mode. " );
2019-07-08 21:58:53 +02:00
}
2019-07-08 22:13:21 +02:00
2019-07-08 21:58:53 +02:00
$result = \gethostbynamel ( $name );
if ( $result === false ) {
2019-07-08 22:13:21 +02:00
throw new DnsException ( " Query for ' { $name } ' failed, because loading the system's DNS configuration failed and blocking fallback via gethostbynamel() failed, too. " );
}
if ( $result === []) {
throw new NoRecordException ( " No records returned for ' { $name } ' using blocking fallback mode. " );
2019-07-08 21:58:53 +02:00
}
2019-07-08 22:13:21 +02:00
$records = [];
foreach ( $result as $record ) {
$records [] = new Record ( $record , Record :: A , null );
2019-07-08 21:58:53 +02:00
}
2019-07-08 22:13:21 +02:00
return $records ;
2019-07-08 21:58:53 +02:00
}
2017-06-23 13:14:51 +02:00
2018-03-31 17:56:32 +02:00
switch ( $typeRestriction ) {
case Record :: A :
2019-01-25 02:27:47 +01:00
if ( \filter_var ( $name , FILTER_VALIDATE_IP , FILTER_FLAG_IPV4 )) {
2018-03-31 17:56:32 +02:00
return [ new Record ( $name , Record :: A , null )];
2019-01-25 02:27:47 +01:00
} elseif ( \filter_var ( $name , FILTER_VALIDATE_IP , FILTER_FLAG_IPV6 )) {
2019-01-04 18:20:52 +01:00
throw new DnsException ( " Got an IPv6 address, but type is restricted to IPv4 " );
2017-06-23 13:39:20 +02:00
}
2018-03-31 17:56:32 +02:00
break ;
case Record :: AAAA :
2019-01-25 02:27:47 +01:00
if ( \filter_var ( $name , FILTER_VALIDATE_IP , FILTER_FLAG_IPV6 )) {
2018-03-31 17:56:32 +02:00
return [ new Record ( $name , Record :: AAAA , null )];
2019-01-25 02:27:47 +01:00
} elseif ( \filter_var ( $name , FILTER_VALIDATE_IP , FILTER_FLAG_IPV4 )) {
2019-01-04 18:20:52 +01:00
throw new DnsException ( " Got an IPv4 address, but type is restricted to IPv6 " );
2017-06-23 13:39:20 +02:00
}
2018-03-31 17:56:32 +02:00
break ;
default :
2019-01-25 02:27:47 +01:00
if ( \filter_var ( $name , FILTER_VALIDATE_IP , FILTER_FLAG_IPV4 )) {
2018-03-31 17:56:32 +02:00
return [ new Record ( $name , Record :: A , null )];
2019-01-25 02:27:47 +01:00
} elseif ( \filter_var ( $name , FILTER_VALIDATE_IP , FILTER_FLAG_IPV6 )) {
2018-03-31 17:56:32 +02:00
return [ new Record ( $name , Record :: AAAA , null )];
}
break ;
2017-06-23 13:14:51 +02:00
}
2019-01-04 18:20:52 +01:00
$name = normalizeName ( $name );
2017-06-23 13:14:51 +02:00
if ( $records = $this -> queryHosts ( $name , $typeRestriction )) {
return $records ;
}
2019-03-13 17:18:23 +01:00
// Follow RFC 6761 and never send queries for localhost to the caching DNS server
// Usually, these queries are already resolved via queryHosts()
if ( $name === 'localhost' ) {
return $typeRestriction === Record :: AAAA
? [ new Record ( '::1' , Record :: AAAA , null )]
: [ new Record ( '127.0.0.1' , Record :: A , null )];
}
2017-06-23 14:36:46 +02:00
for ( $redirects = 0 ; $redirects < 5 ; $redirects ++ ) {
2017-06-23 14:11:11 +02:00
try {
if ( $typeRestriction ) {
$records = yield $this -> query ( $name , $typeRestriction );
} else {
try {
list (, $records ) = yield Promise\some ([
$this -> query ( $name , Record :: A ),
$this -> query ( $name , Record :: AAAA ),
]);
2017-06-23 18:35:48 +02:00
$records = \array_merge ( ... $records );
2017-09-12 17:08:38 +02:00
break ; // Break redirect loop, otherwise we query the same records 5 times
2017-06-23 14:11:11 +02:00
} catch ( MultiReasonException $e ) {
2018-05-01 19:55:02 +02:00
$errors = [];
2017-06-23 14:11:11 +02:00
foreach ( $e -> getReasons () as $reason ) {
if ( $reason instanceof NoRecordException ) {
throw $reason ;
}
2018-05-01 19:55:02 +02:00
$errors [] = $reason -> getMessage ();
2017-06-23 14:11:11 +02:00
}
2019-07-08 22:13:21 +02:00
throw new DnsException ( " All query attempts failed for { $name } : " . \implode ( " , " , $errors ),
0 , $e );
2017-06-23 14:11:11 +02:00
}
}
} catch ( NoRecordException $e ) {
try {
/** @var Record[] $cnameRecords */
$cnameRecords = yield $this -> query ( $name , Record :: CNAME );
$name = $cnameRecords [ 0 ] -> getValue ();
continue ;
} catch ( NoRecordException $e ) {
/** @var Record[] $dnameRecords */
$dnameRecords = yield $this -> query ( $name , Record :: DNAME );
$name = $dnameRecords [ 0 ] -> getValue ();
continue ;
}
}
2017-06-23 13:14:51 +02:00
}
2017-06-23 18:35:48 +02:00
return $records ;
2017-06-23 13:14:51 +02:00
});
}
2019-03-13 17:18:23 +01:00
/**
* Reloads the configuration in the background .
*
* Once it ' s finished , the configuration will be used for new requests .
*
* @ return Promise
*/
public function reloadConfig () : Promise
{
if ( $this -> pendingConfig ) {
return $this -> pendingConfig ;
}
$promise = call ( function () {
2019-07-08 21:58:53 +02:00
try {
$this -> config = yield $this -> configLoader -> loadConfig ();
$this -> configStatus = self :: CONFIG_LOADED ;
} catch ( ConfigException $e ) {
$this -> configStatus = self :: CONFIG_FAILED ;
2019-07-08 22:13:21 +02:00
try {
\trigger_error ( " Could not load the system's DNS configuration, using synchronous, blocking fallback " ,
\E_USER_WARNING );
} catch ( \Throwable $triggerException ) {
\set_error_handler ( null );
\trigger_error ( " Could not load the system's DNS configuration, using synchronous, blocking fallback " ,
\E_USER_WARNING );
\restore_error_handler ();
}
2019-07-08 21:58:53 +02:00
}
2019-03-13 17:18:23 +01:00
});
$this -> pendingConfig = $promise ;
$promise -> onResolve ( function () {
$this -> pendingConfig = null ;
});
return $promise ;
}
2017-06-23 09:24:26 +02:00
/** @inheritdoc */
2019-01-25 02:27:47 +01:00
public function query ( string $name , int $type ) : Promise
{
2017-09-12 17:05:40 +02:00
$pendingQueryKey = $type . " " . $name ;
if ( isset ( $this -> pendingQueries [ $pendingQueryKey ])) {
return $this -> pendingQueries [ $pendingQueryKey ];
2017-06-23 17:48:03 +02:00
}
$promise = call ( function () use ( $name , $type ) {
2019-07-08 21:58:53 +02:00
if ( $this -> configStatus === self :: CONFIG_NOT_LOADED ) {
2017-07-13 21:02:59 +02:00
yield $this -> reloadConfig ();
2017-06-23 17:06:30 +02:00
}
2019-07-08 21:58:53 +02:00
if ( $this -> configStatus === self :: CONFIG_FAILED ) {
if ( $type !== Record :: A ) {
2019-07-08 22:13:21 +02:00
throw new DnsException ( " Query for ' { $name } ' failed, because loading the system's DNS configuration failed and querying records other than A records isn't supported in blocking fallback mode. " );
2019-07-08 21:58:53 +02:00
}
2019-07-08 22:13:21 +02:00
2019-07-08 21:58:53 +02:00
$result = \gethostbynamel ( $name );
2019-07-08 22:13:21 +02:00
if ( $result === false ) {
throw new DnsException ( " Query for ' { $name } ' failed, because loading the system's DNS configuration failed and blocking fallback via gethostbynamel() failed, too. " );
2019-07-08 21:58:53 +02:00
}
2019-07-08 22:13:21 +02:00
if ( $result === []) {
throw new NoRecordException ( " No records returned for ' { $name } ' using blocking fallback mode. " );
}
$records = [];
foreach ( $result as $record ) {
$records [] = new Record ( $record , Record :: A , null );
2019-07-08 21:58:53 +02:00
}
2019-07-08 22:13:21 +02:00
return $records ;
2019-07-08 21:58:53 +02:00
}
2017-06-23 07:34:11 +02:00
2017-06-23 17:06:30 +02:00
$name = $this -> normalizeName ( $name , $type );
$question = $this -> createQuestion ( $name , $type );
2017-06-23 07:34:11 +02:00
2017-06-23 17:06:30 +02:00
if ( null !== $cachedValue = yield $this -> cache -> get ( $this -> getCacheKey ( $name , $type ))) {
return $this -> decodeCachedResult ( $name , $type , $cachedValue );
}
2017-06-23 07:34:11 +02:00
2017-06-23 17:06:30 +02:00
$nameservers = $this -> config -> getNameservers ();
$attempts = $this -> config -> getAttempts ();
2017-06-27 17:35:53 +02:00
$protocol = " udp " ;
$attempt = 0 ;
2017-06-23 09:24:26 +02:00
2017-06-27 17:47:57 +02:00
/** @var Socket $socket */
2017-06-27 17:35:53 +02:00
$uri = $protocol . " :// " . $nameservers [ 0 ];
2017-06-27 17:47:57 +02:00
$socket = yield $this -> getSocket ( $uri );
2017-06-23 07:34:11 +02:00
2018-05-01 19:55:02 +02:00
$attemptDescription = [];
2017-06-27 17:35:53 +02:00
while ( $attempt < $attempts ) {
2017-06-23 20:51:50 +02:00
try {
2017-06-27 17:47:57 +02:00
if ( ! $socket -> isAlive ()) {
unset ( $this -> sockets [ $uri ]);
$socket -> close ();
2017-06-23 07:34:11 +02:00
2017-06-27 18:30:28 +02:00
/** @var Socket $server */
2017-06-27 17:35:53 +02:00
$i = $attempt % \count ( $nameservers );
2018-05-01 19:55:02 +02:00
$uri = $protocol . " :// " . $nameservers [ $i ];
$socket = yield $this -> getSocket ( $uri );
2017-06-23 17:06:30 +02:00
}
2017-06-23 07:34:11 +02:00
2018-05-01 19:55:02 +02:00
$attemptDescription [] = $uri ;
2017-06-23 20:51:50 +02:00
/** @var Message $response */
2017-06-27 17:47:57 +02:00
$response = yield $socket -> ask ( $question , $this -> config -> getTimeout ());
2017-06-23 20:51:50 +02:00
$this -> assertAcceptableResponse ( $response );
2017-06-23 12:49:16 +02:00
2017-11-07 09:41:30 +01:00
// UDP sockets are never reused, they're not in the $this->sockets map
if ( $protocol === " udp " ) {
// Defer call, because it interferes with the unreference() call in Internal\Socket otherwise
Loop :: defer ( function () use ( $socket ) {
$socket -> close ();
});
}
2017-06-23 20:51:50 +02:00
if ( $response -> isTruncated ()) {
2017-06-27 17:35:53 +02:00
if ( $protocol !== " tcp " ) {
2017-06-23 20:51:50 +02:00
// Retry with TCP, don't count attempt
2017-06-27 17:35:53 +02:00
$protocol = " tcp " ;
$i = $attempt % \count ( $nameservers );
2018-05-01 19:55:02 +02:00
$uri = $protocol . " :// " . $nameservers [ $i ];
$socket = yield $this -> getSocket ( $uri );
2017-06-23 20:51:50 +02:00
continue ;
}
2017-06-23 09:24:26 +02:00
2019-01-04 18:20:52 +01:00
throw new DnsException ( " Server returned a truncated response for ' { $name } ' ( " . Record :: getName ( $type ) . " ) " );
2017-06-23 20:51:50 +02:00
}
2017-06-23 09:24:26 +02:00
2017-06-23 20:51:50 +02:00
$answers = $response -> getAnswerRecords ();
$result = [];
$ttls = [];
2017-06-23 09:24:26 +02:00
2017-06-23 20:51:50 +02:00
/** @var \LibDNS\Records\Resource $record */
foreach ( $answers as $record ) {
$recordType = $record -> getType ();
$result [ $recordType ][] = ( string ) $record -> getData ();
2017-06-25 21:00:30 +02:00
// Cache for max one day
$ttls [ $recordType ] = \min ( $ttls [ $recordType ] ? ? 86400 , $record -> getTTL ());
2017-06-23 20:51:50 +02:00
}
foreach ( $result as $recordType => $records ) {
// We don't care here whether storing in the cache fails
2019-07-08 22:13:21 +02:00
$this -> cache -> set ( $this -> getCacheKey ( $name , $recordType ), \json_encode ( $records ),
$ttls [ $recordType ]);
2017-06-23 20:51:50 +02:00
}
if ( ! isset ( $result [ $type ])) {
// "it MUST NOT cache it for longer than five (5) minutes" per RFC 2308 section 7.1
$this -> cache -> set ( $this -> getCacheKey ( $name , $type ), \json_encode ([]), 300 );
2018-05-01 19:55:02 +02:00
throw new NoRecordException ( " No records returned for ' { $name } ' ( " . Record :: getName ( $type ) . " ) " );
2017-06-23 20:51:50 +02:00
}
2017-06-23 09:24:26 +02:00
2017-06-23 20:51:50 +02:00
return \array_map ( function ( $data ) use ( $type , $ttls ) {
return new Record ( $data , $type , $ttls [ $type ]);
}, $result [ $type ]);
} catch ( TimeoutException $e ) {
2017-11-07 09:41:30 +01:00
// Defer call, because it might interfere with the unreference() call in Internal\Socket otherwise
Loop :: defer ( function () use ( $socket , $uri ) {
unset ( $this -> sockets [ $uri ]);
$socket -> close ();
});
2017-06-27 17:35:53 +02:00
$i = ++ $attempt % \count ( $nameservers );
2018-05-01 19:55:02 +02:00
$uri = $protocol . " :// " . $nameservers [ $i ];
$socket = yield $this -> getSocket ( $uri );
2017-06-27 17:35:53 +02:00
2017-06-23 20:51:50 +02:00
continue ;
}
2017-06-23 07:34:11 +02:00
}
2017-06-23 09:24:26 +02:00
2018-05-01 19:55:02 +02:00
throw new TimeoutException ( \sprintf (
" No response for '%s' (%s) from any nameserver after %d attempts, tried %s " ,
$name ,
Record :: getName ( $type ),
$attempts ,
\implode ( " , " , $attemptDescription )
));
2017-06-23 17:06:30 +02:00
});
2017-06-23 17:48:03 +02:00
$this -> pendingQueries [ $type . " " . $name ] = $promise ;
$promise -> onResolve ( function () use ( $name , $type ) {
unset ( $this -> pendingQueries [ $type . " " . $name ]);
});
return $promise ;
2017-06-23 07:34:11 +02:00
}
2019-07-08 22:13:21 +02:00
private function queryHosts ( string $name , int $typeRestriction = null ) : array
{
$hosts = $this -> config -> getKnownHosts ();
$records = [];
$returnIPv4 = $typeRestriction === null || $typeRestriction === Record :: A ;
$returnIPv6 = $typeRestriction === null || $typeRestriction === Record :: AAAA ;
if ( $returnIPv4 && isset ( $hosts [ Record :: A ][ $name ])) {
$records [] = new Record ( $hosts [ Record :: A ][ $name ], Record :: A , null );
}
if ( $returnIPv6 && isset ( $hosts [ Record :: AAAA ][ $name ])) {
$records [] = new Record ( $hosts [ Record :: AAAA ][ $name ], Record :: AAAA , null );
}
return $records ;
}
2019-03-13 17:18:23 +01:00
private function normalizeName ( string $name , int $type )
2019-01-25 02:27:47 +01:00
{
2019-03-13 17:18:23 +01:00
if ( $type === Record :: PTR ) {
if (( $packedIp = @ \inet_pton ( $name )) !== false ) {
if ( isset ( $packedIp [ 4 ])) { // IPv6
$name = \wordwrap ( \strrev ( \bin2hex ( $packedIp )), 1 , " . " , true ) . " .ip6.arpa " ;
} else { // IPv4
$name = \inet_ntop ( \strrev ( $packedIp )) . " .in-addr.arpa " ;
}
}
} elseif ( \in_array ( $type , [ Record :: A , Record :: AAAA ])) {
$name = normalizeName ( $name );
2017-07-13 21:02:59 +02:00
}
2019-03-13 17:18:23 +01:00
return $name ;
2017-06-23 13:32:04 +02:00
}
2017-06-23 07:34:11 +02:00
/**
* @ param string $name
2017-06-23 09:24:26 +02:00
* @ param int $type
2017-06-23 07:34:11 +02:00
*
* @ return \LibDNS\Records\Question
*/
2019-01-25 02:27:47 +01:00
private function createQuestion ( string $name , int $type ) : Question
{
2017-06-23 07:34:11 +02:00
if ( 0 > $type || 0xffff < $type ) {
2017-06-23 09:24:26 +02:00
$message = \sprintf ( '%d does not correspond to a valid record type (must be between 0 and 65535).' , $type );
throw new \Error ( $message );
2017-06-23 07:34:11 +02:00
}
2017-06-23 09:24:26 +02:00
2017-06-23 07:34:11 +02:00
$question = $this -> questionFactory -> create ( $type );
$question -> setName ( $name );
2017-06-23 09:24:26 +02:00
2017-06-23 07:34:11 +02:00
return $question ;
}
2017-06-23 09:24:26 +02:00
2019-01-25 02:27:47 +01:00
private function getCacheKey ( string $name , int $type ) : string
{
2017-06-23 09:24:26 +02:00
return self :: CACHE_PREFIX . $name . " # " . $type ;
}
2019-06-10 16:01:51 +02:00
private function decodeCachedResult ( string $name , int $type , string $encoded )
2019-01-25 02:27:47 +01:00
{
2017-06-23 09:24:26 +02:00
$decoded = \json_decode ( $encoded , true );
if ( ! $decoded ) {
throw new NoRecordException ( " No records returned for { $name } (cached result) " );
}
$result = [];
foreach ( $decoded as $data ) {
$result [] = new Record ( $data , $type );
}
return $result ;
}
2019-01-25 02:27:47 +01:00
private function getSocket ( $uri ) : Promise
{
2017-06-27 18:35:57 +02:00
// We use a new socket for each UDP request, as that increases the entropy and mitigates response forgery.
2017-06-27 17:35:53 +02:00
if ( \substr ( $uri , 0 , 3 ) === " udp " ) {
2017-06-27 17:47:57 +02:00
return UdpSocket :: connect ( $uri );
2017-06-27 17:35:53 +02:00
}
2017-06-27 18:35:57 +02:00
// Over TCP we might reuse sockets if the server allows to keep them open. Sequence IDs in TCP are already
// better than a random port. Additionally, a TCP connection is more expensive.
2017-06-27 17:47:57 +02:00
if ( isset ( $this -> sockets [ $uri ])) {
return new Success ( $this -> sockets [ $uri ]);
2017-06-23 19:13:28 +02:00
}
2017-06-27 17:47:57 +02:00
if ( isset ( $this -> pendingSockets [ $uri ])) {
return $this -> pendingSockets [ $uri ];
2017-06-23 12:49:16 +02:00
}
2017-06-27 17:47:57 +02:00
$server = TcpSocket :: connect ( $uri );
2017-06-23 12:49:16 +02:00
2017-06-23 19:13:28 +02:00
$server -> onResolve ( function ( $error , $server ) use ( $uri ) {
2017-06-27 17:47:57 +02:00
unset ( $this -> pendingSockets [ $uri ]);
2017-06-27 17:35:53 +02:00
if ( ! $error ) {
2017-06-27 17:47:57 +02:00
$this -> sockets [ $uri ] = $server ;
2017-06-23 12:49:16 +02:00
}
});
return $server ;
}
2019-01-25 02:27:47 +01:00
private function assertAcceptableResponse ( Message $response )
{
2017-06-23 12:49:16 +02:00
if ( $response -> getResponseCode () !== 0 ) {
2019-01-04 18:20:52 +01:00
throw new DnsException ( \sprintf ( " Server returned error code: %d " , $response -> getResponseCode ()));
2017-06-23 12:49:16 +02:00
}
}
2017-06-23 09:24:26 +02:00
}