]>
Commit | Line | Data |
---|---|---|
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 | 19 | StatBag S; |
ff6a1e7b | 20 | |
cc3d516d | 21 | static bool hidettl = false; |
6e4c4a26 | 22 | |
cc3d516d | 23 | static 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 | 31 | static 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 | 41 | const 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 |
60 | void 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 | ||
105 | void 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 | ||
187 | int main(int argc, char** argv) | |
188 | try { | |
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 | } |