Skip to content

Commit 5109a48

Browse files
committed
Add EDNS0 options OPT_TCP_KEEPALIVE and OPT_PADDING
1 parent 31f02ae commit 5109a48

File tree

6 files changed

+194
-4
lines changed

6 files changed

+194
-4
lines changed

src/Model/Message.php

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,26 @@ final class Message
5050
const RCODE_NOT_IMPLEMENTED = 4;
5151
const RCODE_REFUSED = 5;
5252

53+
/**
54+
* The edns-tcp-keepalive EDNS0 Option
55+
*
56+
* Option value contains a `?float` with timeout in seconds (in 0.1s steps)
57+
* for DNS response or `null` for DNS query.
58+
*
59+
* @link https://tools.ietf.org/html/rfc7828
60+
*/
61+
const OPT_TCP_KEEPALIVE = 11;
62+
63+
/**
64+
* The EDNS(0) Padding Option
65+
*
66+
* Option value contains a `string` with binary data (usually variable
67+
* number of null bytes)
68+
*
69+
* @link https://tools.ietf.org/html/rfc7830
70+
*/
71+
const OPT_PADDING = 12;
72+
5373
/**
5474
* Creates a new request message for the given query
5575
*

src/Model/Record.php

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -114,8 +114,14 @@ final class Record
114114
*
115115
* - OPT:
116116
* Special pseudo-type for EDNS0. Includes an array of additional opt codes
117-
* with a binary string value in the form `[10=>"\x00",15=>"abc"]`. See
118-
* also [RFC 6891](https://tools.ietf.org/html/rfc6891) for more details.
117+
* with a value according to the respective OPT code. See `Message::OPT_*`
118+
* for list of supported OPT codes. Any other OPT code not currently
119+
* supported will be an opaque binary string containing the raw data
120+
* as transported in the DNS record. For forwards compatibility, you should
121+
* not rely on this format for unknown types. Future versions may add
122+
* support for new types and this may then parse the payload data
123+
* appropriately - this will not be considered a BC break. See also
124+
* [RFC 6891](https://tools.ietf.org/html/rfc6891) for more details.
119125
*
120126
* - Any other unknown type:
121127
* An opaque binary string containing the RDATA as transported in the DNS

src/Protocol/BinaryDumper.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,9 @@ private function recordsToBinary(array $records)
142142
case Message::TYPE_OPT:
143143
$binary = '';
144144
foreach ($record->data as $opt => $value) {
145+
if ($opt === Message::OPT_TCP_KEEPALIVE && $value !== null) {
146+
$value = \pack('n', round($value * 10));
147+
}
145148
$binary .= \pack('n*', $opt, \strlen($value)) . $value;
146149
}
147150
break;

src/Protocol/Parser.php

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -234,7 +234,16 @@ private function parseRecord(Message $message)
234234
$rdata = array();
235235
while (isset($message->data[$consumed + 4 - 1])) {
236236
list($code, $length) = array_values(unpack('n*', substr($message->data, $consumed, 4)));
237-
$rdata[$code] = (string) substr($message->data, $consumed + 4, $length);
237+
$value = (string) substr($message->data, $consumed + 4, $length);
238+
if ($code === Message::OPT_TCP_KEEPALIVE && $value === '') {
239+
$value = null;
240+
} elseif ($code === Message::OPT_TCP_KEEPALIVE && $length === 2) {
241+
list($value) = array_values(unpack('n', $value));
242+
$value = round($value * 0.1, 1);
243+
} elseif ($code === Message::OPT_TCP_KEEPALIVE) {
244+
break;
245+
}
246+
$rdata[$code] = $value;
238247
$consumed += 4 + $length;
239248
}
240249
} elseif (Message::TYPE_CAA === $type) {

tests/Protocol/BinaryDumperTest.php

Lines changed: 100 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,105 @@ public function testToBinaryRequestMessageWithAdditionalOptForEdns0()
9696
$this->assertSame($expected, $data);
9797
}
9898

99+
public function testToBinaryRequestMessageWithAdditionalOptForEdns0WithOptTcpKeepAliveDesired()
100+
{
101+
$data = "";
102+
$data .= "72 62 01 00 00 01 00 00 00 00 00 01"; // header
103+
$data .= "04 69 67 6f 72 02 69 6f 00"; // question: igor.io
104+
$data .= "00 01 00 01"; // question: type A, class IN
105+
$data .= "00"; // additional: (empty hostname)
106+
$data .= "00 29 03 e8 00 00 00 00 00 04 "; // additional: type OPT, class 1000 UDP size, TTL 0, 4 bytes RDATA
107+
$data .= "00 0b 00 00"; // OPT_TCP_KEEPALIVE=null encoded
108+
109+
$expected = $this->formatHexDump($data);
110+
111+
$request = new Message();
112+
$request->id = 0x7262;
113+
$request->rd = true;
114+
115+
$request->questions[] = new Query(
116+
'igor.io',
117+
Message::TYPE_A,
118+
Message::CLASS_IN
119+
);
120+
121+
$request->additional[] = new Record('', Message::TYPE_OPT, 1000, 0, array(
122+
Message::OPT_TCP_KEEPALIVE => null,
123+
));
124+
125+
$dumper = new BinaryDumper();
126+
$data = $dumper->toBinary($request);
127+
$data = $this->convertBinaryToHexDump($data);
128+
129+
$this->assertSame($expected, $data);
130+
}
131+
132+
public function testToBinaryRequestMessageWithAdditionalOptForEdns0WithOptTcpKeepAliveGiven()
133+
{
134+
$data = "";
135+
$data .= "72 62 01 00 00 01 00 00 00 00 00 01"; // header
136+
$data .= "04 69 67 6f 72 02 69 6f 00"; // question: igor.io
137+
$data .= "00 01 00 01"; // question: type A, class IN
138+
$data .= "00"; // additional: (empty hostname)
139+
$data .= "00 29 03 e8 00 00 00 00 00 06 "; // additional: type OPT, class 1000 UDP size, TTL 0, 6 bytes RDATA
140+
$data .= "00 0b 00 02 00 0c"; // OPT_TCP_KEEPALIVE=1.2 encoded
141+
142+
$expected = $this->formatHexDump($data);
143+
144+
$request = new Message();
145+
$request->id = 0x7262;
146+
$request->rd = true;
147+
148+
$request->questions[] = new Query(
149+
'igor.io',
150+
Message::TYPE_A,
151+
Message::CLASS_IN
152+
);
153+
154+
$request->additional[] = new Record('', Message::TYPE_OPT, 1000, 0, array(
155+
Message::OPT_TCP_KEEPALIVE => 1.2,
156+
));
157+
158+
$dumper = new BinaryDumper();
159+
$data = $dumper->toBinary($request);
160+
$data = $this->convertBinaryToHexDump($data);
161+
162+
$this->assertSame($expected, $data);
163+
}
164+
165+
public function testToBinaryRequestMessageWithAdditionalOptForEdns0WithOptPadding()
166+
{
167+
$data = "";
168+
$data .= "72 62 01 00 00 01 00 00 00 00 00 01"; // header
169+
$data .= "04 69 67 6f 72 02 69 6f 00"; // question: igor.io
170+
$data .= "00 01 00 01"; // question: type A, class IN
171+
$data .= "00"; // additional: (empty hostname)
172+
$data .= "00 29 03 e8 00 00 00 00 00 06 "; // additional: type OPT, class 1000 UDP size, TTL 0, 6 bytes RDATA
173+
$data .= "00 0c 00 02 00 00 "; // OPT_PADDING=0x0000 encoded
174+
175+
$expected = $this->formatHexDump($data);
176+
177+
$request = new Message();
178+
$request->id = 0x7262;
179+
$request->rd = true;
180+
181+
$request->questions[] = new Query(
182+
'igor.io',
183+
Message::TYPE_A,
184+
Message::CLASS_IN
185+
);
186+
187+
$request->additional[] = new Record('', Message::TYPE_OPT, 1000, 0, array(
188+
Message::OPT_PADDING => "\x00\x00"
189+
));
190+
191+
$dumper = new BinaryDumper();
192+
$data = $dumper->toBinary($request);
193+
$data = $this->convertBinaryToHexDump($data);
194+
195+
$this->assertSame($expected, $data);
196+
}
197+
99198
public function testToBinaryRequestMessageWithAdditionalOptForEdns0WithCustomOptCodes()
100199
{
101200
$data = "";
@@ -121,7 +220,7 @@ public function testToBinaryRequestMessageWithAdditionalOptForEdns0WithCustomOpt
121220

122221
$request->additional[] = new Record('', Message::TYPE_OPT, 1000, 0, array(
123222
0xa0 => 'foo',
124-
0x01 => "\x00\00"
223+
0x01 => "\x00\x00"
125224
));
126225

127226
$dumper = new BinaryDumper();

tests/Protocol/ParserTest.php

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -554,6 +554,44 @@ public function testParseOptResponseWithoutOptions()
554554
$this->assertSame(array(), $response->answers[0]->data);
555555
}
556556

557+
public function testParseOptResponseWithOptTcpKeepaliveDesired()
558+
{
559+
$data = "";
560+
$data .= "00"; // answer: empty domain
561+
$data .= "00 29 03 e8"; // answer: type OPT, class 1000 UDP size
562+
$data .= "00 00 00 00"; // answer: ttl 0
563+
$data .= "00 04"; // answer: rdlength 4
564+
$data .= "00 0b 00 00"; // OPT_TCP_KEEPALIVE=null encoded
565+
566+
$response = $this->parseAnswer($data);
567+
568+
$this->assertCount(1, $response->answers);
569+
$this->assertSame('', $response->answers[0]->name);
570+
$this->assertSame(Message::TYPE_OPT, $response->answers[0]->type);
571+
$this->assertSame(1000, $response->answers[0]->class);
572+
$this->assertSame(0, $response->answers[0]->ttl);
573+
$this->assertSame(array(Message::OPT_TCP_KEEPALIVE => null), $response->answers[0]->data);
574+
}
575+
576+
public function testParseOptResponseWithOptTcpKeepaliveGiven()
577+
{
578+
$data = "";
579+
$data .= "00"; // answer: empty domain
580+
$data .= "00 29 03 e8"; // answer: type OPT, class 1000 UDP size
581+
$data .= "00 00 00 00"; // answer: ttl 0
582+
$data .= "00 06"; // answer: rdlength 4
583+
$data .= "00 0b 00 02 00 0c"; // OPT_TCP_KEEPALIVE=1.2s encoded
584+
585+
$response = $this->parseAnswer($data);
586+
587+
$this->assertCount(1, $response->answers);
588+
$this->assertSame('', $response->answers[0]->name);
589+
$this->assertSame(Message::TYPE_OPT, $response->answers[0]->type);
590+
$this->assertSame(1000, $response->answers[0]->class);
591+
$this->assertSame(0, $response->answers[0]->ttl);
592+
$this->assertSame(array(Message::OPT_TCP_KEEPALIVE => 1.2), $response->answers[0]->data);
593+
}
594+
557595
public function testParseOptResponseWithCustomOptions()
558596
{
559597
$data = "";
@@ -1010,6 +1048,21 @@ public function testParseInvalidOPTResponseWhereRecordIsTooSmall()
10101048
$this->parseAnswer($data);
10111049
}
10121050

1051+
/**
1052+
* @expectedException InvalidArgumentException
1053+
*/
1054+
public function testParseInvalidOPTResponseWhereRecordLengthDoesNotMatchOptType()
1055+
{
1056+
$data = "";
1057+
$data .= "04 69 67 6f 72 02 69 6f 00"; // answer: igor.io
1058+
$data .= "00 29 03 e8"; // answer: type OPT, 1000 bytes max size
1059+
$data .= "00 00 00 00"; // answer: ttl 0
1060+
$data .= "00 07"; // answer: rdlength 7
1061+
$data .= "00 0b 00 03 01 02 03"; // answer: type OPT_TCP_KEEPALIVE, length 3 instead of 2
1062+
1063+
$this->parseAnswer($data);
1064+
}
1065+
10131066
/**
10141067
* @expectedException InvalidArgumentException
10151068
*/

0 commit comments

Comments
 (0)