Skip to content

Commit bc76e1f

Browse files
committed
Support escaping literal dots and special characters in domain names
Among others, this is needed for PTR records for DNS-based service discovery (DNS-SD) that contain "human friendly" names. The backslash-escaping allows literal dots in the name (escaped) to be distinguished from label-separator dots (not escaped). See https://tools.ietf.org/html/rfc6763#section-4.3
1 parent a04f6f2 commit bc76e1f

4 files changed

Lines changed: 94 additions & 2 deletions

File tree

src/Protocol/BinaryDumper.php

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,15 @@ private function domainNameToBinary($host)
175175
return "\0";
176176
}
177177

178-
return $this->textsToBinary(\explode('.', $host . '.'));
178+
// break up domain name at each dot that is not preceeded by a backslash (escaped notation)
179+
return $this->textsToBinary(
180+
\array_map(
181+
'stripcslashes',
182+
\preg_split(
183+
'/(?<!\\\\)\./',
184+
$host . '.'
185+
)
186+
)
187+
);
179188
}
180189
}

src/Protocol/Parser.php

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -270,7 +270,19 @@ private function readDomain($data, $consumed)
270270
return array(null, null);
271271
}
272272

273-
return array(implode('.', $labels), $consumed);
273+
// use escaped notation for each label part, then join using dots
274+
return array(
275+
\implode(
276+
'.',
277+
\array_map(
278+
function ($label) {
279+
return \addcslashes($label, "\0..\40.\177");
280+
},
281+
$labels
282+
)
283+
),
284+
$consumed
285+
);
274286
}
275287

276288
private function readLabels($data, $consumed)

tests/Protocol/BinaryDumperTest.php

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,47 @@ public function testToBinaryForResponseWithSOARecord()
177177
$this->assertSame($expected, $data);
178178
}
179179

180+
public function testToBinaryForResponseWithPTRRecordWithSpecialCharactersEscaped()
181+
{
182+
$data = "";
183+
$data .= "72 62 01 00 00 01 00 01 00 00 00 00"; // header
184+
$data .= "08 5f 70 72 69 6e 74 65 72 04 5f 74 63 70 06 64 6e 73 2d 73 64 03 6f 72 67 00"; // question: _printer._tcp.dns-sd.org
185+
$data .= "00 0c 00 01"; // question: type PTR, class IN
186+
$data .= "08 5f 70 72 69 6e 74 65 72 04 5f 74 63 70 06 64 6e 73 2d 73 64 03 6f 72 67 00"; // answer: _printer._tcp.dns-sd.org
187+
$data .= "00 0c 00 01"; // answer: type PTR, class IN
188+
$data .= "00 01 51 80"; // answer: ttl 86400
189+
$data .= "00 2f"; // answer: rdlength 47
190+
$data .= "14 33 72 64 2e 20 46 6c 6f 6f 72 20 43 6f 70 79 20 52 6f 6f 6d"; // answer: answer: rdata "3rd. Floor Copy Room" …
191+
$data .= "08 5f 70 72 69 6e 74 65 72 04 5f 74 63 70 06 64 6e 73 2d 73 64 03 6f 72 67 00"; // answer: … "._printer._tcp.dns-sd.org"
192+
193+
$expected = $this->formatHexDump($data);
194+
195+
$response = new Message();
196+
$response->id = 0x7262;
197+
$response->rd = true;
198+
$response->rcode = Message::RCODE_OK;
199+
200+
$response->questions[] = new Query(
201+
'_printer._tcp.dns-sd.org',
202+
Message::TYPE_PTR,
203+
Message::CLASS_IN
204+
);
205+
206+
$response->answers[] = new Record(
207+
'_printer._tcp.dns-sd.org',
208+
Message::TYPE_PTR,
209+
Message::CLASS_IN,
210+
86400,
211+
'3rd\.\ Floor\ Copy\ Room._printer._tcp.dns-sd.org'
212+
);
213+
214+
$dumper = new BinaryDumper();
215+
$data = $dumper->toBinary($response);
216+
$data = $this->convertBinaryToHexDump($data);
217+
218+
$this->assertSame($expected, $data);
219+
}
220+
180221
public function testToBinaryForResponseWithMultipleAnswerRecords()
181222
{
182223
$data = "";

tests/Protocol/ParserTest.php

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -629,6 +629,36 @@ public function testParsePTRResponse()
629629
$this->assertSame('google-public-dns-b.google.com', $response->answers[0]->data);
630630
}
631631

632+
public function testParsePTRResponseWithSpecialCharactersEscaped()
633+
{
634+
$data = "";
635+
$data .= "5d d8 81 80 00 01 00 01 00 00 00 00"; // header
636+
$data .= "08 5f 70 72 69 6e 74 65 72 04 5f 74 63 70 06 64 6e 73 2d 73 64 03 6f 72 67 00"; // question: _printer._tcp.dns-sd.org
637+
$data .= "00 0c 00 01"; // question: type PTR, class IN
638+
$data .= "c0 0c"; // answer: offset pointer to rdata
639+
$data .= "00 0c 00 01"; // answer: type PTR, class IN
640+
$data .= "00 01 51 7f"; // answer: ttl 86399
641+
$data .= "00 17"; // answer: rdlength 23
642+
$data .= "14 33 72 64 2e 20 46 6c 6f 6f 72 20 43 6f 70 79 20 52 6f 6f 6d"; // answer: rdata "3rd. Floor Copy Room" …
643+
$data .= "c0 0c"; // answer: offset pointer to rdata
644+
645+
$data = $this->convertTcpDumpToBinary($data);
646+
647+
$response = $this->parser->parseMessage($data);
648+
649+
$this->assertCount(1, $response->questions);
650+
$this->assertSame('_printer._tcp.dns-sd.org', $response->questions[0]->name);
651+
$this->assertSame(Message::TYPE_PTR, $response->questions[0]->type);
652+
$this->assertSame(Message::CLASS_IN, $response->questions[0]->class);
653+
654+
$this->assertCount(1, $response->answers);
655+
$this->assertSame('_printer._tcp.dns-sd.org', $response->answers[0]->name);
656+
$this->assertSame(Message::TYPE_PTR, $response->answers[0]->type);
657+
$this->assertSame(Message::CLASS_IN, $response->answers[0]->class);
658+
$this->assertSame(86399, $response->answers[0]->ttl);
659+
$this->assertSame('3rd\.\ Floor\ Copy\ Room._printer._tcp.dns-sd.org', $response->answers[0]->data);
660+
}
661+
632662
/**
633663
* @expectedException InvalidArgumentException
634664
*/

0 commit comments

Comments
 (0)