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