Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions src/Model/Message.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,19 @@ final class Message
const TYPE_AAAA = 28;
const TYPE_SRV = 33;
const TYPE_SSHFP = 44;

/**
* pseudo-type for EDNS0
*
* These are included in the additional section and usually not in answer section.
* Defined in [RFC 6891](https://tools.ietf.org/html/rfc6891) (or older
* [RFC 2671](https://tools.ietf.org/html/rfc2671)).
*
* The OPT record uses the "class" field to store the maximum size.
*
* The OPT record uses the "ttl" field to store additional flags.
*/
const TYPE_OPT = 41;
const TYPE_ANY = 255;
const TYPE_CAA = 257;

Expand All @@ -37,6 +50,26 @@ final class Message
const RCODE_NOT_IMPLEMENTED = 4;
const RCODE_REFUSED = 5;

/**
* The edns-tcp-keepalive EDNS0 Option
*
* Option value contains a `?float` with timeout in seconds (in 0.1s steps)
* for DNS response or `null` for DNS query.
*
* @link https://tools.ietf.org/html/rfc7828
*/
const OPT_TCP_KEEPALIVE = 11;

/**
* The EDNS(0) Padding Option
*
* Option value contains a `string` with binary data (usually variable
* number of null bytes)
*
* @link https://tools.ietf.org/html/rfc7830
*/
const OPT_PADDING = 12;

/**
* Creates a new request message for the given query
*
Expand Down
21 changes: 21 additions & 0 deletions src/Model/Record.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,23 @@ final class Record
public $type;

/**
* Defines the network class, usually `Message::CLASS_IN`.
*
* For `OPT` records (EDNS0), this defines the maximum message size instead.
*
* @var int see Message::CLASS_IN constant (UINT16)
* @see Message::CLASS_IN
*/
public $class;

/**
* Defines the maximum time-to-live (TTL) in seconds
*
* For `OPT` records (EDNS0), this defines additional flags instead.
*
* @var int maximum TTL in seconds (UINT32, most significant bit always unset)
* @link https://tools.ietf.org/html/rfc2181#section-8
* @link https://tools.ietf.org/html/rfc6891#section-6.1.3 for `OPT` records (EDNS0)
*/
public $ttl;

