Skip to content

Commit 0745fef

Browse files
authored
Merge pull request #141 from clue-labs/caa
Support parsing CAA records
2 parents d99bc4e + a80b558 commit 0745fef

File tree

8 files changed

+125
-3
lines changed

8 files changed

+125
-3
lines changed

examples/92-query-any.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,11 @@
5858
$type = 'SOA';
5959
$data = json_encode($data);
6060
break;
61+
case Message::TYPE_CAA:
62+
// CAA records contains flag, tag and value
63+
$type = 'CAA';
64+
$data = $data['flag'] . ' ' . $data['tag'] . ' "' . $data['value'] . '"';
65+
break;
6166
default:
6267
// unknown type uses HEX format
6368
$type = 'Type ' . $answer->type;

src/Model/Message.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ final class Message
2121
const TYPE_AAAA = 28;
2222
const TYPE_SRV = 33;
2323
const TYPE_ANY = 255;
24+
const TYPE_CAA = 257;
2425

2526
const CLASS_IN = 1;
2627

src/Model/Record.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,9 @@ final class Record
8484
* minimum times in seconds (UINT32 each), for example:
8585
* `{"mname":"ns.example.com","rname":"hostmaster.example.com","serial":
8686
* 2018082601,"refresh":3600,"retry":1800,"expire":60000,"minimum":3600}`.
87+
* - CAA:
88+
* Includes flag (UNIT8), tag string and value string, for example:
89+
* `{"flag":128,"tag":"issue","value":"letsencrypt.org"}`
8790
* - Any other unknown type:
8891
* An opaque binary string containing the RDATA as transported in the DNS
8992
* record. For forwards compatibility, you should not rely on this format

src/Protocol/BinaryDumper.php

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,15 @@ private function recordsToBinary(array $records)
122122
$record->data['minimum']
123123
);
124124
break;
125+
case Message::TYPE_CAA:
126+
$binary = \pack(
127+
'C*',
128+
$record->data['flag'],
129+
\strlen($record->data['tag'])
130+
);
131+
$binary .= $record->data['tag'];
132+
$binary .= $record->data['value'];
133+
break;
125134
default:
126135
// RDATA is already stored as binary value for unknown record types
127136
$binary = $record->data;

src/Protocol/Parser.php

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -218,6 +218,22 @@ private function parseRecord(Message $message)
218218
'minimum' => $minimum
219219
);
220220
}
221+
} elseif (Message::TYPE_CAA === $type) {
222+
if ($rdLength > 3) {
223+
list($flag, $tagLength) = array_values(unpack('C*', substr($message->data, $consumed, 2)));
224+
225+
if ($tagLength > 0 && $rdLength - 2 - $tagLength > 0) {
226+
$tag = substr($message->data, $consumed + 2, $tagLength);
227+
$value = substr($message->data, $consumed + 2 + $tagLength, $rdLength - 2 - $tagLength);
228+
$consumed += $rdLength;
229+
230+
$rdata = array(
231+
'flag' => $flag,
232+
'tag' => $tag,
233+
'value' => $value
234+
);
235+
}
236+
}
221237
} else {
222238
// unknown types simply parse rdata as an opaque binary string
223239
$rdata = substr($message->data, $consumed, $rdLength);

tests/FunctionalResolverTest.php

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,19 @@ public function testResolveAllGoogleMxResolvesWithCache()
5757

5858
$this->loop->run();
5959
}
60+
/**
61+
* @group internet
62+
*/
63+
public function testResolveAllGoogleCaaResolvesWithCache()
64+
{
65+
$factory = new Factory();
66+
$this->resolver = $factory->createCached('8.8.8.8', $this->loop);
67+
68+
$promise = $this->resolver->resolveAll('google.com', Message::TYPE_CAA);
69+
$promise->then($this->expectCallableOnceWith($this->isType('array')), $this->expectCallableNever());
70+
71+
$this->loop->run();
72+
}
6073

