mirror of
https://github.com/danog/dns.git
synced 2024-11-26 20:14:51 +01:00
Move native encoder classes
This commit is contained in:
parent
acf6e83e3c
commit
c56fa69008
@ -42,8 +42,7 @@
|
||||
"amphp/parser": "^1",
|
||||
"amphp/windows-registry": "^0.3",
|
||||
"daverandom/libdns": "^2.0.1",
|
||||
"ext-filter": "*",
|
||||
"danog/libdns-native": "^0.1.0"
|
||||
"ext-filter": "*"
|
||||
},
|
||||
"require-dev": {
|
||||
"amphp/phpunit-util": "^1",
|
||||
|
@ -2,11 +2,11 @@
|
||||
|
||||
namespace Amp\Dns;
|
||||
|
||||
use Amp\Dns\Native\NativeDecoderFactory;
|
||||
use Amp\Dns\Native\NativeEncoderFactory;
|
||||
use Amp\Failure;
|
||||
use Amp\Promise;
|
||||
use Amp\Success;
|
||||
use danog\LibDNSNative\NativeDecoderFactory;
|
||||
use danog\LibDNSNative\NativeEncoderFactory;
|
||||
use LibDNS\Messages\MessageFactory;
|
||||
use LibDNS\Messages\MessageTypes;
|
||||
use LibDNS\Records\Question;
|
||||
|
281
lib/Native/NativeDecoder.php
Normal file
281
lib/Native/NativeDecoder.php
Normal file
@ -0,0 +1,281 @@
|
||||
<?php
|
||||
|
||||
namespace Amp\Dns\Native;
|
||||
|
||||
use LibDNS\Decoder\DecoderFactory;
|
||||
use LibDNS\Encoder\EncodingContext;
|
||||
use LibDNS\Encoder\EncodingContextFactory;
|
||||
use LibDNS\Messages\Message;
|
||||
use LibDNS\Messages\MessageFactory;
|
||||
use LibDNS\Messages\MessageTypes;
|
||||
use LibDNS\Packets\PacketFactory;
|
||||
use LibDNS\Records\Question;
|
||||
use LibDNS\Records\QuestionFactory;
|
||||
use LibDNS\Records\Resource;
|
||||
use LibDNS\Records\Types\DomainName;
|
||||
use LibDNS\Records\Types\Type;
|
||||
use LibDNS\Records\Types\TypeBuilder;
|
||||
use LibDNS\Records\Types\Types;
|
||||
|
||||
/**
|
||||
* Decodes JSON DNS strings to Message objects.
|
||||
*
|
||||
* @author Daniil Gentili <https://daniil.it>, Chris Wright <https://github.com/DaveRandom>
|
||||
*/
|
||||
class NativeDecoder
|
||||
{
|
||||
/**
|
||||
* @var \LibDNS\Packets\PacketFactory
|
||||
*/
|
||||
private $packetFactory;
|
||||
|
||||
/**
|
||||
* @var \LibDNS\Messages\MessageFactory
|
||||
*/
|
||||
private $messageFactory;
|
||||
|
||||
/**
|
||||
* @var \LibDNS\Records\QuestionFactory
|
||||
*/
|
||||
private $questionFactory;
|
||||
|
||||
/**
|
||||
* @var \LibDNS\Records\Types\TypeBuilder
|
||||
*/
|
||||
private $typeBuilder;
|
||||
/**
|
||||
* @var \LibDNS\Decoder\DecoderFactory
|
||||
*/
|
||||
private $decoderFactory;
|
||||
|
||||
/**
|
||||
* Map class names to IDs.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $classMap = [];
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param \LibDNS\Packets\PacketFactory $packetFactory
|
||||
* @param \LibDNS\Messages\MessageFactory $messageFactory
|
||||
* @param \LibDNS\Records\QuestionFactory $questionFactory
|
||||
* @param \LibDNS\Records\Types\TypeBuilder $typeBuilder
|
||||
* @param \LibDNS\Encoder\EncodingContextFactory $encodingContextFactory
|
||||
* @param \LibDNS\Decoder\DecoderFactory $decoderFactory
|
||||
* @param bool $allowTrailingData
|
||||
*/
|
||||
public function __construct(
|
||||
PacketFactory $packetFactory,
|
||||
MessageFactory $messageFactory,
|
||||
QuestionFactory $questionFactory,
|
||||
TypeBuilder $typeBuilder,
|
||||
EncodingContextFactory $encodingContextFactory,
|
||||
DecoderFactory $decoderFactory
|
||||
) {
|
||||
$this->packetFactory = $packetFactory;
|
||||
$this->messageFactory = $messageFactory;
|
||||
$this->questionFactory = $questionFactory;
|
||||
$this->typeBuilder = $typeBuilder;
|
||||
$this->encodingContextFactory = $encodingContextFactory;
|
||||
$this->decoderFactory = $decoderFactory;
|
||||
|
||||
$classes = new \ReflectionClass('\\LibDNS\\Records\\ResourceClasses');
|
||||
foreach ($classes->getConstants() as $name => $value) {
|
||||
$this->classMap[$name] = $value;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Decode a question record.
|
||||
*
|
||||
*
|
||||
* @return \LibDNS\Records\Question
|
||||
* @throws \UnexpectedValueException When the record is invalid
|
||||
*/
|
||||
private function decodeQuestionRecord(string $name, int $type): Question
|
||||
{
|
||||
/** @var \LibDNS\Records\Types\DomainName $domainName */
|
||||
$domainName = $this->typeBuilder->build(Types::DOMAIN_NAME);
|
||||
$labels = \explode('.', $name);
|
||||
if (!empty($last = \array_pop($labels))) {
|
||||
$labels[] = $last;
|
||||
}
|
||||
$domainName->setLabels($labels);
|
||||
|
||||
$question = $this->questionFactory->create($type);
|
||||
$question->setName($domainName);
|
||||
//$question->setClass($meta['class']);
|
||||
return $question;
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode a question record.
|
||||
*
|
||||
* @param \LibDNS\Encoder\EncodingContext $encodingContext
|
||||
* @param \LibDNS\Records\Question $record
|
||||
*/
|
||||
private function encodeQuestionRecord(EncodingContext $encodingContext, string $name, int $type)
|
||||
{
|
||||
if (!$encodingContext->isTruncated()) {
|
||||
$packet = $encodingContext->getPacket();
|
||||
$record = $this->decodeQuestionRecord($name, $type);
|
||||
$name = $this->encodeDomainName($record->getName(), $encodingContext);
|
||||
$meta = \pack('n*', $record->getType(), $record->getClass());
|
||||
|
||||
if (12 + $packet->getLength()+\strlen($name) + 4 > 512) {
|
||||
$encodingContext->isTruncated(true);
|
||||
} else {
|
||||
$packet->write($name);
|
||||
$packet->write($meta);
|
||||
}
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Encode a DomainName field.
|
||||
*
|
||||
* @param \LibDNS\Records\Types\DomainName $domainName
|
||||
* @param \LibDNS\Encoder\EncodingContext $encodingContext
|
||||
* @return string
|
||||
*/
|
||||
private function encodeDomainName(DomainName $domainName, EncodingContext $encodingContext): string
|
||||
{
|
||||
$packetIndex = $encodingContext->getPacket()->getLength() + 12;
|
||||
$labelRegistry = $encodingContext->getLabelRegistry();
|
||||
|
||||
$result = '';
|
||||
$labels = $domainName->getLabels();
|
||||
|
||||
if ($encodingContext->useCompression()) {
|
||||
do {
|
||||
$part = \implode('.', $labels);
|
||||
$index = $labelRegistry->lookupIndex($part);
|
||||
|
||||
if ($index === null) {
|
||||
$labelRegistry->register($part, $packetIndex);
|
||||
|
||||
$label = \array_shift($labels);
|
||||
$length = \strlen($label);
|
||||
|
||||
$result .= \chr($length).$label;
|
||||
$packetIndex += $length + 1;
|
||||
} else {
|
||||
$result .= \pack('n', 0b1100000000000000 | $index);
|
||||
break;
|
||||
}
|
||||
} while ($labels);
|
||||
|
||||
if (!$labels) {
|
||||
$result .= "\x00";
|
||||
}
|
||||
} else {
|
||||
foreach ($labels as $label) {
|
||||
$result .= \chr(\strlen($label)).$label;
|
||||
}
|
||||
|
||||
$result .= "\x00";
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode a resource record.
|
||||
*
|
||||
* @param \LibDNS\Encoder\EncodingContext $encodingContext
|
||||
* @param array $record
|
||||
*/
|
||||
private function encodeResourceRecord(EncodingContext $encodingContext, array $record)
|
||||
{
|
||||
if (!$encodingContext->isTruncated()) {
|
||||
/** @var \LibDNS\Records\Types\DomainName $domainName */
|
||||
$domainName = $this->typeBuilder->build(Types::DOMAIN_NAME);
|
||||
$labels = \explode('.', $record['host']);
|
||||
if (!empty($last = \array_pop($labels))) {
|
||||
$labels[] = $last;
|
||||
}
|
||||
$domainName->setLabels($labels);
|
||||
|
||||
$packet = $encodingContext->getPacket();
|
||||
$name = $this->encodeDomainName($domainName, $encodingContext);
|
||||
|
||||
$data = $record['data'];
|
||||
$meta = \pack('n2Nn', $record['type'], $this->classMap[$record['class']], $record['ttl'], \strlen($data));
|
||||
|
||||
if (12 + $packet->getLength()+\strlen($name) + 10+\strlen($data) > 512) {
|
||||
$encodingContext->isTruncated(true);
|
||||
} else {
|
||||
$packet->write($name);
|
||||
$packet->write($meta);
|
||||
$packet->write($data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode a Message from JSON-encoded string.
|
||||
*
|
||||
* @param array $result The actual response
|
||||
* @param string $domain The domain name that was queried
|
||||
* @param int $type The record type that was queried
|
||||
* @param array $authoritative Authoritative NS results
|
||||
* @param array $additional Additional results
|
||||
* @return \LibDNS\Messages\Message
|
||||
* @throws \UnexpectedValueException When the packet data is invalid
|
||||
* @throws \InvalidArgumentException When a type subtype is unknown
|
||||
*/
|
||||
public function decode(array $result, string $domain, int $type, array $authoritative = null, array $additional = null): Message
|
||||
{
|
||||
$additional = $additional ?? [];
|
||||
$authoritative = $additional ?? [];
|
||||
|
||||
$packet = $this->packetFactory->create();
|
||||
$encodingContext = $this->encodingContextFactory->create($packet, false);
|
||||
$message = $this->messageFactory->create();
|
||||
|
||||
//$message->isAuthoritative(true);
|
||||
$message->setType(MessageTypes::RESPONSE);
|
||||
//$message->setID($requestId);
|
||||
$message->setResponseCode(0);
|
||||
$message->isTruncated(false);
|
||||
$message->isRecursionDesired(false);
|
||||
$message->isRecursionAvailable(false);
|
||||
|
||||
$this->encodeQuestionRecord($encodingContext, $domain, $type);
|
||||
|
||||
$expectedAnswers = \count($result);
|
||||
for ($i = 0; $i < $expectedAnswers; $i++) {
|
||||
$this->encodeResourceRecord($encodingContext, $result[$i]);
|
||||
}
|
||||
|
||||
$expectedAuth = \count($authoritative);
|
||||
for ($i = 0; $i < $expectedAuth; $i++) {
|
||||
$this->encodeResourceRecord($encodingContext, $authoritative[$i]);
|
||||
}
|
||||
|
||||
$expectedAdditional = \count($additional);
|
||||
for ($i = 0; $i < $expectedAdditional; $i++) {
|
||||
$this->encodeResourceRecord($encodingContext, $additional[$i]);
|
||||
}
|
||||
|
||||
$header = [
|
||||
'id' => $message->getID(),
|
||||
'meta' => 0,
|
||||
'qd' => 1,
|
||||
'an' => $expectedAnswers,
|
||||
'ns' => $expectedAuth,
|
||||
'ar' => $expectedAdditional,
|
||||
];
|
||||
|
||||
$header['meta'] |= $message->getType() << 15;
|
||||
$header['meta'] |= $message->getOpCode() << 11;
|
||||
$header['meta'] |= ((int) $message->isAuthoritative()) << 10;
|
||||
$header['meta'] |= ((int) $encodingContext->isTruncated()) << 9;
|
||||
$header['meta'] |= ((int) $message->isRecursionDesired()) << 8;
|
||||
$header['meta'] |= ((int) $message->isRecursionAvailable()) << 7;
|
||||
$header['meta'] |= $message->getResponseCode();
|
||||
|
||||
$data = \pack('n*', $header['id'], $header['meta'], $header['qd'], $header['an'], $header['ns'], $header['ar']).$packet->read($packet->getLength());
|
||||
|
||||
return $this->decoderFactory->create()->decode($data);
|
||||
}
|
||||
}
|
47
lib/Native/NativeDecoderFactory.php
Normal file
47
lib/Native/NativeDecoderFactory.php
Normal file
@ -0,0 +1,47 @@
|
||||
<?php
|
||||
/**
|
||||
* Creates NativeDecoder objects.
|
||||
*
|
||||
* @author Daniil Gentili <https://daniil.it>, Chris Wright <https://github.com/DaveRandom>
|
||||
* @copyright Copyright (c) Chris Wright <https://github.com/DaveRandom>,
|
||||
* @license http://www.opensource.org/licenses/mit-license.html MIT License
|
||||
*/
|
||||
namespace Amp\Dns\Native;
|
||||
|
||||
use \LibDNS\Messages\MessageFactory;
|
||||
use \LibDNS\Packets\PacketFactory;
|
||||
use \LibDNS\Records\QuestionFactory;
|
||||
use \LibDNS\Records\RecordCollectionFactory;
|
||||
use \LibDNS\Records\TypeDefinitions\TypeDefinitionManager;
|
||||
use \LibDNS\Records\Types\TypeBuilder;
|
||||
use \LibDNS\Records\Types\TypeFactory;
|
||||
use LibDNS\Decoder\DecoderFactory;
|
||||
use LibDNS\Encoder\EncodingContextFactory;
|
||||
|
||||
/**
|
||||
* Creates NativeDecoder objects.
|
||||
*
|
||||
* @author Daniil Gentili <https://daniil.it>, Chris Wright <https://github.com/DaveRandom>
|
||||
*/
|
||||
class NativeDecoderFactory
|
||||
{
|
||||
/**
|
||||
* Create a new NativeDecoder object.
|
||||
*
|
||||
* @param \LibDNS\Records\TypeDefinitions\TypeDefinitionManager $typeDefinitionManager
|
||||
* @return NativeDecoder
|
||||
*/
|
||||
public function create(TypeDefinitionManager $typeDefinitionManager = null): NativeDecoder
|
||||
{
|
||||
$typeBuilder = new TypeBuilder(new TypeFactory);
|
||||
|
||||
return new NativeDecoder(
|
||||
new PacketFactory,
|
||||
new MessageFactory(new RecordCollectionFactory),
|
||||
new QuestionFactory,
|
||||
$typeBuilder,
|
||||
new EncodingContextFactory,
|
||||
new DecoderFactory
|
||||
);
|
||||
}
|
||||
}
|
57
lib/Native/NativeEncoder.php
Normal file
57
lib/Native/NativeEncoder.php
Normal file
@ -0,0 +1,57 @@
|
||||
<?php declare(strict_types = 1);
|
||||
/**
|
||||
* Encodes Message objects to query strings.
|
||||
*
|
||||
* PHP version 5.4
|
||||
*
|
||||
* @category LibDNS
|
||||
* @package Encoder
|
||||
* @author Daniil Gentili <https://daniil.it>, Chris Wright <https://github.com/DaveRandom>
|
||||
* @copyright Copyright (c) Chris Wright <https://github.com/DaveRandom>
|
||||
* @license http://www.opensource.org/licenses/mit-license.html MIT License
|
||||
* @version 2.0.0
|
||||
*/
|
||||
namespace Amp\Dns\Native;
|
||||
|
||||
use \LibDNS\Messages\Message;
|
||||
use LibDNS\Messages\MessageTypes;
|
||||
|
||||
/**
|
||||
* Encodes Message objects to query strings.
|
||||
*
|
||||
* @category LibDNS
|
||||
* @package Encoder
|
||||
* @author Daniil Gentili <https://daniil.it>, Chris Wright <https://github.com/DaveRandom>
|
||||
*/
|
||||
class NativeEncoder
|
||||
{
|
||||
/**
|
||||
* Encode a Message to URL payload.
|
||||
*
|
||||
* @param \LibDNS\Messages\Message $message The Message to encode
|
||||
* @return array Array of parameters to pass to the \dns_get_record function
|
||||
*/
|
||||
public function encode(Message $message): array
|
||||
{
|
||||
if ($message->getType() !== MessageTypes::QUERY) {
|
||||
throw new \InvalidArgumentException('Invalid question: is not a question record');
|
||||
}
|
||||
$questions = $message->getQuestionRecords();
|
||||
if ($questions->count() === 0) {
|
||||
throw new \InvalidArgumentException('Invalid question: 0 question records provided');
|
||||
}
|
||||
if ($questions->count() !== 1) {
|
||||
throw new \InvalidArgumentException('Invalid question: only one question record can be provided at a time');
|
||||
}
|
||||
|
||||
$question = $questions->getRecordByIndex(0);
|
||||
|
||||
return [
|
||||
\implode('.', $question->getName()->getLabels()), // Name
|
||||
$question->getType(), // Type
|
||||
null, // Authority records
|
||||
null, // Additional records
|
||||
true, // Raw results
|
||||
];
|
||||
}
|
||||
}
|
34
lib/Native/NativeEncoderFactory.php
Normal file
34
lib/Native/NativeEncoderFactory.php
Normal file
@ -0,0 +1,34 @@
|
||||
<?php declare(strict_types = 1);
|
||||
/**
|
||||
* Creates NativeEncoder objects.
|
||||
*
|
||||
* PHP version 5.4
|
||||
*
|
||||
* @category LibDNS
|
||||
* @package Encoder
|
||||
* @author Daniil Gentili <https://daniil.it>, Chris Wright <https://github.com/DaveRandom>
|
||||
* @copyright Copyright (c) Chris Wright <https://github.com/DaveRandom>
|
||||
* @license http://www.opensource.org/licenses/mit-license.html MIT License
|
||||
* @version 2.0.0
|
||||
*/
|
||||
namespace Amp\Dns\Native;
|
||||
|
||||
/**
|
||||
* Creates NativeEncoder objects.
|
||||
*
|
||||
* @category LibDNS
|
||||
* @package Encoder
|
||||
* @author Daniil Gentili <https://daniil.it>, Chris Wright <https://github.com/DaveRandom>
|
||||
*/
|
||||
class NativeEncoderFactory
|
||||
{
|
||||
/**
|
||||
* Create a new Encoder object.
|
||||
*
|
||||
* @return \LibDNS\Encoder\Encoder
|
||||
*/
|
||||
public function create(): NativeEncoder
|
||||
{
|
||||
return new NativeEncoder();
|
||||
}
|
||||
}
|
45
test/BlockingFallbackResolverTest.php
Normal file
45
test/BlockingFallbackResolverTest.php
Normal file
@ -0,0 +1,45 @@
|
||||
<?php
|
||||
|
||||
namespace Amp\Dns\Test;
|
||||
|
||||
use Amp\Dns\BlockingFallbackResolver;
|
||||
use Amp\Dns\DnsException;
|
||||
use Amp\Dns\InvalidNameException;
|
||||
use Amp\Dns\Record;
|
||||
use Amp\Loop;
|
||||
use Amp\PHPUnit\TestCase;
|
||||
|
||||
class BlockingFallbackResolverTest extends TestCase
|
||||
{
|
||||
public function testResolveSecondParameterAcceptedValues()
|
||||
{
|
||||
Loop::run(function () {
|
||||
$this->expectException(\Error::class);
|
||||
(new BlockingFallbackResolver)->resolve("abc.de", Record::TXT);
|
||||
});
|
||||
}
|
||||
|
||||
public function testIpAsArgumentWithIPv4Restriction()
|
||||
{
|
||||
Loop::run(function () {
|
||||
$this->expectException(DnsException::class);
|
||||
yield (new BlockingFallbackResolver)->resolve("::1", Record::A);
|
||||
});
|
||||
}
|
||||
|
||||
public function testIpAsArgumentWithIPv6Restriction()
|
||||
{
|
||||
Loop::run(function () {
|
||||
$this->expectException(DnsException::class);
|
||||
yield (new BlockingFallbackResolver)->resolve("127.0.0.1", Record::AAAA);
|
||||
});
|
||||
}
|
||||
|
||||
public function testInvalidName()
|
||||
{
|
||||
Loop::run(function () {
|
||||
$this->expectException(InvalidNameException::class);
|
||||
yield (new BlockingFallbackResolver)->resolve("go@gle.com", Record::A);
|
||||
});
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user