Expand Down Expand Up @@ -102,6 +112,17 @@ final class Record
* Includes flag (UNIT8), tag string and value string, for example:
* `{"flag":128,"tag":"issue","value":"letsencrypt.org"}`
*
* - OPT:
* Special pseudo-type for EDNS0. Includes an array of additional opt codes
* with a value according to the respective OPT code. See `Message::OPT_*`
* for list of supported OPT codes. Any other OPT code not currently
* supported will be an opaque binary string containing the raw data
* as transported in the DNS record. For forwards compatibility, you should
* not rely on this format for unknown types. Future versions may add
* support for new types and this may then parse the payload data
* appropriately - this will not be considered a BC break. See also
* [RFC 6891](https://tools.ietf.org/html/rfc6891) for more details.
*
* - Any other unknown type:
* An opaque binary string containing the RDATA as transported in the DNS
* record. For forwards compatibility, you should not rely on this format
Expand Down
9 changes: 9 additions & 0 deletions src/Protocol/BinaryDumper.php
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,15 @@ private function recordsToBinary(array $records)
$record->data['fingerprint']
);
break;
case Message::TYPE_OPT:
$binary = '';
foreach ($record->data as $opt => $value) {
if ($opt === Message::OPT_TCP_KEEPALIVE && $value !== null) {
$value = \pack('n', round($value * 10));
}
$binary .= \pack('n*', $opt, \strlen($value)) . $value;
}
break;
default:
// RDATA is already stored as binary value for unknown record types
$binary = $record->data;
Expand Down
16 changes: 16 additions & 0 deletions src/Protocol/Parser.php
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,22 @@ private function parseRecord(Message $message)
'minimum' => $minimum
);
}
} elseif (Message::TYPE_OPT === $type) {
$rdata = array();
while (isset($message->data[$consumed + 4 - 1])) {
list($code, $length) = array_values(unpack('n*', substr($message->data, $consumed, 4)));
$value = (string) substr($message->data, $consumed + 4, $length);
if ($code === Message::OPT_TCP_KEEPALIVE && $value === '') {
$value = null;
} elseif ($code === Message::OPT_TCP_KEEPALIVE && $length === 2) {
list($value) = array_values(unpack('n', $value));
$value = round($value * 0.1, 1);
} elseif ($code === Message::OPT_TCP_KEEPALIVE) {
break;
}
$rdata[$code] = $value;
$consumed += 4 + $length;
}
} elseif (Message::TYPE_CAA === $type) {
if ($rdLength > 3) {
list($flag, $tagLength) = array_values(unpack('C*', substr($message->data, $consumed, 2)));
Expand Down
170 changes: 167 additions & 3 deletions tests/Protocol/BinaryDumperTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -36,14 +36,108 @@ public function testToBinaryRequestMessage()
$this->assertSame($expected, $data);
}

public function testToBinaryRequestMessageWithCustomOptForEdns0()
public function testToBinaryRequestMessageWithUnknownAuthorityTypeEncodesValueAsBinary()
{
$data = "";
$data .= "72 62 01 00 00 01 00 00 00 01 00 00"; // header
$data .= "04 69 67 6f 72 02 69 6f 00"; // question: igor.io
$data .= "00 01 00 01"; // question: type A, class IN
$data .= "00"; // additional: (empty hostname)
$data .= "d4 31 03 e8 00 00 00 00 00 02 01 02 ";// additional: type OPT, class 1000, TTL 0, binary rdata

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

$request = new Message();
$request->id = 0x7262;
$request->rd = true;

$request->questions[] = new Query(
'igor.io',
Message::TYPE_A,
Message::CLASS_IN
);

$request->authority[] = new Record('', 54321, 1000, 0, "\x01\x02");

$dumper = new BinaryDumper();
$data = $dumper->toBinary($request);
$data = $this->convertBinaryToHexDump($data);

$this->assertSame($expected, $data);
}

public function testToBinaryRequestMessageWithAdditionalOptForEdns0()
{
$data = "";
$data .= "72 62 01 00 00 01 00 00 00 00 00 01"; // header
$data .= "04 69 67 6f 72 02 69 6f 00"; // question: igor.io
$data .= "00 01 00 01"; // question: type A, class IN
$data .= "00"; // additional: (empty hostname)
$data .= "00 29 03 e8 00 00 00 00 00 00 "; // additional: type OPT, class 1000 UDP size, TTL 0, no RDATA

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

$request = new Message();
$request->id = 0x7262;
$request->rd = true;

$request->questions[] = new Query(
'igor.io',
Message::TYPE_A,
Message::CLASS_IN
);

$request->additional[] = new Record('', Message::TYPE_OPT, 1000, 0, array());

$dumper = new BinaryDumper();
$data = $dumper->toBinary($request);
$data = $this->convertBinaryToHexDump($data);

$this->assertSame($expected, $data);
}

public function testToBinaryRequestMessageWithAdditionalOptForEdns0WithOptTcpKeepAliveDesired()
{
$data = "";
$data .= "72 62 01 00 00 01 00 00 00 00 00 01"; // header
$data .= "04 69 67 6f 72 02 69 6f 00"; // question: igor.io
$data .= "00 01 00 01"; // question: type A, class IN
$data .= "00"; // additional: (empty hostname)
$data .= "00 29 03 e8 00 00 00 00 00 04 "; // additional: type OPT, class 1000 UDP size, TTL 0, 4 bytes RDATA
$data .= "00 0b 00 00"; // OPT_TCP_KEEPALIVE=null encoded

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

$request = new Message();
$request->id = 0x7262;
$request->rd = true;

$request->questions[] = new Query(
'igor.io',
Message::TYPE_A,
Message::CLASS_IN
);

$request->additional[] = new Record('', Message::TYPE_OPT, 1000, 0, array(
Message::OPT_TCP_KEEPALIVE => null,
));

$dumper = new BinaryDumper();
$data = $dumper->toBinary($request);
$data = $this->convertBinaryToHexDump($data);

$this->assertSame($expected, $data);
}

public function testToBinaryRequestMessageWithAdditionalOptForEdns0WithOptTcpKeepAliveGiven()
{
$data = "";
$data .= "72 62 01 00 00 01 00 00 00 00 00 01"; // header
$data .= "04 69 67 6f 72 02 69 6f 00"; // question: igor.io
$data .= "00 01 00 01"; // question: type A, class IN
$data .= "00"; // additional: (empty hostname)
$data .= "00 29 03 e8 00 00 00 00 00 00 "; // additional: type OPT, class UDP size, TTL 0, no RDATA
$data .= "00 29 03 e8 00 00 00 00 00 06 "; // additional: type OPT, class 1000 UDP size, TTL 0, 6 bytes RDATA
$data .= "00 0b 00 02 00 0c"; // OPT_TCP_KEEPALIVE=1.2 encoded

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

Expand All @@ -57,7 +151,77 @@ public function testToBinaryRequestMessageWithCustomOptForEdns0()
Message::CLASS_IN
);

$request->additional[] = new Record('', 41, 1000, 0, '');
$request->additional[] = new Record('', Message::TYPE_OPT, 1000, 0, array(
Message::OPT_TCP_KEEPALIVE => 1.2,
));

$dumper = new BinaryDumper();
$data = $dumper->toBinary($request);
$data = $this->convertBinaryToHexDump($data);

$this->assertSame($expected, $data);
}

