]> git.ipfire.org Git - thirdparty/pdns.git/blob - pdns/sdig.cc
Test now uses rec_control, so supply location of the executable
[thirdparty/pdns.git] / pdns / sdig.cc
1 #ifdef HAVE_CONFIG_H
2 #include "config.h"
3 #endif
4 #include "dnsparser.hh"
5 #include "dnsrecords.hh"
6 #include "dnswriter.hh"
7 #include "ednsoptions.hh"
8 #include "ednssubnet.hh"
9 #include "misc.hh"
10 #include "sstuff.hh"
11 #include "statbag.hh"
12 #include <boost/array.hpp>
13
14 #ifdef HAVE_LIBCURL
15 #include "minicurl.hh"
16 #endif
17
18 StatBag S;
19
20 bool hidettl = false;
21
22 string ttl(uint32_t ttl)
23 {
24 if (hidettl)
25 return "[ttl]";
26 else
27 return std::to_string(ttl);
28 }
29
30 void usage()
31 {
32 cerr << "sdig" << endl;
33 cerr << "Syntax: sdig IP-ADDRESS-OR-DOH-URL PORT QNAME QTYPE "
34 "[dnssec] [ednssubnet SUBNET/MASK] [hidesoadetails] [hidettl] "
35 "[recurse] [showflags] [tcp] [xpf XPFDATA] [class CLASSNUM]"
36 << endl;
37 }
38
39 const string nameForClass(uint16_t qclass, uint16_t qtype)
40 {
41 if (qtype == QType::OPT)
42 return "IN";
43
44 switch (qclass) {
45 case QClass::IN:
46 return "IN";
47 case QClass::CHAOS:
48 return "CHAOS";
49 case QClass::NONE:
50 return "NONE";
51 case QClass::ANY:
52 return "ANY";
53 default:
54 return string("CLASS") + std::to_string(qclass);
55 }
56 }
57
58 void fillPacket(vector<uint8_t>& packet, const string& q, const string& t,
59 bool dnssec, const boost::optional<Netmask> ednsnm,
60 bool recurse, uint16_t xpfcode, uint16_t xpfversion,
61 uint64_t xpfproto, char* xpfsrc, char* xpfdst,
62 uint16_t qclass)
63 {
64 DNSPacketWriter pw(packet, DNSName(q), DNSRecordContent::TypeToNumber(t), qclass);
65
66 if (dnssec || ednsnm || getenv("SDIGBUFSIZE")) {
67 char* sbuf = getenv("SDIGBUFSIZE");
68 int bufsize;
69 if (sbuf)
70 bufsize = atoi(sbuf);
71 else
72 bufsize = 2800;
73 DNSPacketWriter::optvect_t opts;
74 if (ednsnm) {
75 EDNSSubnetOpts eo;
76 eo.source = *ednsnm;
77 opts.push_back(
78 make_pair(EDNSOptionCode::ECS, makeEDNSSubnetOptsString(eo)));
79 }
80
81 pw.addOpt(bufsize, 0, dnssec ? EDNSOpts::DNSSECOK : 0, opts);
82 pw.commit();
83 }
84
85 if (xpfcode) {
86 ComboAddress src(xpfsrc), dst(xpfdst);
87 pw.startRecord(g_rootdnsname, xpfcode, 0, QClass::IN, DNSResourceRecord::ADDITIONAL);
88 // xpf->toPacket(pw);
89 pw.xfr8BitInt(xpfversion);
90 pw.xfr8BitInt(xpfproto);
91 pw.xfrCAWithoutPort(xpfversion, src);
92 pw.xfrCAWithoutPort(xpfversion, dst);
93 pw.xfrCAPort(src);
94 pw.xfrCAPort(dst);
95 pw.commit();
96 }
97
98 if (recurse) {
99 pw.getHeader()->rd = true;
100 }
101 }
102
103 void printReply(const string& reply, bool showflags, bool hidesoadetails)
104 {
105 MOADNSParser mdp(false, reply);
106 cout << "Reply to question for qname='" << mdp.d_qname.toString()
107 << "', qtype=" << DNSRecordContent::NumberToType(mdp.d_qtype) << endl;
108 cout << "Rcode: " << mdp.d_header.rcode << " ("
109 << RCode::to_s(mdp.d_header.rcode) << "), RD: " << mdp.d_header.rd
110 << ", QR: " << mdp.d_header.qr;
111 cout << ", TC: " << mdp.d_header.tc << ", AA: " << mdp.d_header.aa
112 << ", opcode: " << mdp.d_header.opcode << endl;
113
114 for (MOADNSParser::answers_t::const_iterator i = mdp.d_answers.begin();
115 i != mdp.d_answers.end(); ++i) {
116 cout << i->first.d_place - 1 << "\t" << i->first.d_name.toString() << "\t"
117 << nameForClass(i->first.d_class, i->first.d_type) << "\t"
118 << DNSRecordContent::NumberToType(i->first.d_type);
119 if (i->first.d_class == QClass::IN) {
120 if (i->first.d_type == QType::RRSIG) {
121 string zoneRep = i->first.d_content->getZoneRepresentation();
122 vector<string> parts;
123 stringtok(parts, zoneRep);
124 cout << "\t" << ttl(i->first.d_ttl) << "\t" << parts[0] << " "
125 << parts[1] << " " << parts[2] << " " << parts[3]
126 << " [expiry] [inception] [keytag] " << parts[7] << " ...\n";
127 continue;
128 }
129 if (!showflags && i->first.d_type == QType::NSEC3) {
130 string zoneRep = i->first.d_content->getZoneRepresentation();
131 vector<string> parts;
132 stringtok(parts, zoneRep);
133 cout << "\t" << ttl(i->first.d_ttl) << "\t" << parts[0] << " [flags] "
134 << parts[2] << " " << parts[3] << " " << parts[4];
135 for (vector<string>::iterator iter = parts.begin() + 5;
136 iter != parts.end(); ++iter)
137 cout << " " << *iter;
138 cout << "\n";
139 continue;
140 }
141 if (i->first.d_type == QType::DNSKEY) {
142 string zoneRep = i->first.d_content->getZoneRepresentation();
143 vector<string> parts;
144 stringtok(parts, zoneRep);
145 cout << "\t" << ttl(i->first.d_ttl) << "\t" << parts[0] << " "
146 << parts[1] << " " << parts[2] << " ...\n";
147 continue;
148 }
149 if (i->first.d_type == QType::SOA && hidesoadetails) {
150 string zoneRep = i->first.d_content->getZoneRepresentation();
151 vector<string> parts;
152 stringtok(parts, zoneRep);
153 cout << "\t" << ttl(i->first.d_ttl) << "\t" << parts[0] << " "
154 << parts[1] << " [serial] " << parts[3] << " " << parts[4] << " "
155 << parts[5] << " " << parts[6] << "\n";
156 continue;
157 }
158 }
159 cout << "\t" << ttl(i->first.d_ttl) << "\t"
160 << i->first.d_content->getZoneRepresentation() << "\n";
161 }
162
163 EDNSOpts edo;
164 if (getEDNSOpts(mdp, &edo)) {
165 // cerr<<"Have "<<edo.d_options.size()<<" options!"<<endl;
166 for (vector<pair<uint16_t, string>>::const_iterator iter = edo.d_options.begin();
167 iter != edo.d_options.end(); ++iter) {
168 if (iter->first == EDNSOptionCode::ECS) { // 'EDNS subnet'
169 EDNSSubnetOpts reso;
170 if (getEDNSSubnetOptsFromString(iter->second, &reso)) {
171 cerr << "EDNS Subnet response: " << reso.source.toString()
172 << ", scope: " << reso.scope.toString()
173 << ", family = " << reso.scope.getNetwork().sin4.sin_family
174 << endl;
175 }
176 } else if (iter->first == EDNSOptionCode::PADDING) {
177 cerr << "EDNS Padding size: " << (iter->second.size()) << endl;
178 } else {
179 cerr << "Have unknown option " << (int)iter->first << endl;
180 }
181 }
182 }
183 }
184
185 int main(int argc, char** argv)
186 try {
187 bool dnssec = false;
188 bool recurse = false;
189 bool tcp = false;
190 bool showflags = false;
191 bool hidesoadetails = false;
192 bool doh = false;
193 bool fromstdin = false;
194 boost::optional<Netmask> ednsnm;
195 uint16_t xpfcode = 0, xpfversion = 0, xpfproto = 0;
196 char *xpfsrc = NULL, *xpfdst = NULL;
197 uint16_t qclass = QClass::IN;
198
199 for (int i = 1; i < argc; i++) {
200 if ((string)argv[i] == "--help") {
201 usage();
202 exit(EXIT_SUCCESS);
203 }
204
205 if ((string)argv[i] == "--version") {
206 cerr << "sdig " << VERSION << endl;
207 exit(EXIT_SUCCESS);
208 }
209 }
210
211 if (argc < 5) {
212 usage();
213 exit(EXIT_FAILURE);
214 }
215
216 reportAllTypes();
217
218 if (argc > 5) {
219 for (int i = 5; i < argc; i++) {
220 if (strcmp(argv[i], "dnssec") == 0)
221 dnssec = true;
222 if (strcmp(argv[i], "recurse") == 0)
223 recurse = true;
224 if (strcmp(argv[i], "showflags") == 0)
225 showflags = true;
226 if (strcmp(argv[i], "hidesoadetails") == 0)
227 hidesoadetails = true;
228 if (strcmp(argv[i], "hidettl") == 0)
229 hidettl = true;
230 if (strcmp(argv[i], "tcp") == 0)
231 tcp = true;
232 if (strcmp(argv[i], "ednssubnet") == 0) {
233 if (argc < i + 2) {
234 cerr << "ednssubnet needs an argument" << endl;
235 exit(EXIT_FAILURE);
236 }
237 ednsnm = Netmask(argv[++i]);
238 }
239 if (strcmp(argv[i], "xpf") == 0) {
240 if (argc < i + 6) {
241 cerr << "xpf needs five arguments" << endl;
242 exit(EXIT_FAILURE);
243 }
244 xpfcode = atoi(argv[++i]);
245 xpfversion = atoi(argv[++i]);
246 xpfproto = atoi(argv[++i]);
247 xpfsrc = argv[++i];
248 xpfdst = argv[++i];
249 }
250 if (strcmp(argv[i], "class") == 0) {
251 if (argc < i+2) {
252 cerr << "class needs an argument"<<endl;
253 exit(EXIT_FAILURE);
254 }
255 qclass = atoi(argv[++i]);
256 }
257 }
258 }
259
260 string reply;
261 ComboAddress dest;
262 if (*argv[1] == 'h') {
263 doh = true;
264 } else if(strcmp(argv[1], "stdin") == 0) {
265 fromstdin = true;
266 } else {
267 dest = ComboAddress(argv[1] + (*argv[1] == '@'), atoi(argv[2]));
268 }
269
270 string name = string(argv[3]);
271 string type = string(argv[4]);
272
273 vector<pair<string, string>> questions;
274 if (name == "-" && type == "-") {
275 if (!tcp) {
276 throw PDNSException("multi-query from stdin only supported for tcp");
277 }
278 string line;
279 while (getline(std::cin, line)) {
280 auto fields = splitField(line, ' ');
281
282 questions.push_back(make_pair(fields.first, fields.second));
283 }
284 } else {
285 questions.push_back(make_pair(name, type));
286 }
287
288 if (doh) {
289 #ifdef HAVE_LIBCURL
290 vector<uint8_t> packet;
291 fillPacket(packet, name, type, dnssec, ednsnm, recurse, xpfcode, xpfversion,
292 xpfproto, xpfsrc, xpfdst, qclass);
293 MiniCurl mc;
294 MiniCurl::MiniCurlHeaders mch;
295 mch.insert(std::make_pair("Content-Type", "application/dns-message"));
296 mch.insert(std::make_pair("Accept", "application/dns-message"));
297 string question(packet.begin(), packet.end());
298 reply = mc.postURL(argv[1], question, mch);
299 printReply(reply, showflags, hidesoadetails);
300 #else
301 throw PDNSException("please link sdig against libcurl for DoH support");
302 #endif
303 } else if (fromstdin) {
304 std::istreambuf_iterator<char> begin(std::cin), end;
305 reply = string(begin, end);
306 printReply(reply, showflags, hidesoadetails);
307 } else if (tcp) {
308 Socket sock(dest.sin4.sin_family, SOCK_STREAM);
309 sock.connect(dest);
310 for (const auto& it : questions) {
311 vector<uint8_t> packet;
312 fillPacket(packet, it.first, it.second, dnssec, ednsnm, recurse, xpfcode,
313 xpfversion, xpfproto, xpfsrc, xpfdst, qclass);
314
315 uint16_t len = htons(packet.size());
316 if (sock.write((const char *)&len, 2) != 2)
317 throw PDNSException("tcp write failed");
318 string question(packet.begin(), packet.end());
319 sock.writen(question);
320 }
321 for (size_t i = 0; i < questions.size(); i++) {
322 uint16_t len;
323 if (sock.read((char *)&len, 2) != 2)
324 throw PDNSException("tcp read failed");
325
326 len = ntohs(len);
327 char* creply = new char[len];
328 int n = 0;
329 int numread;
330 while (n < len) {
331 numread = sock.read(creply + n, len - n);
332 if (numread < 0)
333 throw PDNSException("tcp read failed");
334 n += numread;
335 }
336
337 reply = string(creply, len);
338 delete[] creply;
339 printReply(reply, showflags, hidesoadetails);
340 }
341 } else // udp
342 {
343 vector<uint8_t> packet;
344 fillPacket(packet, name, type, dnssec, ednsnm, recurse, xpfcode, xpfversion,
345 xpfproto, xpfsrc, xpfdst, qclass);
346 string question(packet.begin(), packet.end());
347 Socket sock(dest.sin4.sin_family, SOCK_DGRAM);
348 sock.sendTo(question, dest);
349 int result = waitForData(sock.getHandle(), 10);
350 if (result < 0)
351 throw std::runtime_error("Error waiting for data: " + stringerror());
352 if (!result)
353 throw std::runtime_error("Timeout waiting for data");
354 sock.recvFrom(reply, dest);
355 printReply(reply, showflags, hidesoadetails);
356 }
357
358 } catch (std::exception& e) {
359 cerr << "Fatal: " << e.what() << endl;
360 } catch (PDNSException& e) {
361 cerr << "Fatal: " << e.reason << endl;
362 }