Skip to content

Commit ee8542d

Browse files
Merge pull request #99 from samuelwilliams/bind-resolver
Bind resolver
2 parents f922c78 + 6bd1688 commit ee8542d

File tree

10 files changed

+365
-51
lines changed

10 files changed

+365
-51
lines changed

composer.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,9 @@
3737
"psr/log": "^1.0",
3838
"symfony/filesystem": "^4.3",
3939
"symfony/console": "^4.3",
40-
"vanilla/garden-cli": "^2.2"
40+
"vanilla/garden-cli": "^2.2",
41+
"badcow/dns": "^3.4",
42+
"symfony/property-access": "^5.0"
4143
},
4244
"autoload": {
4345
"psr-4": {

example/example.com.db

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
$ORIGIN example.com.
2+
$TTL 1337
3+
$INCLUDE hq.example.com.txt
4+
@ IN SOA (
5+
example.com. ; MNAME
6+
post.example.com. ; RNAME
7+
2014110501 ; SERIAL
8+
3600 ; REFRESH
9+
14400 ; RETRY
10+
604800 ; EXPIRE
11+
3600 ; MINIMUM
12+
); This is my Start of Authority Record; AKA SOA.
13+
14+
; NS RECORDS
15+
@ NS ns1.nameserver.com.
16+
@ NS ns2.nameserver.com.
17+
18+
info TXT "This is some additional \"information\""
19+
20+
; A RECORDS
21+
sub.domain A 192.168.1.42 ; This is a local ip.
22+
23+
; AAAA RECORDS
24+
ipv6.domain AAAA ::1 ; This is an IPv6 domain.
25+
26+
; MX RECORDS
27+
@ MX 10 mail-gw1.example.net.
28+
@ MX 20 mail-gw2.example.net.
29+
@ MX 30 mail-gw3.example.net.
30+
31+
mail IN TXT "THIS IS SOME TEXT; WITH A SEMICOLON"
32+
33+
multicast APL (
34+
1:192.168.0.0/23
35+
2:2001:acad:1::/112
36+
!1:192.168.1.64/28
37+
!2:2001:acad:1::8/128
38+
)

src/RdataEncoder.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111

1212
namespace yswery\DNS;
1313

14+
use yswery\DNS\Resolver\ArrayRdata;
15+
1416
class RdataEncoder
1517
{
1618
private static $methodMap = [
@@ -36,6 +38,10 @@ class RdataEncoder
3638
*/
3739
public static function encodeRdata(int $type, $rdata): string
3840
{
41+
if ($rdata instanceof ArrayRdata) {
42+
return $rdata->getBadcowRdata()->toWire();
43+
}
44+
3945
if (!array_key_exists($type, self::$methodMap)) {
4046
throw new UnsupportedTypeException(sprintf('Record type "%s" is not a supported type.', RecordTypeEnum::getName($type)));
4147
}

src/Resolver/ArrayRdata.php

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
<?php
2+
3+
/*
4+
* This file is part of PHP DNS Server.
5+
*
6+
* (c) Yif Swery <yiftachswr@gmail.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace yswery\DNS\Resolver;
13+
14+
use ArrayAccess;
15+
use Badcow\DNS\Rdata\RdataInterface;
16+
use Symfony\Component\PropertyAccess\PropertyAccess;
17+
use Symfony\Component\PropertyAccess\PropertyAccessor;
18+
19+
/**
20+
* Represents Badcow\DNS\Rdata\RdataInterface as an array.
21+
*/
22+
class ArrayRdata implements ArrayAccess
23+
{
24+
/**
25+
* @var RdataInterface
26+
*/
27+
private $rdata;
28+
29+
/**
30+
* @var PropertyAccessor
31+
*/
32+
private $propertyAccessor;
33+
34+
public function __construct(RdataInterface $rdata)
35+
{
36+
$this->rdata = $rdata;
37+
$this->propertyAccessor = PropertyAccess::createPropertyAccessor();
38+
}
39+
40+
public function getBadcowRdata(): RdataInterface
41+
{
42+
return $this->rdata;
43+
}
44+
45+
public function offsetExists($offset): bool
46+
{
47+
return $this->propertyAccessor->isReadable($this->rdata, $offset);
48+
}
49+
50+
public function offsetGet($offset)
51+
{
52+
return $this->propertyAccessor->getValue($this->rdata, $offset);
53+
}
54+
55+
public function offsetSet($offset, $value): void
56+
{
57+
$this->propertyAccessor->setValue($this->rdata, $offset, $value);
58+
}
59+
60+
public function offsetUnset($offset): void
61+
{
62+
$this->propertyAccessor->setValue($this->rdata, $offset, null);
63+
}
64+
65+
public function __toString()
66+
{
67+
return $this->rdata->toText();
68+
}
69+
}

src/Resolver/BindResolver.php

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
<?php
2+
3+
/*
4+
* This file is part of PHP DNS Server.
5+
*
6+
* (c) Yif Swery <yiftachswr@gmail.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace yswery\DNS\Resolver;
13+
14+
use Badcow\DNS\Parser\ParseException;
15+
use Badcow\DNS\Parser\Parser;
16+
use Badcow\DNS\ResourceRecord as BadcowRR;
17+
use Badcow\DNS\ZoneBuilder;
18+
use yswery\DNS\ResourceRecord;
19+
20+
class BindResolver extends AbstractResolver
21+
{
22+
/**
23+
* BindResolver constructor.
24+
*
25+
* @param array $files
26+
*
27+
* @throws ParseException
28+
*/
29+
public function __construct(array $files)
30+
{
31+
$this->isAuthoritative = true;
32+
$this->allowRecursion = false;
33+
34+
$resourceRecords = [];
35+
36+
foreach ($files as $file) {
37+
$fileContents = file_get_contents($file);
38+
$zone = Parser::parse('.', $fileContents);
39+
ZoneBuilder::fillOutZone($zone);
40+
41+
foreach ($zone as $rr) {
42+
$resourceRecords[] = self::convertResourceRecord($rr);
43+
}
44+
}
45+
46+
$this->addZone($resourceRecords);
47+
}
48+
49+
/**
50+
* Converts Badcow\DNS\ResourceRecord object to yswery\DNS\ResourceRecord object.
51+
*
52+
* @return ResourceRecord
53+
*/
54+
public static function convertResourceRecord(BadcowRR $badcowResourceRecord, bool $isQuestion = false): ResourceRecord
55+
{
56+
$rr = new ResourceRecord();
57+
$rr->setName($badcowResourceRecord->getName());
58+
$rr->setClass($badcowResourceRecord->getClassId());
59+
$rr->setRdata(new ArrayRdata($badcowResourceRecord->getRdata()));
60+
$rr->setTtl($badcowResourceRecord->getTtl());
61+
$rr->setType($badcowResourceRecord->getRdata()->getTypeCode());
62+
$rr->setQuestion($isQuestion);
63+
64+
return $rr;
65+
}
66+
}

tests/Resolver/AbstractResolverTest.php

Lines changed: 53 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -27,44 +27,39 @@ abstract class AbstractResolverTest extends TestCase
2727

2828
public function testGetAnswer()
2929
{
30-
$soa = (new ResourceRecord())
31-
->setName('example.com.')
32-
->setClass(ClassEnum::INTERNET)
33-
->setTtl(10800)
34-
->setType(RecordTypeEnum::TYPE_SOA)
35-
->setRdata([
36-
'mname' => 'example.com.',
37-
'rname' => 'postmaster.example.com.',
38-
'serial' => 2,
39-
'refresh' => 3600,
40-
'retry' => 7200,
41-
'expire' => 10800,
42-
'minimum' => 3600,
43-
]);
44-
45-
$aaaa = (new ResourceRecord())
46-
->setName('example.com.')
47-
->setClass(ClassEnum::INTERNET)
48-
->setTtl(7200)
49-
->setType(RecordTypeEnum::TYPE_AAAA)
50-
->setRdata('2001:acad:ad::32');
51-
52-
$soa_query = (new ResourceRecord())
30+
$query[] = (new ResourceRecord())
5331
->setName('example.com.')
5432
->setType(RecordTypeEnum::TYPE_SOA)
5533
->setClass(ClassEnum::INTERNET)
5634
->setQuestion(true);
5735

58-
$aaaa_query = (new ResourceRecord())
36+
$query[] = (new ResourceRecord())
5937
->setName('example.com.')
6038
->setType(RecordTypeEnum::TYPE_AAAA)
6139
->setClass(ClassEnum::INTERNET)
6240
->setQuestion(true);
6341

64-
$query = [$soa_query, $aaaa_query];
65-
$answer = [$soa, $aaaa];
66-
67-
$this->assertEquals($answer, $this->resolver->getAnswer($query));
42+
$answer = $this->resolver->getAnswer($query);
43+
$this->assertCount(2, $answer);
44+
list($soa, $aaaa) = $answer;
45+
46+
$this->assertEquals('example.com.', $soa->getName());
47+
$this->assertEquals(ClassEnum::INTERNET, $soa->getClass());
48+
$this->assertEquals(10800, $soa->getTtl());
49+
$this->assertEquals(RecordTypeEnum::TYPE_SOA, $soa->getType());
50+
$this->assertEquals('example.com.', $soa->getRdata()['mname']);
51+
$this->assertEquals('postmaster.example.com.', $soa->getRdata()['rname']);
52+
$this->assertEquals(2, $soa->getRdata()['serial']);
53+
$this->assertEquals(3600, $soa->getRdata()['refresh']);
54+
$this->assertEquals(7200, $soa->getRdata()['retry']);
55+
$this->assertEquals(10800, $soa->getRdata()['expire']);
56+
$this->assertEquals(3600, $soa->getRdata()['minimum']);
57+
58+
$this->assertEquals('example.com.', $aaaa->getName());
59+
$this->assertEquals(ClassEnum::INTERNET, $aaaa->getClass());
60+
$this->assertEquals(7200, $aaaa->getTtl());
61+
$this->assertEquals(RecordTypeEnum::TYPE_AAAA, $aaaa->getType());
62+
$this->assertEquals(inet_pton('2001:acad:ad::32'), inet_pton($aaaa->getRdata()));
6863
}
6964

7065
public function testUnconfiguredRecordDoesNotResolve()
@@ -84,19 +79,16 @@ public function testHostRecordReturnsArray()
8479
->setType(RecordTypeEnum::TYPE_A)
8580
->setQuestion(true);
8681

87-
$expectation[] = (new ResourceRecord())
88-
->setName('test2.com.')
89-
->setType(RecordTypeEnum::TYPE_A)
90-
->setTtl(300)
91-
->setRdata('111.111.111.111');
92-
93-
$expectation[] = (new ResourceRecord())
94-
->setName('test2.com.')
95-
->setType(RecordTypeEnum::TYPE_A)
96-
->setTtl(300)
97-
->setRdata('112.112.112.112');
98-
99-
$this->assertEquals($expectation, $this->resolver->getAnswer($question));
82+
$answer = $this->resolver->getAnswer($question);
83+
$this->assertCount(2, $answer);
84+
$this->assertEquals('test2.com.', $answer[0]->getName());
85+
$this->assertEquals(RecordTypeEnum::TYPE_A, $answer[0]->getType());
86+
$this->assertEquals('111.111.111.111', (string) $answer[0]->getRdata());
87+
$this->assertEquals(300, $answer[0]->getTtl());
88+
$this->assertEquals('test2.com.', $answer[1]->getName());
89+
$this->assertEquals(RecordTypeEnum::TYPE_A, $answer[1]->getType());
90+
$this->assertEquals('112.112.112.112', (string) $answer[1]->getRdata());
91+
$this->assertEquals(300, $answer[1]->getTtl());
10092
}
10193

10294
public function testWildcardDomains()
@@ -106,13 +98,12 @@ public function testWildcardDomains()
10698
->setType(RecordTypeEnum::TYPE_A)
10799
->setQuestion(true);
108100

109-
$expectation[] = (new ResourceRecord())
110-
->setName('badcow.subdomain.example.com.')
111-
->setType(RecordTypeEnum::TYPE_A)
112-
->setTtl(7200)
113-
->setRdata('192.168.1.42');
114-
115-
$this->assertEquals($expectation, $this->resolver->getAnswer($question));
101+
$answer = $this->resolver->getAnswer($question);
102+
$this->assertCount(1, $answer);
103+
$this->assertEquals('badcow.subdomain.example.com.', $answer[0]->getName());
104+
$this->assertEquals(1, $answer[0]->getType());
105+
$this->assertEquals('192.168.1.42', (string) $answer[0]->getRdata());
106+
$this->assertEquals(7200, $answer[0]->getTtl());
116107
}
117108

118109
/**
@@ -137,7 +128,7 @@ public function testIsAuthority()
137128

138129
public function testSrvRdata()
139130
{
140-
$question[] = (new ResourceRecord())
131+
$query[] = (new ResourceRecord())
141132
->setName('_ldap._tcp.example.com.')
142133
->setType(RecordTypeEnum::TYPE_SRV)
143134
->setQuestion(true);
@@ -153,6 +144,18 @@ public function testSrvRdata()
153144
'target' => 'ldap.example.com.',
154145
]);
155146

156-
$this->assertEquals($expectation, $this->resolver->getAnswer($question));
147+
$answer = $this->resolver->getAnswer($query);
148+
$this->assertCount(1, $answer);
149+
150+
$srv = $answer[0];
151+
$this->assertEquals('_ldap._tcp.example.com.', $srv->getName());
152+
$this->assertEquals(ClassEnum::INTERNET, $srv->getClass());
153+
$this->assertEquals(7200, $srv->getTtl());
154+
$this->assertEquals(RecordTypeEnum::TYPE_SRV, $srv->getType());
155+
156+
$this->assertEquals(1, $srv->getRdata()['priority']);
157+
$this->assertEquals(5, $srv->getRdata()['weight']);
158+
$this->assertEquals(389, $srv->getRdata()['port']);
159+
$this->assertEquals('ldap.example.com.', $srv->getRdata()['target']);
157160
}
158161
}

0 commit comments

Comments
 (0)