Skip to content

Commit

Permalink
Merge pull request #99 from samuelwilliams/bind-resolver
Browse files Browse the repository at this point in the history
Bind resolver
  • Loading branch information
samuelwilliams authored Mar 7, 2020
2 parents f922c78 + 6bd1688 commit ee8542d
Show file tree
Hide file tree
Showing 10 changed files with 365 additions and 51 deletions.
4 changes: 3 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,9 @@
"psr/log": "^1.0",
"symfony/filesystem": "^4.3",
"symfony/console": "^4.3",
"vanilla/garden-cli": "^2.2"
"vanilla/garden-cli": "^2.2",
"badcow/dns": "^3.4",
"symfony/property-access": "^5.0"
},
"autoload": {
"psr-4": {
Expand Down
38 changes: 38 additions & 0 deletions example/example.com.db
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
$ORIGIN example.com.
$TTL 1337
$INCLUDE hq.example.com.txt
@ IN SOA (
example.com. ; MNAME
post.example.com. ; RNAME
2014110501 ; SERIAL
3600 ; REFRESH
14400 ; RETRY
604800 ; EXPIRE
3600 ; MINIMUM
); This is my Start of Authority Record; AKA SOA.

; NS RECORDS
@ NS ns1.nameserver.com.
@ NS ns2.nameserver.com.

info TXT "This is some additional \"information\""

; A RECORDS
sub.domain A 192.168.1.42 ; This is a local ip.

; AAAA RECORDS
ipv6.domain AAAA ::1 ; This is an IPv6 domain.

; MX RECORDS
@ MX 10 mail-gw1.example.net.
@ MX 20 mail-gw2.example.net.
@ MX 30 mail-gw3.example.net.

mail IN TXT "THIS IS SOME TEXT; WITH A SEMICOLON"

multicast APL (
1:192.168.0.0/23
2:2001:acad:1::/112
!1:192.168.1.64/28
!2:2001:acad:1::8/128
)
6 changes: 6 additions & 0 deletions src/RdataEncoder.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@

namespace yswery\DNS;

use yswery\DNS\Resolver\ArrayRdata;

class RdataEncoder
{
private static $methodMap = [
Expand All @@ -36,6 +38,10 @@ class RdataEncoder
*/
public static function encodeRdata(int $type, $rdata): string
{
if ($rdata instanceof ArrayRdata) {
return $rdata->getBadcowRdata()->toWire();
}

if (!array_key_exists($type, self::$methodMap)) {
throw new UnsupportedTypeException(sprintf('Record type "%s" is not a supported type.', RecordTypeEnum::getName($type)));
}
Expand Down
69 changes: 69 additions & 0 deletions src/Resolver/ArrayRdata.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
<?php

/*
* This file is part of PHP DNS Server.
*
* (c) Yif Swery <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace yswery\DNS\Resolver;

use ArrayAccess;
use Badcow\DNS\Rdata\RdataInterface;
use Symfony\Component\PropertyAccess\PropertyAccess;
use Symfony\Component\PropertyAccess\PropertyAccessor;

/**
* Represents Badcow\DNS\Rdata\RdataInterface as an array.
*/
class ArrayRdata implements ArrayAccess
{
/**
* @var RdataInterface
*/
private $rdata;

/**
* @var PropertyAccessor
*/
private $propertyAccessor;

public function __construct(RdataInterface $rdata)
{
$this->rdata = $rdata;
$this->propertyAccessor = PropertyAccess::createPropertyAccessor();
}

public function getBadcowRdata(): RdataInterface
{
return $this->rdata;
}

public function offsetExists($offset): bool
{
return $this->propertyAccessor->isReadable($this->rdata, $offset);
}

public function offsetGet($offset)
{
return $this->propertyAccessor->getValue($this->rdata, $offset);
}

public function offsetSet($offset, $value): void
{
$this->propertyAccessor->setValue($this->rdata, $offset, $value);
}

public function offsetUnset($offset): void
{
$this->propertyAccessor->setValue($this->rdata, $offset, null);
}

public function __toString()
{
return $this->rdata->toText();
}
}
66 changes: 66 additions & 0 deletions src/Resolver/BindResolver.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
<?php

/*
* This file is part of PHP DNS Server.
*
* (c) Yif Swery <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace yswery\DNS\Resolver;

use Badcow\DNS\Parser\ParseException;
use Badcow\DNS\Parser\Parser;
use Badcow\DNS\ResourceRecord as BadcowRR;
use Badcow\DNS\ZoneBuilder;
use yswery\DNS\ResourceRecord;

class BindResolver extends AbstractResolver
{
/**
* BindResolver constructor.
*
* @param array $files
*
* @throws ParseException
*/
public function __construct(array $files)
{
$this->isAuthoritative = true;
$this->allowRecursion = false;

$resourceRecords = [];

foreach ($files as $file) {
$fileContents = file_get_contents($file);
$zone = Parser::parse('.', $fileContents);
ZoneBuilder::fillOutZone($zone);

foreach ($zone as $rr) {
$resourceRecords[] = self::convertResourceRecord($rr);
}
}

$this->addZone($resourceRecords);
}

/**
* Converts Badcow\DNS\ResourceRecord object to yswery\DNS\ResourceRecord object.
*
* @return ResourceRecord
*/
public static function convertResourceRecord(BadcowRR $badcowResourceRecord, bool $isQuestion = false): ResourceRecord
{
$rr = new ResourceRecord();
$rr->setName($badcowResourceRecord->getName());
$rr->setClass($badcowResourceRecord->getClassId());
$rr->setRdata(new ArrayRdata($badcowResourceRecord->getRdata()));
$rr->setTtl($badcowResourceRecord->getTtl());
$rr->setType($badcowResourceRecord->getRdata()->getTypeCode());
$rr->setQuestion($isQuestion);

return $rr;
}
}
103 changes: 53 additions & 50 deletions tests/Resolver/AbstractResolverTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -27,44 +27,39 @@ abstract class AbstractResolverTest extends TestCase

public function testGetAnswer()
{
$soa = (new ResourceRecord())
->setName('example.com.')
->setClass(ClassEnum::INTERNET)
->setTtl(10800)
->setType(RecordTypeEnum::TYPE_SOA)
->setRdata([
'mname' => 'example.com.',
'rname' => 'postmaster.example.com.',
'serial' => 2,
'refresh' => 3600,
'retry' => 7200,
'expire' => 10800,
'minimum' => 3600,
]);

$aaaa = (new ResourceRecord())
->setName('example.com.')
->setClass(ClassEnum::INTERNET)
->setTtl(7200)
->setType(RecordTypeEnum::TYPE_AAAA)
->setRdata('2001:acad:ad::32');

$soa_query = (new ResourceRecord())
$query[] = (new ResourceRecord())
->setName('example.com.')
->setType(RecordTypeEnum::TYPE_SOA)
->setClass(ClassEnum::INTERNET)
->setQuestion(true);

$aaaa_query = (new ResourceRecord())
$query[] = (new ResourceRecord())
->setName('example.com.')
->setType(RecordTypeEnum::TYPE_AAAA)
->setClass(ClassEnum::INTERNET)
->setQuestion(true);

$query = [$soa_query, $aaaa_query];
$answer = [$soa, $aaaa];

$this->assertEquals($answer, $this->resolver->getAnswer($query));
$answer = $this->resolver->getAnswer($query);
$this->assertCount(2, $answer);
list($soa, $aaaa) = $answer;

$this->assertEquals('example.com.', $soa->getName());
$this->assertEquals(ClassEnum::INTERNET, $soa->getClass());
$this->assertEquals(10800, $soa->getTtl());
$this->assertEquals(RecordTypeEnum::TYPE_SOA, $soa->getType());
$this->assertEquals('example.com.', $soa->getRdata()['mname']);
$this->assertEquals('postmaster.example.com.', $soa->getRdata()['rname']);
$this->assertEquals(2, $soa->getRdata()['serial']);
$this->assertEquals(3600, $soa->getRdata()['refresh']);
$this->assertEquals(7200, $soa->getRdata()['retry']);
$this->assertEquals(10800, $soa->getRdata()['expire']);
$this->assertEquals(3600, $soa->getRdata()['minimum']);

$this->assertEquals('example.com.', $aaaa->getName());
$this->assertEquals(ClassEnum::INTERNET, $aaaa->getClass());
$this->assertEquals(7200, $aaaa->getTtl());
$this->assertEquals(RecordTypeEnum::TYPE_AAAA, $aaaa->getType());
$this->assertEquals(inet_pton('2001:acad:ad::32'), inet_pton($aaaa->getRdata()));
}

public function testUnconfiguredRecordDoesNotResolve()
Expand All @@ -84,19 +79,16 @@ public function testHostRecordReturnsArray()
->setType(RecordTypeEnum::TYPE_A)
->setQuestion(true);

$expectation[] = (new ResourceRecord())
->setName('test2.com.')
->setType(RecordTypeEnum::TYPE_A)
->setTtl(300)
->setRdata('111.111.111.111');

$expectation[] = (new ResourceRecord())
->setName('test2.com.')
->setType(RecordTypeEnum::TYPE_A)
->setTtl(300)
->setRdata('112.112.112.112');

$this->assertEquals($expectation, $this->resolver->getAnswer($question));
$answer = $this->resolver->getAnswer($question);
$this->assertCount(2, $answer);
$this->assertEquals('test2.com.', $answer[0]->getName());
$this->assertEquals(RecordTypeEnum::TYPE_A, $answer[0]->getType());
$this->assertEquals('111.111.111.111', (string) $answer[0]->getRdata());
$this->assertEquals(300, $answer[0]->getTtl());
$this->assertEquals('test2.com.', $answer[1]->getName());
$this->assertEquals(RecordTypeEnum::TYPE_A, $answer[1]->getType());
$this->assertEquals('112.112.112.112', (string) $answer[1]->getRdata());
$this->assertEquals(300, $answer[1]->getTtl());
}

public function testWildcardDomains()
Expand All @@ -106,13 +98,12 @@ public function testWildcardDomains()
->setType(RecordTypeEnum::TYPE_A)
->setQuestion(true);

$expectation[] = (new ResourceRecord())
->setName('badcow.subdomain.example.com.')
->setType(RecordTypeEnum::TYPE_A)
->setTtl(7200)
->setRdata('192.168.1.42');

$this->assertEquals($expectation, $this->resolver->getAnswer($question));
$answer = $this->resolver->getAnswer($question);
$this->assertCount(1, $answer);
$this->assertEquals('badcow.subdomain.example.com.', $answer[0]->getName());
$this->assertEquals(1, $answer[0]->getType());
$this->assertEquals('192.168.1.42', (string) $answer[0]->getRdata());
$this->assertEquals(7200, $answer[0]->getTtl());
}

/**
Expand All @@ -137,7 +128,7 @@ public function testIsAuthority()

public function testSrvRdata()
{
$question[] = (new ResourceRecord())
$query[] = (new ResourceRecord())
->setName('_ldap._tcp.example.com.')
->setType(RecordTypeEnum::TYPE_SRV)
->setQuestion(true);
Expand All @@ -153,6 +144,18 @@ public function testSrvRdata()
'target' => 'ldap.example.com.',
]);

$this->assertEquals($expectation, $this->resolver->getAnswer($question));
$answer = $this->resolver->getAnswer($query);
$this->assertCount(1, $answer);

$srv = $answer[0];
$this->assertEquals('_ldap._tcp.example.com.', $srv->getName());
$this->assertEquals(ClassEnum::INTERNET, $srv->getClass());
$this->assertEquals(7200, $srv->getTtl());
$this->assertEquals(RecordTypeEnum::TYPE_SRV, $srv->getType());

$this->assertEquals(1, $srv->getRdata()['priority']);
$this->assertEquals(5, $srv->getRdata()['weight']);
$this->assertEquals(389, $srv->getRdata()['port']);
$this->assertEquals('ldap.example.com.', $srv->getRdata()['target']);
}
}
Loading

0 comments on commit ee8542d

Please sign in to comment.