public function testToBinaryRequestMessageWithAdditionalOptForEdns0WithOptPadding()
{
$data = "";
$data .= "72 62 01 00 00 01 00 00 00 00 00 01"; // header
$data .= "04 69 67 6f 72 02 69 6f 00"; // question: igor.io
$data .= "00 01 00 01"; // question: type A, class IN
$data .= "00"; // additional: (empty hostname)
$data .= "00 29 03 e8 00 00 00 00 00 06 "; // additional: type OPT, class 1000 UDP size, TTL 0, 6 bytes RDATA
$data .= "00 0c 00 02 00 00 "; // OPT_PADDING=0x0000 encoded

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

$request = new Message();
$request->id = 0x7262;
$request->rd = true;

$request->questions[] = new Query(
'igor.io',
Message::TYPE_A,
Message::CLASS_IN
);

$request->additional[] = new Record('', Message::TYPE_OPT, 1000, 0, array(
Message::OPT_PADDING => "\x00\x00"
));

$dumper = new BinaryDumper();
$data = $dumper->toBinary($request);
$data = $this->convertBinaryToHexDump($data);

$this->assertSame($expected, $data);
}

public function testToBinaryRequestMessageWithAdditionalOptForEdns0WithCustomOptCodes()
{
$data = "";
$data .= "72 62 01 00 00 01 00 00 00 00 00 01"; // header
$data .= "04 69 67 6f 72 02 69 6f 00"; // question: igor.io
$data .= "00 01 00 01"; // question: type A, class IN
$data .= "00"; // additional: (empty hostname)
$data .= "00 29 03 e8 00 00 00 00 00 0d "; // additional: type OPT, class 1000 UDP size, TTL 0, 13 bytes RDATA
$data .= "00 a0 00 03 66 6f 6f"; // OPT code 0xa0 encoded
$data .= "00 01 00 02 00 00 "; // OPT code 0x01 encoded

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

$request = new Message();
$request->id = 0x7262;
$request->rd = true;

$request->questions[] = new Query(
'igor.io',
Message::TYPE_A,
Message::CLASS_IN
);

$request->additional[] = new Record('', Message::TYPE_OPT, 1000, 0, array(
0xa0 => 'foo',
0x01 => "\x00\x00"
));

$dumper = new BinaryDumper();
$data = $dumper->toBinary($request);
Expand Down
Loading