Skip to content

Commit de8e83d

Browse files
committed
Support reverse DNS lookups for HostsFileExecutor
1 parent 5a3e0fa commit de8e83d

2 files changed

Lines changed: 88 additions & 0 deletions

File tree

src/Query/HostsFileExecutor.php

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ public function __construct(HostsFile $hosts, ExecutorInterface $fallback)
2828
public function query($nameserver, Query $query)
2929
{
3030
if ($query->class === Message::CLASS_IN && ($query->type === Message::TYPE_A || $query->type === Message::TYPE_AAAA)) {
31+
// forward lookup for type A or AAAA
3132
$records = array();
3233
$expectsColon = $query->type === Message::TYPE_AAAA;
3334
foreach ($this->hosts->getIpsForHost($query->name) as $ip) {
@@ -42,8 +43,47 @@ public function query($nameserver, Query $query)
4243
Message::createResponseWithAnswersForQuery($query, $records)
4344
);
4445
}
46+
} elseif ($query->class === Message::CLASS_IN && $query->type === Message::TYPE_PTR) {
47+
// reverse lookup: extract IPv4 or IPv6 from special `.arpa` domain
48+
$ip = $this->getIpFromHost($query->name);
49+
50+
if ($ip !== null) {
51+
$records = array();
52+
foreach ($this->hosts->getHostsForIp($ip) as $host) {
53+
$records[] = new Record($query->name, $query->type, $query->class, 0, $host);
54+
}
55+
56+
if ($records) {
57+
return Promise\resolve(
58+
Message::createResponseWithAnswersForQuery($query, $records)
59+
);
60+
}
61+
}
4562
}
4663

4764
return $this->fallback->query($nameserver, $query);
4865
}
66+
67+
private function getIpFromHost($host)
68+
{
69+
if (substr($host, -13) === '.in-addr.arpa') {
70+
// IPv4: read as IP and reverse bytes
71+
$ip = @inet_pton(substr($host, 0, -13));
72+
if ($ip === false || isset($ip[4])) {
73+
return null;
74+
}
75+
76+
return inet_ntop(strrev($ip));
77+
} elseif (substr($host, -9) === '.ip6.arpa') {
78+
// IPv6: replace dots, reverse nibbles and interpret as hexadecimal string
79+
$ip = @inet_ntop(hex2bin(strrev(str_replace('.', '', substr($host, 0, -9)))));
80+
if ($ip === false) {
81+
return null;
82+
}
83+
84+
return $ip;
85+
} else {
86+
return null;
87+
}
88+
}
4989
}

tests/Query/HostsFileExecutorTest.php

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,4 +67,52 @@ public function testFallsBackIfNoIpv6Matches()
6767

6868
$ret = $this->executor->query('8.8.8.8', new Query('google.com', Message::TYPE_AAAA, Message::CLASS_IN, 0));
6969
}
70+
71+
public function testDoesReturnReverseIpv4Lookup()
72+
{
73+
$this->hosts->expects($this->once())->method('getHostsForIp')->with('127.0.0.1')->willReturn(array('localhost'));
74+
$this->fallback->expects($this->never())->method('query');
75+
76+
$this->executor->query('8.8.8.8', new Query('1.0.0.127.in-addr.arpa', Message::TYPE_PTR, Message::CLASS_IN, 0));
77+
}
78+
79+
public function testFallsBackIfNoReverseIpv4Matches()
80+
{
81+
$this->hosts->expects($this->once())->method('getHostsForIp')->with('127.0.0.1')->willReturn(array());
82+
$this->fallback->expects($this->once())->method('query');
83+
84+
$this->executor->query('8.8.8.8', new Query('1.0.0.127.in-addr.arpa', Message::TYPE_PTR, Message::CLASS_IN, 0));
85+
}
86+
87+
public function testDoesReturnReverseIpv6Lookup()
88+
{
89+
$this->hosts->expects($this->once())->method('getHostsForIp')->with('2a02:2e0:3fe:100::6')->willReturn(array('ip6-localhost'));
90+
$this->fallback->expects($this->never())->method('query');
91+
92+
$this->executor->query('8.8.8.8', new Query('6.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.1.0.e.f.3.0.0.e.2.0.2.0.a.2.ip6.arpa', Message::TYPE_PTR, Message::CLASS_IN, 0));
93+
}
94+
95+
public function testFallsBackForInvalidAddress()
96+
{
97+
$this->hosts->expects($this->never())->method('getHostsForIp');
98+
$this->fallback->expects($this->once())->method('query');
99+
100+
$this->executor->query('8.8.8.8', new Query('example.com', Message::TYPE_PTR, Message::CLASS_IN, 0));
101+
}
102+
103+
public function testReverseFallsBackForInvalidIpv4Address()
104+
{
105+
$this->hosts->expects($this->never())->method('getHostsForIp');
106+
$this->fallback->expects($this->once())->method('query');
107+
108+
$this->executor->query('8.8.8.8', new Query('::1.in-addr.arpa', Message::TYPE_PTR, Message::CLASS_IN, 0));
109+
}
110+
111+
public function testReverseFallsBackForInvalidIpv6Address()
112+
{
113+
$this->hosts->expects($this->never())->method('getHostsForIp');
114+
$this->fallback->expects($this->once())->method('query');
115+
116+
$this->executor->query('8.8.8.8', new Query('abcd.ip6.arpa', Message::TYPE_PTR, Message::CLASS_IN, 0));
117+
}
70118
}

0 commit comments

Comments
 (0)