1
0
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:
Daniil Gentili 2019-08-22 20:44:19 +02:00
parent acf6e83e3c
commit c56fa69008
7 changed files with 467 additions and 4 deletions

View File

@ -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",

View File

@ -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;

View 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);
}
}

View 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
);
}
}

View 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
];
}
}

View 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();
}
}

View 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);
});
}
}