Skip to content

Commit 6f2f761

Browse files
authored
Merge pull request #116 from clue-labs/ttl
Fix interpretation of TTL as UINT32 with most significant bit unset
2 parents 6638313 + 9952957 commit 6f2f761

3 files changed

Lines changed: 86 additions & 2 deletions

File tree

src/Model/Record.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,8 @@ class Record
2020
public $class;
2121

2222
/**
23-
* @var int maximum TTL in seconds (UINT16)
23+
* @var int maximum TTL in seconds (UINT32, most significant bit always unset)
24+
* @link https://tools.ietf.org/html/rfc2181#section-8
2425
*/
2526
public $ttl;
2627

src/Protocol/Parser.php

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,11 @@ public function parseAnswer(Message $message)
155155
list($ttl) = array_values(unpack('N', substr($message->data, $consumed, 4)));
156156
$consumed += 4;
157157

158+
// TTL is a UINT32 that must not have most significant bit set for BC reasons
159+
if ($ttl < 0 || $ttl >= 1 << 31) {
160+
$ttl = 0;
161+
}
162+
158163
list($rdLength) = array_values(unpack('n', substr($message->data, $consumed, 2)));
159164
$consumed += 2;
160165

@@ -220,7 +225,6 @@ public function parseAnswer(Message $message)
220225
$message->consumed = $consumed;
221226

222227
$name = implode('.', $labels);
223-
$ttl = $this->signedLongToUnsignedLong($ttl);
224228
$record = new Record($name, $type, $class, $ttl, $rdata);
225229

226230
$message->answers[] = $record;
@@ -324,6 +328,10 @@ public function getCompressedLabelOffset($data, $consumed)
324328
return array($peek & $mask, $consumed + 2);
325329
}
326330

331+
/**
332+
* @deprecated unused, exists for BC only
333+
* @codeCoverageIgnore
334+
*/
327335
public function signedLongToUnsignedLong($i)
328336
{
329337
return $i & 0x80000000 ? $i - 0xffffffff : $i;

tests/Protocol/ParserTest.php

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,81 @@ public function testParseAnswerWithInlineData()
157157
$this->assertSame('178.79.169.131', $response->answers[0]->data);
158158
}
159159

160+
public function testParseAnswerWithExcessiveTtlReturnsZeroTtl()
161+
{
162+
$data = "";
163+
$data .= "04 69 67 6f 72 02 69 6f 00"; // answer: igor.io
164+
$data .= "00 01 00 01"; // answer: type A, class IN
165+
$data .= "ff ff ff ff"; // answer: ttl 2^32 - 1
166+
$data .= "00 04"; // answer: rdlength 4
167+
$data .= "b2 4f a9 83"; // answer: rdata 178.79.169.131
168+
169+
$data = $this->convertTcpDumpToBinary($data);
170+
171+
$response = new Message();
172+
$response->header->set('anCount', 1);
173+
$response->data = $data;
174+
175+
$this->parser->parseAnswer($response);
176+
177+
$this->assertCount(1, $response->answers);
178+
$this->assertSame('igor.io', $response->answers[0]->name);
179+
$this->assertSame(Message::TYPE_A, $response->answers[0]->type);
180+
$this->assertSame(Message::CLASS_IN, $response->answers[0]->class);
181+
$this->assertSame(0, $response->answers[0]->ttl);
182+
$this->assertSame('178.79.169.131', $response->answers[0]->data);
183+
}
184+
185+
public function testParseAnswerWithTtlExactlyBoundaryReturnsZeroTtl()
186+
{
187+
$data = "";
188+
$data .= "04 69 67 6f 72 02 69 6f 00"; // answer: igor.io
189+
$data .= "00 01 00 01"; // answer: type A, class IN
190+
$data .= "80 00 00 00"; // answer: ttl 2^31
191+
$data .= "00 04"; // answer: rdlength 4
192+
$data .= "b2 4f a9 83"; // answer: rdata 178.79.169.131
193+
194+
$data = $this->convertTcpDumpToBinary($data);
195+
196+
$response = new Message();
197+
$response->header->set('anCount', 1);
198+
$response->data = $data;
199+
200+
$this->parser->parseAnswer($response);
201+
202+
$this->assertCount(1, $response->answers);
203+
$this->assertSame('igor.io', $response->answers[0]->name);
204+
$this->assertSame(Message::TYPE_A, $response->answers[0]->type);
205+
$this->assertSame(Message::CLASS_IN, $response->answers[0]->class);
206+
$this->assertSame(0, $response->answers[0]->ttl);
207+
$this->assertSame('178.79.169.131', $response->answers[0]->data);
208+
}
209+
210+
public function testParseAnswerWithMaximumTtlReturnsExactTtl()
211+
{
212+
$data = "";
213+
$data .= "04 69 67 6f 72 02 69 6f 00"; // answer: igor.io
214+
$data .= "00 01 00 01"; // answer: type A, class IN
215+
$data .= "7f ff ff ff"; // answer: ttl 2^31 - 1
216+
$data .= "00 04"; // answer: rdlength 4
217+
$data .= "b2 4f a9 83"; // answer: rdata 178.79.169.131
218+
219+
$data = $this->convertTcpDumpToBinary($data);
220+
221+
$response = new Message();
222+
$response->header->set('anCount', 1);
223+
$response->data = $data;
224+
225+
$this->parser->parseAnswer($response);
226+
227+
$this->assertCount(1, $response->answers);
228+
$this->assertSame('igor.io', $response->answers[0]->name);
229+
$this->assertSame(Message::TYPE_A, $response->answers[0]->type);
230+
$this->assertSame(Message::CLASS_IN, $response->answers[0]->class);
231+
$this->assertSame(0x7fffffff, $response->answers[0]->ttl);
232+
$this->assertSame('178.79.169.131', $response->answers[0]->data);
233+
}
234+
160235
public function testParseAnswerWithUnknownType()
161236
{
162237
$data = "";

0 commit comments

Comments
 (0)