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 "proxy-protocol.hh"
11 #include "sstuff.hh"
12 #include "statbag.hh"
13 #include <boost/array.hpp>
14
15 #ifdef HAVE_LIBCURL
16 #include "minicurl.hh"
17 #endif
18
19 #include "tcpiohandler.hh"
20
21 StatBag S;
22
23 // Vars below used by tcpiohandler.cc
24 bool g_verbose = true;
25 bool g_syslog = false;
26
27 static bool hidettl = false;
28
ttl(uint32_t ttl)29 static string ttl(uint32_t ttl)
30 {
31 if (hidettl)
32 return "[ttl]";
33 else
34 return std::to_string(ttl);
35 }
36
usage()37 static void usage()
38 {
39 cerr << "sdig" << endl;
40 cerr << "Syntax: sdig IP-ADDRESS-OR-DOH-URL PORT QNAME QTYPE "
41 "[dnssec] [ednssubnet SUBNET/MASK] [hidesoadetails] [hidettl] [recurse] [showflags] "
42 "[tcp] [dot] [insecure] [fastOpen] [subjectName name] [caStore file] [tlsProvider openssl|gnutls] "
43 "[xpf XPFDATA] [class CLASSNUM] "
44 "[proxy UDP(0)/TCP(1) SOURCE-IP-ADDRESS-AND-PORT DESTINATION-IP-ADDRESS-AND-PORT]"
45 "dumpluaraw"
46 << endl;
47 }
48
nameForClass(QClass qclass,uint16_t qtype)49 static const string nameForClass(QClass qclass, uint16_t qtype)
50 {
51 if (qtype == QType::OPT)
52 return "IN";
53
54 return qclass.toString();
55 }
56
57 static std::unordered_set<uint16_t> s_expectedIDs;
58
fillPacket(vector<uint8_t> & packet,const string & q,const string & t,bool dnssec,const boost::optional<Netmask> ednsnm,bool recurse,uint16_t xpfcode,uint16_t xpfversion,uint64_t xpfproto,char * xpfsrc,char * xpfdst,QClass qclass,uint16_t qid)59 static void fillPacket(vector<uint8_t>& packet, const string& q, const string& t,
60 bool dnssec, const boost::optional<Netmask> ednsnm,
61 bool recurse, uint16_t xpfcode, uint16_t xpfversion,
62 uint64_t xpfproto, char* xpfsrc, char* xpfdst,
63 QClass qclass, uint16_t qid)
64 {
65 DNSPacketWriter pw(packet, DNSName(q), DNSRecordContent::TypeToNumber(t), qclass);
66
67 if (dnssec || ednsnm || getenv("SDIGBUFSIZE")) {
68 char* sbuf = getenv("SDIGBUFSIZE");
69 int bufsize;
70 if (sbuf)
71 bufsize = atoi(sbuf);
72 else
73 bufsize = 2800;
74 DNSPacketWriter::optvect_t opts;
75 if (ednsnm) {
76 EDNSSubnetOpts eo;
77 eo.source = *ednsnm;
78 opts.push_back(
79 make_pair(EDNSOptionCode::ECS, makeEDNSSubnetOptsString(eo)));
80 }
81
82 pw.addOpt(bufsize, 0, dnssec ? EDNSOpts::DNSSECOK : 0, opts);
83 pw.commit();
84 }
85
86 if (xpfcode) {
87 ComboAddress src(xpfsrc), dst(xpfdst);
88 pw.startRecord(g_rootdnsname, xpfcode, 0, QClass::IN, DNSResourceRecord::ADDITIONAL);
89 // xpf->toPacket(pw);
90 pw.xfr8BitInt(xpfversion);
91 pw.xfr8BitInt(xpfproto);
92 pw.xfrCAWithoutPort(xpfversion, src);
93 pw.xfrCAWithoutPort(xpfversion, dst);
94 pw.xfrCAPort(src);
95 pw.xfrCAPort(dst);
96 pw.commit();
97 }
98
99 if (recurse) {
100 pw.getHeader()->rd = true;
101 }
102
103 pw.getHeader()->id = htons(qid);
104 }
105
printReply(const string & reply,bool showflags,bool hidesoadetails,bool dumpluaraw)106 static void printReply(const string& reply, bool showflags, bool hidesoadetails, bool dumpluaraw)
107 {
108 MOADNSParser mdp(false, reply);
109 if (!s_expectedIDs.count(ntohs(mdp.d_header.id))) {
110 cout << "ID " << ntohs(mdp.d_header.id) << " was not expected, this response was not meant for us!"<<endl;
111 }
112 s_expectedIDs.erase(ntohs(mdp.d_header.id));
113
114 cout << "Reply to question for qname='" << mdp.d_qname.toString()
115 << "', qtype=" << DNSRecordContent::NumberToType(mdp.d_qtype) << endl;
116 cout << "Rcode: " << mdp.d_header.rcode << " ("
117 << RCode::to_s(mdp.d_header.rcode) << "), RD: " << mdp.d_header.rd
118 << ", QR: " << mdp.d_header.qr;
119 cout << ", TC: " << mdp.d_header.tc << ", AA: " << mdp.d_header.aa
120 << ", opcode: " << mdp.d_header.opcode << endl;
121
122 for (MOADNSParser::answers_t::const_iterator i = mdp.d_answers.begin();
123 i != mdp.d_answers.end(); ++i) {
124 cout << i->first.d_place - 1 << "\t" << i->first.d_name.toString() << "\t"
125 << nameForClass(i->first.d_class, i->first.d_type) << "\t"
126 << DNSRecordContent::NumberToType(i->first.d_type);
127 if (dumpluaraw) {
128 cout<<"\t"<< makeLuaString(i->first.d_content->serialize(DNSName(), true))<<endl;
129 continue;
130 }
131 if (i->first.d_class == QClass::IN) {
132 if (i->first.d_type == QType::RRSIG) {
133 string zoneRep = i->first.d_content->getZoneRepresentation();
134 vector<string> parts;
135 stringtok(parts, zoneRep);
136 cout << "\t" << ttl(i->first.d_ttl) << "\t" << parts[0] << " "
137 << parts[1] << " " << parts[2] << " " << parts[3]
138 << " [expiry] [inception] [keytag] " << parts[7] << " ...\n";
139 continue;
140 }
141 if (!showflags && i->first.d_type == QType::NSEC3) {
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] << " [flags] "
146 << parts[2] << " " << parts[3] << " " << parts[4];
147 for (vector<string>::iterator iter = parts.begin() + 5;
148 iter != parts.end(); ++iter)
149 cout << " " << *iter;
150 cout << "\n";
151 continue;
152 }
153 if (i->first.d_type == QType::DNSKEY) {
154 string zoneRep = i->first.d_content->getZoneRepresentation();
155 vector<string> parts;
156 stringtok(parts, zoneRep);
157 cout << "\t" << ttl(i->first.d_ttl) << "\t" << parts[0] << " "
158 << parts[1] << " " << parts[2] << " ...\n";
159 continue;
160 }
161 if (i->first.d_type == QType::SOA && hidesoadetails) {
162 string zoneRep = i->first.d_content->getZoneRepresentation();
163 vector<string> parts;
164 stringtok(parts, zoneRep);
165 cout << "\t" << ttl(i->first.d_ttl) << "\t" << parts[0] << " "
166 << parts[1] << " [serial] " << parts[3] << " " << parts[4] << " "
167 << parts[5] << " " << parts[6] << "\n";
168 continue;
169 }
170 }
171 cout << "\t" << ttl(i->first.d_ttl) << "\t"
172 << i->first.d_content->getZoneRepresentation() << "\n";
173 }
174
175 EDNSOpts edo;
176 if (getEDNSOpts(mdp, &edo)) {
177 // cerr<<"Have "<<edo.d_options.size()<<" options!"<<endl;
178 for (vector<pair<uint16_t, string>>::const_iterator iter = edo.d_options.begin();
179 iter != edo.d_options.end(); ++iter) {
180 if (iter->first == EDNSOptionCode::ECS) { // 'EDNS subnet'
181 EDNSSubnetOpts reso;
182 if (getEDNSSubnetOptsFromString(iter->second, &reso)) {
183 cerr << "EDNS Subnet response: " << reso.source.toString()
184 << ", scope: " << reso.scope.toString()
185 << ", family = " << reso.scope.getNetwork().sin4.sin_family
186 << endl;
187 }
188 } else if (iter->first == EDNSOptionCode::PADDING) {
189 cerr << "EDNS Padding size: " << (iter->second.size()) << endl;
190 } else {
191 cerr << "Have unknown option " << (int)iter->first << endl;
192 }
193 }
194 }
195 }
196
main(int argc,char ** argv)197 int main(int argc, char** argv)
198 try {
199 /* default timeout of 10s */
200 int timeout = 10;
201 bool dnssec = false;
202 bool recurse = false;
203 bool tcp = false;
204 bool showflags = false;
205 bool hidesoadetails = false;
206 bool doh = false;
207 bool dot = false;
208 bool fastOpen = false;
209 bool insecureDoT = false;
210 bool fromstdin = false;
211 boost::optional<Netmask> ednsnm;
212 uint16_t xpfcode = 0, xpfversion = 0, xpfproto = 0;
213 char *xpfsrc = NULL, *xpfdst = NULL;
214 QClass qclass = QClass::IN;
215 string proxyheader;
216 string subjectName;
217 string caStore;
218 string tlsProvider = "openssl";
219 bool dumpluaraw = false;
220
221 for (int i = 1; i < argc; i++) {
222 if ((string)argv[i] == "--help") {
223 usage();
224 exit(EXIT_SUCCESS);
225 }
226
227 if ((string)argv[i] == "--version") {
228 cerr << "sdig " << VERSION << endl;
229 exit(EXIT_SUCCESS);
230 }
231 }
232
233 if (argc < 5) {
234 usage();
235 exit(EXIT_FAILURE);
236 }
237
238 reportAllTypes();
239
240 if (argc > 5) {
241 for (int i = 5; i < argc; i++) {
242 if (strcmp(argv[i], "dnssec") == 0)
243 dnssec = true;
244 else if (strcmp(argv[i], "recurse") == 0)
245 recurse = true;
246 else if (strcmp(argv[i], "showflags") == 0)
247 showflags = true;
248 else if (strcmp(argv[i], "hidesoadetails") == 0)
249 hidesoadetails = true;
250 else if (strcmp(argv[i], "hidettl") == 0)
251 hidettl = true;
252 else if (strcmp(argv[i], "tcp") == 0)
253 tcp = true;
254 else if (strcmp(argv[i], "dot") == 0)
255 dot = true;
256 else if (strcmp(argv[i], "insecure") == 0)
257 insecureDoT = true;
258 else if (strcmp(argv[i], "fastOpen") == 0)
259 fastOpen = true;
260 else if (strcmp(argv[i], "ednssubnet") == 0) {
261 if (argc < i + 2) {
262 cerr << "ednssubnet needs an argument" << endl;
263 exit(EXIT_FAILURE);
264 }
265 ednsnm = Netmask(argv[++i]);
266 }
267 else if (strcmp(argv[i], "xpf") == 0) {
268 if (argc < i + 6) {
269 cerr << "xpf needs five arguments" << endl;
270 exit(EXIT_FAILURE);
271 }
272 xpfcode = atoi(argv[++i]);
273 xpfversion = atoi(argv[++i]);
274 xpfproto = atoi(argv[++i]);
275 xpfsrc = argv[++i];
276 xpfdst = argv[++i];
277 }
278 else if (strcmp(argv[i], "class") == 0) {
279 if (argc < i+2) {
280 cerr << "class needs an argument"<<endl;
281 exit(EXIT_FAILURE);
282 }
283 qclass = atoi(argv[++i]);
284 }
285 else if (strcmp(argv[i], "subjectName") == 0) {
286 if (argc < i + 2) {
287 cerr << "subjectName needs an argument"<<endl;
288 exit(EXIT_FAILURE);
289 }
290 subjectName = argv[++i];
291 }
292 else if (strcmp(argv[i], "caStore") == 0) {
293 if (argc < i + 2) {
294 cerr << "caStore needs an argument"<<endl;
295 exit(EXIT_FAILURE);
296 }
297 caStore = argv[++i];
298 }
299 else if (strcmp(argv[i], "tlsProvider") == 0) {
300 if (argc < i + 2) {
301 cerr << "tlsProvider needs an argument"<<endl;
302 exit(EXIT_FAILURE);
303 }
304 tlsProvider = argv[++i];
305 }
306 else if (strcmp(argv[i], "proxy") == 0) {
307 if(argc < i+4) {
308 cerr<<"proxy needs three arguments"<<endl;
309 exit(EXIT_FAILURE);
310 }
311 bool ptcp = atoi(argv[++i]);
312 ComboAddress src(argv[++i]);
313 ComboAddress dest(argv[++i]);
314 proxyheader = makeProxyHeader(ptcp, src, dest, {});
315 }
316 else if (strcmp(argv[i], "dumpluaraw") == 0) {
317 dumpluaraw = true;
318 }
319 else {
320 cerr << argv[i] << ": unknown argument" << endl;
321 exit(EXIT_FAILURE);
322 }
323 }
324 }
325
326 if (dot) {
327 tcp = true;
328 }
329
330 #ifndef HAVE_DNS_OVER_TLS
331 if (dot) {
332 cerr << "DoT requested but not compiled in" << endl;
333 exit(EXIT_FAILURE);
334 }
335 #endif
336
337 string reply;
338 ComboAddress dest;
339 if (*argv[1] == 'h') {
340 doh = true;
341 } else if(strcmp(argv[1], "stdin") == 0) {
342 fromstdin = true;
343 } else {
344 dest = ComboAddress(argv[1] + (*argv[1] == '@'), atoi(argv[2]));
345 }
346
347 string name = string(argv[3]);
348 string type = string(argv[4]);
349
350 vector<pair<string, string>> questions;
351 if (name == "-" && type == "-") {
352 if (!tcp) {
353 throw PDNSException("multi-query from stdin only supported for tcp");
354 }
355 string line;
356 while (getline(std::cin, line)) {
357 auto fields = splitField(line, ' ');
358
359 questions.push_back(make_pair(fields.first, fields.second));
360 }
361 } else {
362 questions.push_back(make_pair(name, type));
363 }
364
365 if (doh) {
366 #ifdef HAVE_LIBCURL
367 vector<uint8_t> packet;
368 s_expectedIDs.insert(0);
369 fillPacket(packet, name, type, dnssec, ednsnm, recurse, xpfcode, xpfversion,
370 xpfproto, xpfsrc, xpfdst, qclass, 0);
371 MiniCurl mc;
372 MiniCurl::MiniCurlHeaders mch;
373 mch.insert(std::make_pair("Content-Type", "application/dns-message"));
374 mch.insert(std::make_pair("Accept", "application/dns-message"));
375 string question(packet.begin(), packet.end());
376 // FIXME: how do we use proxyheader here?
377 reply = mc.postURL(argv[1], question, mch, timeout, fastOpen);
378 printReply(reply, showflags, hidesoadetails, dumpluaraw);
379 #else
380 throw PDNSException("please link sdig against libcurl for DoH support");
381 #endif
382 } else if (fromstdin) {
383 std::istreambuf_iterator<char> begin(std::cin), end;
384 reply = string(begin, end);
385
386 ComboAddress source, destination;
387 bool wastcp;
388 bool proxy = false;
389 std::vector<ProxyProtocolValue> ignoredValues;
390 ssize_t offset = parseProxyHeader(reply, proxy, source, destination, wastcp, ignoredValues);
391 if (offset && proxy) {
392 cout<<"proxy "<<(wastcp ? "tcp" : "udp")<<" headersize="<<offset<<" source="<<source.toStringWithPort()<<" destination="<<destination.toStringWithPort()<<endl;
393 reply = reply.substr(offset);
394 }
395
396 if (tcp) {
397 reply = reply.substr(2);
398 }
399
400 printReply(reply, showflags, hidesoadetails, dumpluaraw);
401 } else if (tcp) {
402 std::shared_ptr<TLSCtx> tlsCtx{nullptr};
403 if (dot) {
404 TLSContextParameters tlsParams;
405 tlsParams.d_provider = tlsProvider;
406 tlsParams.d_validateCertificates = !insecureDoT;
407 tlsParams.d_caStore = caStore;
408 tlsCtx = getTLSContext(tlsParams);
409 }
410 uint16_t counter = 0;
411 Socket sock(dest.sin4.sin_family, SOCK_STREAM);
412 setTCPNoDelay(sock.getHandle()); // disable NAGLE, which does not play nicely with delayed ACKs
413 TCPIOHandler handler(subjectName, sock.releaseHandle(), timeout, tlsCtx, time(nullptr));
414 handler.connect(fastOpen, dest, timeout);
415 // we are writing the proxyheader inside the TLS connection. Is that right?
416 if (proxyheader.size() > 0 && handler.write(proxyheader.data(), proxyheader.size(), timeout) != proxyheader.size()) {
417 throw PDNSException("tcp write failed");
418 }
419
420 for (const auto& it : questions) {
421 vector<uint8_t> packet;
422 s_expectedIDs.insert(counter);
423 fillPacket(packet, it.first, it.second, dnssec, ednsnm, recurse, xpfcode,
424 xpfversion, xpfproto, xpfsrc, xpfdst, qclass, counter);
425 counter++;
426
427 // Prefer to do a single write, so that fastopen can send all the data on SYN
428 uint16_t len = packet.size();
429 string question;
430 question.reserve(sizeof(len) + packet.size());
431 question.push_back(static_cast<char>(len >> 8));
432 question.push_back(static_cast<char>(len & 0xff));
433 question.append(packet.begin(), packet.end());
434 if (handler.write(question.data(), question.size(), timeout) != question.size()) {
435 throw PDNSException("tcp write failed");
436 }
437 }
438 for (size_t i = 0; i < questions.size(); i++) {
439 uint16_t len;
440 if (handler.read((char *)&len, sizeof(len), timeout) != sizeof(len)) {
441 throw PDNSException("tcp read failed");
442 }
443 len = ntohs(len);
444 reply.resize(len);
445 if (handler.read(&reply[0], len, timeout) != len) {
446 throw PDNSException("tcp read failed");
447 }
448 printReply(reply, showflags, hidesoadetails, dumpluaraw);
449 }
450 } else // udp
451 {
452 vector<uint8_t> packet;
453 s_expectedIDs.insert(0);
454 fillPacket(packet, name, type, dnssec, ednsnm, recurse, xpfcode, xpfversion,
455 xpfproto, xpfsrc, xpfdst, qclass, 0);
456 string question(packet.begin(), packet.end());
457 Socket sock(dest.sin4.sin_family, SOCK_DGRAM);
458 question = proxyheader + question;
459 sock.sendTo(question, dest);
460 int result = waitForData(sock.getHandle(), timeout);
461 if (result < 0)
462 throw std::runtime_error("Error waiting for data: " + stringerror());
463 if (!result)
464 throw std::runtime_error("Timeout waiting for data");
465 sock.recvFrom(reply, dest);
466 printReply(reply, showflags, hidesoadetails, dumpluaraw);
467 }
468
469 } catch (std::exception& e) {
470 cerr << "Fatal: " << e.what() << endl;
471 } catch (PDNSException& e) {
472 cerr << "Fatal: " << e.reason << endl;
473 }
474