Skip to content

Commit a25393b

Browse files
authored
Dns plaintext - return all A/AAAA records (#85)
* better dns resolving - return all A/AAAA records * update * csfixer
1 parent de732f6 commit a25393b

6 files changed

Lines changed: 154 additions & 167 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
* feature: Add configurable test timeout
1313
* feature: Add Servcheck graph template
1414
* issue: Better themes support
15+
* issue: Better plaintext DNS - return all A and AAAA records
1516

1617
--- 0.3 ---
1718

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ HTTP and HTTPS - returns webpage
3535
SMTP, SMTPS - connect to SMTP server and displays banner and EHLO/HELO or authentication result
3636
POP3, POP3S - try to server connection and eventually login and display first unread message
3737
IMAP, IMAPS - try to server connection and eventually login and display unread messages in inbox
38-
DNS, DOH - try to resolve A record and return answer
38+
DNS, DOH - try to resolve A (and AAAA for plaintext dns) record and return answer
3939
LDAP and LDAPS - do searching in LDAP
4040
FTP, FTPS, SFTP - try to login and return directory listing
4141
TFTP - try to download specific file

includes/arrays.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -467,7 +467,7 @@
467467
'dns_query' => [
468468
'method' => 'textbox',
469469
'friendly_name' => __('DNS Name for Query', 'servcheck'),
470-
'description' => __('DNS name for querying. For DOH (DNS over HTTPS) use /resolve?name=example.com or /dns-query?name=example.com&type=A', 'servcheck'),
470+
'description' => __('DNS name for querying. For DOH (DNS over HTTPS) use /resolve?name=example.com or /dns-query?name=example.com&type=A. For plaintext DNS A and AAAA records will be returned', 'servcheck'),
471471
'value' => '|arg1:dns_query|',
472472
'max_length' => '40',
473473
'size' => '30'

includes/dnslookup.php

Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
<?php
2+
/*
3+
+-------------------------------------------------------------------------+
4+
| Copyright (C) 2004-2026 The Cacti Group |
5+
| |
6+
| This program is free software; you can redistribute it and/or |
7+
| modify it under the terms of the GNU General Public License |
8+
| as published by the Free Software Foundation; either version 2 |
9+
| of the License, or (at your option) any later version. |
10+
| |
11+
| This program is distributed in the hope that it will be useful, |
12+
| but WITHOUT ANY WARRANTY; without even the implied warranty of |
13+
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
14+
| GNU General Public License for more details. |
15+
+-------------------------------------------------------------------------+
16+
| Cacti: The Complete RRDtool-based Graphing Solution |
17+
+-------------------------------------------------------------------------+
18+
| This code is designed, written, and maintained by the Cacti Group. See |
19+
| about.php and/or the AUTHORS file for specific developer information. |
20+
+-------------------------------------------------------------------------+
21+
| http://www.cacti.net/ |
22+
+-------------------------------------------------------------------------+
23+
*/
24+
25+
class dnslookup {
26+
private $dns_reply = '';
27+
private $cIx = 0;
28+
private $results = [];
29+
30+
function __construct($domain, $dns = '8.8.8.8', $timeout = 5) {
31+
$this->dns_query($domain, 1, $dns, $timeout, 'A');
32+
$this->dns_query($domain, 28, $dns, $timeout, 'AAAA');
33+
}
34+
35+
public function get_results($format = 'text') {
36+
$output = '';
37+
38+
if (empty($this->results)) {
39+
return false;
40+
}
41+
42+
foreach ($this->results as $type => $ips) {
43+
foreach ($ips as $ip) {
44+
$output .= " $ip\n";
45+
}
46+
$output .= "\n";
47+
}
48+
49+
return $output;
50+
}
51+
52+
private function dns_query($domain, $qtype, $dns, $timeout, $type) {
53+
$header = chr(0x12) . chr(0x34) . chr(0x01) . chr(0x00) . chr(0x00) . chr(0x01) .
54+
chr(0x00) . chr(0x00) . chr(0x00) . chr(0x00) . chr(0x00) . chr(0x00);
55+
56+
$packet = $header . $this->dns_name($domain) .
57+
chr(0x00) . chr($qtype) . chr(0x00) . chr(0x01);
58+
59+
$socket = @fsockopen("udp://$dns", 53, $errno, $errstr, $timeout);
60+
61+
if (!$socket) {
62+
return false;
63+
}
64+
65+
fwrite($socket, $packet);
66+
stream_set_timeout($socket, $timeout);
67+
$this->dns_reply = fread($socket, 512);
68+
fclose($socket);
69+
70+
$len = strlen($this->dns_reply);
71+
72+
if ($len > 12) {
73+
$this->cIx = 12;
74+
$this->parse_response($type, $len);
75+
}
76+
}
77+
78+
private function dns_name($domain) {
79+
$parts = explode('.', $domain);
80+
$name = '';
81+
82+
foreach ($parts as $part) {
83+
$len = strlen($part);
84+
$name .= chr($len);
85+
$name .= $part;
86+
}
87+
88+
return $name . chr(0);
89+
}
90+
91+
private function parse_response($type_name, $reply_len) {
92+
// Skip question
93+
$max_skip = min(255, $reply_len - $this->cIx);
94+
95+
for ($i = 0; $i < $max_skip; $i++) {
96+
if (ord($this->dns_reply[$this->cIx]) == 0) {
97+
$this->cIx++;
98+
99+
break;
100+
}
101+
$this->cIx++;
102+
}
103+
$this->cIx += 4;
104+
105+
$ancount = ord($this->dns_reply[7]) * 256 + ord($this->dns_reply[8]);
106+
107+
for ($i = 0; $i < min($ancount, 10); $i++) {
108+
if ($this->cIx + 12 > $reply_len) {
109+
break;
110+
}
111+
112+
$this->cIx += 2; // name pointer
113+
$type = ord($this->dns_reply[$this->cIx]) * 256 + ord($this->dns_reply[$this->cIx + 1]);
114+
$this->cIx += 8; // CLASS + TTL
115+
$rdlen = ord($this->dns_reply[$this->cIx]) * 256 + ord($this->dns_reply[$this->cIx + 1]);
116+
$this->cIx += 2;
117+
118+
if ($this->cIx + $rdlen > $reply_len) {
119+
break;
120+
}
121+
122+
if ($type == 1 && $rdlen == 4) {
123+
// IPv4
124+
$ip = ord($this->dns_reply[$this->cIx]) . '.' .
125+
ord($this->dns_reply[$this->cIx + 1]) . '.' .
126+
ord($this->dns_reply[$this->cIx + 2]) . '.' .
127+
ord($this->dns_reply[$this->cIx + 3]);
128+
$this->results['A'][] = $ip;
129+
} elseif ($type == 28 && $rdlen == 16) {
130+
// IPv6
131+
$ipv6 = '';
132+
133+
for ($j = 0; $j < 16; $j += 2) {
134+
$byte1 = ord($this->dns_reply[$this->cIx + $j]);
135+
$byte2 = ord($this->dns_reply[$this->cIx + $j + 1]);
136+
$ipv6 .= sprintf('%02x%02x', $byte1, $byte2);
137+
138+
if ($j < 14) {
139+
$ipv6 .= ':';
140+
}
141+
}
142+
$this->results['AAAA'][] = $ipv6;
143+
}
144+
$this->cIx += $rdlen;
145+
}
146+
}
147+
}

includes/mxlookup.php

Lines changed: 0 additions & 159 deletions
This file was deleted.

includes/test_dns.php

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
*/
2424

2525
function dns_try($test) {
26-
include_once(__DIR__ . '/../includes/mxlookup.php');
26+
include_once(__DIR__ . '/../includes/dnslookup.php');
2727

2828
// default result
2929
$results['result'] = 'error';
@@ -38,18 +38,16 @@ function dns_try($test) {
3838

3939
servcheck_debug('Querying ' . $test['hostname'] . ' for record ' . $test['dns_query']);
4040

41-
$a = new mxlookup($test['dns_query'], $test['hostname'], $test['duration_trigger'] > 0 ? ($test['duration_trigger'] + 2) : read_config_option('servcheck_test_max_duration'));
41+
$a = new dnslookup($test['dns_query'], $test['hostname'], $test['duration_trigger'] > 0 ? ($test['duration_trigger'] + 2) : read_config_option('servcheck_test_max_duration'));
4242

43-
if (!cacti_sizeof($a->arrMX)) {
43+
if ($a === false) {
4444
$results['result'] = 'error';
4545
$results['error'] = 'Server did not respond';
4646
$results['result_search'] = 'not tested';
4747

4848
servcheck_debug('Test failed: ' . $results['error']);
4949
} else {
50-
foreach ($a->arrMX as $m) {
51-
$results['data'] .= "$m\n";
52-
}
50+
$results['data'] .= $a->get_results();
5351

5452
$results['result'] = 'ok';
5553
$results['error'] = 'Some data returned';

0 commit comments

Comments
 (0)