Skip to content

Commit 1cbf82f

Browse files
committed
Respect record TTL values from RRset for cache TTL
1 parent 41229e7 commit 1cbf82f

2 files changed

Lines changed: 52 additions & 7 deletions

File tree

src/Query/CachingExecutor.php

Lines changed: 29 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,7 @@
99
class CachingExecutor implements ExecutorInterface
1010
{
1111
/**
12-
* Initial implementation uses a fixed TTL for postive DNS responses as well
13-
* as negative responses (NXDOMAIN etc.).
12+
* Default TTL for negative responses (NXDOMAIN etc.).
1413
*
1514
* @internal
1615
*/
@@ -29,23 +28,24 @@ public function query($nameserver, Query $query)
2928
{
3029
$id = $query->name . ':' . $query->type . ':' . $query->class;
3130
$cache = $this->cache;
31+
$that = $this;
3232
$executor = $this->executor;
3333

3434
$pending = $cache->get($id);
35-
return new Promise(function ($resolve, $reject) use ($nameserver, $query, $id, $cache, $executor, &$pending) {
35+
return new Promise(function ($resolve, $reject) use ($nameserver, $query, $id, $cache, $executor, &$pending, $that) {
3636
$pending->then(
37-
function ($message) use ($nameserver, $query, $id, $cache, $executor, &$pending) {
37+
function ($message) use ($nameserver, $query, $id, $cache, $executor, &$pending, $that) {
3838
// return cached response message on cache hit
3939
if ($message !== null) {
4040
return $message;
4141
}
4242

4343
// perform DNS lookup if not already cached
4444
return $pending = $executor->query($nameserver, $query)->then(
45-
function (Message $message) use ($cache, $id) {
45+
function (Message $message) use ($cache, $id, $that) {
4646
// DNS response message received => store in cache when not truncated and return
4747
if (!$message->header->isTruncated()) {
48-
$cache->set($id, $message, CachingExecutor::TTL);
48+
$cache->set($id, $message, $that->ttl($message));
4949
}
5050

5151
return $message;
@@ -58,4 +58,27 @@ function (Message $message) use ($cache, $id) {
5858
$pending->cancel();
5959
});
6060
}
61+
62+
/**
63+
* @param Message $message
64+
* @return int
65+
* @internal
66+
*/
67+
public function ttl(Message $message)
68+
{
69+
// select TTL from answers (should all be the same), use smallest value if available
70+
// @link https://tools.ietf.org/html/rfc2181#section-5.2
71+
$ttl = null;
72+
foreach ($message->answers as $answer) {
73+
if ($ttl === null || $answer->ttl < $ttl) {
74+
$ttl = $answer->ttl;
75+
}
76+
}
77+
78+
if ($ttl === null) {
79+
$ttl = self::TTL;
80+
}
81+
82+
return $ttl;
83+
}
6184
}

tests/Query/CachingExecutorTest.php

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
use React\Promise\Promise;
99
use React\Tests\Dns\TestCase;
1010
use React\Promise\Deferred;
11+
use React\Dns\Model\Record;
1112

1213
class CachingExecutorTest extends TestCase
1314
{
@@ -63,7 +64,28 @@ public function testQueryWillReturnResolvedPromiseWhenCacheReturnsHitWithoutSend
6364
$promise->then($this->expectCallableOnceWith($message), $this->expectCallableNever());
6465
}
6566

66-
public function testQueryWillReturnResolvedPromiseWhenCacheReturnsMissAndFallbackExecutorResolvesAndSaveMessageToCache()
67+
public function testQueryWillReturnResolvedPromiseWhenCacheReturnsMissAndFallbackExecutorResolvesAndSaveMessageToCacheWithMinimumTtlFromRecord()
68+
{
69+
$message = new Message();
70+
$message->answers[] = new Record('reactphp.org', Message::TYPE_A, Message::CLASS_IN, 3700, '127.0.0.1');
71+
$message->answers[] = new Record('reactphp.org', Message::TYPE_A, Message::CLASS_IN, 3600, '127.0.0.1');
72+
$fallback = $this->getMockBuilder('React\Dns\Query\ExecutorInterface')->getMock();
73+
$fallback->expects($this->once())->method('query')->willReturn(\React\Promise\resolve($message));
74+
75+
$cache = $this->getMockBuilder('React\Cache\CacheInterface')->getMock();
76+
$cache->expects($this->once())->method('get')->with('reactphp.org:1:1')->willReturn(\React\Promise\resolve(null));
77+
$cache->expects($this->once())->method('set')->with('reactphp.org:1:1', $message, 3600);
78+
79+
$executor = new CachingExecutor($fallback, $cache);
80+
81+
$query = new Query('reactphp.org', Message::TYPE_A, Message::CLASS_IN);
82+
83+
$promise = $executor->query('8.8.8.8', $query);
84+
85+
$promise->then($this->expectCallableOnceWith($message), $this->expectCallableNever());
86+
}
87+
88+
public function testQueryWillReturnResolvedPromiseWhenCacheReturnsMissAndFallbackExecutorResolvesAndSaveMessageToCacheWithDefaultTtl()
6789
{
6890
$message = new Message();
6991
$fallback = $this->getMockBuilder('React\Dns\Query\ExecutorInterface')->getMock();

0 commit comments

Comments
 (0)