6174
/**
6275
* @group internet

tests/Protocol/BinaryDumperTest.php

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -180,21 +180,30 @@ public function testToBinaryForResponseWithSOARecord()
180180
public function testToBinaryForResponseWithMultipleAnswerRecords()
181181
{
182182
$data = "";
183-
$data .= "72 62 01 00 00 01 00 04 00 00 00 00"; // header
183+
$data .= "72 62 01 00 00 01 00 05 00 00 00 00"; // header
184184
$data .= "04 69 67 6f 72 02 69 6f 00"; // question: igor.io
185185
$data .= "00 ff 00 01"; // question: type ANY, class IN
186+
186187
$data .= "04 69 67 6f 72 02 69 6f 00"; // answer: igor.io
187188
$data .= "00 01 00 01 00 00 00 00 00 04"; // answer: type A, class IN, TTL 0, 4 bytes
188189
$data .= "7f 00 00 01"; // answer: 127.0.0.1
190+
189191
$data .= "04 69 67 6f 72 02 69 6f 00"; // answer: igor.io
190192
$data .= "00 1c 00 01 00 00 00 00 00 10"; // question: type AAAA, class IN, TTL 0, 16 bytes
191193
$data .= "00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 01"; // answer: ::1
194+
192195
$data .= "04 69 67 6f 72 02 69 6f 00"; // answer: igor.io
193196
$data .= "00 10 00 01 00 00 00 00 00 0c"; // answer: type TXT, class IN, TTL 0, 12 bytes
194197
$data .= "05 68 65 6c 6c 6f 05 77 6f 72 6c 64"; // answer: hello, world
198+
195199
$data .= "04 69 67 6f 72 02 69 6f 00"; // answer: igor.io
196-
$data .= "00 0f 00 01 00 00 00 00 00 03"; // anwser: type MX, class IN, TTL 0, 3 bytes
197-
$data .= "00 00 00"; // priority 0, no target
200+
$data .= "00 0f 00 01 00 00 00 00 00 03"; // answer: type MX, class IN, TTL 0, 3 bytes
201+
$data .= "00 00 00"; // answer: … priority 0, no target
202+
203+
$data .= "04 69 67 6f 72 02 69 6f 00"; // answer: igor.io …
204+
$data .= "01 01 00 01 00 00 00 00 00 16"; // answer: type CAA, class IN, TTL 0, 22 bytes
205+
$data .= "00 05 69 73 73 75 65"; // answer: 0 issue …
206+
$data .= "6c 65 74 73 65 6e 63 72 79 70 74 2e 6f 72 67"; // answer: … letsencrypt.org
198207

199208
$expected = $this->formatHexDump($data);
200209

@@ -213,6 +222,7 @@ public function testToBinaryForResponseWithMultipleAnswerRecords()
213222
$response->answers[] = new Record('igor.io', Message::TYPE_AAAA, Message::CLASS_IN, 0, '::1');
214223
$response->answers[] = new Record('igor.io', Message::TYPE_TXT, Message::CLASS_IN, 0, array('hello', 'world'));
215224
$response->answers[] = new Record('igor.io', Message::TYPE_MX, Message::CLASS_IN, 0, array('priority' => 0, 'target' => ''));
225+
$response->answers[] = new Record('igor.io', Message::TYPE_CAA, Message::CLASS_IN, 0, array('flag' => 0, 'tag' => 'issue', 'value' => 'letsencrypt.org'));
216226

217227
$dumper = new BinaryDumper();
218228
$data = $dumper->toBinary($response);

tests/Protocol/ParserTest.php

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -550,6 +550,26 @@ public function testParseSOAResponse()
550550
);
551551
}
552552

553+
public function testParseCAAResponse()
554+
{
555+
$data = "";
556+
$data .= "04 69 67 6f 72 02 69 6f 00"; // answer: igor.io
557+
$data .= "01 01 00 01"; // answer: type CAA, class IN
558+
$data .= "00 01 51 80"; // answer: ttl 86400
559+
$data .= "00 16"; // answer: rdlength 22
560+
$data .= "00 05 69 73 73 75 65"; // answer: rdata 0, issue
561+
$data .= "6c 65 74 73 65 6e 63 72 79 70 74 2e 6f 72 67"; // answer: letsencrypt.org
562+
563+
$response = $this->parseAnswer($data);
564+
565+
$this->assertCount(1, $response->answers);
566+
$this->assertSame('igor.io', $response->answers[0]->name);
567+
$this->assertSame(Message::TYPE_CAA, $response->answers[0]->type);
568+
$this->assertSame(Message::CLASS_IN, $response->answers[0]->class);
569+
$this->assertSame(86400, $response->answers[0]->ttl);
570+
$this->assertSame(array('flag' => 0, 'tag' => 'issue', 'value' => 'letsencrypt.org'), $response->answers[0]->data);
571+
}
572+
553573
public function testParsePTRResponse()
554574
{
555575
$data = "";
@@ -889,6 +909,51 @@ public function testParseInvalidSOAResponseWhereFlagsAreMissing()
889909
$this->parseAnswer($data);
890910
}
891911

912+
/**
913+
* @expectedException InvalidArgumentException
914+
*/
915+
public function testParseInvalidCAAResponseEmtpyData()
916+
{
917+
$data = "";
918+
$data .= "04 69 67 6f 72 02 69 6f 00"; // answer: igor.io
919+
$data .= "01 01 00 01"; // answer: type CAA, class IN
920+
$data .= "00 01 51 80"; // answer: ttl 86400
921+
$data .= "00 00"; // answer: rdlength 0
922+
923+
$this->parseAnswer($data);
924+
}
925+
926+
/**
927+
* @expectedException InvalidArgumentException
928+
*/
929+
public function testParseInvalidCAAResponseMissingValue()
930+
{
931+
$data = "";
932+
$data .= "04 69 67 6f 72 02 69 6f 00"; // answer: igor.io
933+
$data .= "01 01 00 01"; // answer: type CAA, class IN
934+
$data .= "00 01 51 80"; // answer: ttl 86400
935+
$data .= "00 07"; // answer: rdlength 22
936+
$data .= "00 05 69 73 73 75 65"; // answer: rdata 0, issue
937+
938+
$this->parseAnswer($data);
939+
}
940+
941+
/**
942+
* @expectedException InvalidArgumentException
943+
*/
944+
public function testParseInvalidCAAResponseIncompleteTag()
945+
{
946+
$data = "";
947+
$data .= "04 69 67 6f 72 02 69 6f 00"; // answer: igor.io
948+
$data .= "01 01 00 01"; // answer: type CAA, class IN
949+
$data .= "00 01 51 80"; // answer: ttl 86400
950+
$data .= "00 0c"; // answer: rdlength 22
951+
$data .= "00 ff 69 73 73 75 65"; // answer: rdata 0, issue (incomplete due to invalid tag length)
952+
$data .= "68 65 6c 6c 6f"; // answer: hello
953+
954+
$this->parseAnswer($data);
955+
}
956+
892957
private function convertTcpDumpToBinary($input)
893958
{
894959
// sudo ngrep -d en1 -x port 53

0 commit comments

Comments
 (0)