2019-07-08 22:22:43 +02:00
< ? php
namespace Amp\Dns ;
use Amp\Failure ;
use Amp\Promise ;
use Amp\Success ;
2019-07-15 15:54:38 +02:00
use danog\LibDNSNative\NativeDecoderFactory ;
use danog\LibDNSNative\NativeEncoderFactory ;
use LibDNS\Messages\MessageFactory ;
use LibDNS\Messages\MessageTypes ;
use LibDNS\Records\Question ;
use LibDNS\Records\QuestionFactory ;
use function Amp\call ;
2019-07-08 22:22:43 +02:00
class BlockingFallbackResolver implements Resolver
{
2019-07-15 15:54:38 +02:00
/** @var QuestionFactory */
private $questionFactory ;
/** @var MessageFactory */
private $messageFactory ;
/** @var NativeEncoderFactory */
private $encoderFactory ;
/** @var NativeDecoderFactory */
private $decoderFactory ;
/**
* Constructor function .
*/
public function __construct ()
{
$this -> questionFactory = new QuestionFactory ;
$this -> messageFactory = new MessageFactory ;
$this -> encoderFactory = new NativeEncoderFactory ;
$this -> decoderFactory = new NativeDecoderFactory ;
}
2019-07-08 22:22:43 +02:00
public function resolve ( string $name , int $typeRestriction = null ) : Promise
{
2019-07-15 15:54:38 +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 " );
}
switch ( $typeRestriction ) {
case Record :: A :
if ( \filter_var ( $name , FILTER_VALIDATE_IP , FILTER_FLAG_IPV4 )) {
return new Success ([ new Record ( $name , Record :: A , null )]);
}
if ( \filter_var ( $name , FILTER_VALIDATE_IP , FILTER_FLAG_IPV6 )) {
return new Failure ( new DnsException ( " Got an IPv6 address, but type is restricted to IPv4 " ));
}
break ;
case Record :: AAAA :
if ( \filter_var ( $name , FILTER_VALIDATE_IP , FILTER_FLAG_IPV6 )) {
return new Success ([ new Record ( $name , Record :: AAAA , null )]);
}
if ( \filter_var ( $name , FILTER_VALIDATE_IP , FILTER_FLAG_IPV4 )) {
return new Failure ( new DnsException ( " Got an IPv4 address, but type is restricted to IPv6 " ));
}
break ;
default :
if ( \filter_var ( $name , FILTER_VALIDATE_IP , FILTER_FLAG_IPV4 )) {
return new Success ([ new Record ( $name , Record :: A , null )]);
}
if ( \filter_var ( $name , FILTER_VALIDATE_IP , FILTER_FLAG_IPV6 )) {
return new Success ([ new Record ( $name , Record :: AAAA , null )]);
}
break ;
}
$name = normalizeName ( $name );
// 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 new Success ( $typeRestriction === Record :: AAAA
? [ new Record ( '::1' , Record :: AAAA , null )]
: [ new Record ( '127.0.0.1' , Record :: A , null )]);
2019-07-08 22:22:43 +02:00
}
2019-07-15 15:54:38 +02:00
return call ( function () use ( $name , $typeRestriction ) {
if ( $typeRestriction ) {
return yield $this -> query ( $name , $typeRestriction );
}
list (, $records ) = yield Promise\some ([
$this -> query ( $name , Record :: A ),
$this -> query ( $name , Record :: AAAA ),
]);
return \array_merge ( ... $records );
});
2019-07-08 22:22:43 +02:00
}
public function query ( string $name , int $type ) : Promise
{
2019-07-15 15:54:38 +02:00
$name = $this -> normalizeName ( $name , $type );
$question = $this -> createQuestion ( $name , $type );
$request = $this -> messageFactory -> create ( MessageTypes :: QUERY );
$request -> getQuestionRecords () -> add ( $question );
2019-07-08 22:22:43 +02:00
2019-07-15 15:54:38 +02:00
$encoder = $this -> encoderFactory -> create ();
$question = $encoder -> encode ( $request );
$result = \dns_get_record ( ... $question );
2019-07-08 22:22:43 +02:00
if ( $result === false ) {
2019-07-15 15:54:38 +02:00
return new Failure ( new DnsException ( " Query for ' { $name } ' failed, because loading the system's DNS configuration failed and blocking fallback via dns_get_record() failed, too. " ));
2019-07-08 22:22:43 +02:00
}
2019-07-15 15:54:38 +02:00
$decoder = $this -> decoderFactory -> create ();
$result = $decoder -> decode ( $result , ... $question );
if ( $result -> isTruncated ()) {
return new Failure ( new DnsException ( " Query for ' { $name } ' failed, because loading the system's DNS configuration failed and blocking fallback via dns_get_record() returned a truncated response for ' { $name } ' ( " . Record :: getName ( $type ) . " ) " ));
2019-07-08 22:22:43 +02:00
}
2019-07-15 15:54:38 +02:00
$answers = $result -> getAnswerRecords ();
$result = [];
$ttls = [];
2019-07-08 22:22:43 +02:00
2019-07-15 15:54:38 +02:00
/** @var \LibDNS\Records\Resource $record */
foreach ( $answers as $record ) {
$recordType = $record -> getType ();
$result [ $recordType ][] = ( string ) $record -> getData ();
// Cache for max one day
$ttls [ $recordType ] = \min ( $ttls [ $recordType ] ? ? 86400 , $record -> getTTL ());
}
if ( ! isset ( $result [ $type ])) {
return new Failure ( new NoRecordException ( " Query for ' { $name } ' failed, because loading the system's DNS configuration failed and no records were returned for ' { $name } ' ( " . Record :: getName ( $type ) . " ) " ));
2019-07-08 22:22:43 +02:00
}
2019-07-15 15:54:38 +02:00
return new Success (
\array_map (
static function ( $data ) use ( $type , $ttls ) {
return new Record ( $data , $type , $ttls [ $type ]);
},
$result [ $type ]
)
);
}
private function normalizeName ( string $name , int $type )
{
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 ], true )) {
$name = normalizeName ( $name );
}
return $name ;
}
/**
* @ param string $name
* @ param int $type
*
* @ return Question
*/
private function createQuestion ( string $name , int $type ) : Question
{
if ( 0 > $type || 0xffff < $type ) {
$message = \sprintf ( '%d does not correspond to a valid record type (must be between 0 and 65535).' , $type );
throw new \Error ( $message );
}
$question = $this -> questionFactory -> create ( $type );
$question -> setName ( $name );
return $question ;
2019-07-08 22:22:43 +02:00
}
}