/src/pdns/pdns/dnsparser.cc
Line | Count | Source |
1 | | /* |
2 | | * This file is part of PowerDNS or dnsdist. |
3 | | * Copyright -- PowerDNS.COM B.V. and its contributors |
4 | | * |
5 | | * This program is free software; you can redistribute it and/or modify |
6 | | * it under the terms of version 2 of the GNU General Public License as |
7 | | * published by the Free Software Foundation. |
8 | | * |
9 | | * In addition, for the avoidance of any doubt, permission is granted to |
10 | | * link this program with OpenSSL and to (re)distribute the binaries |
11 | | * produced as the result of such linking. |
12 | | * |
13 | | * This program is distributed in the hope that it will be useful, |
14 | | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
15 | | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
16 | | * GNU General Public License for more details. |
17 | | * |
18 | | * You should have received a copy of the GNU General Public License |
19 | | * along with this program; if not, write to the Free Software |
20 | | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. |
21 | | */ |
22 | | #include "dnsparser.hh" |
23 | | #include "dnswriter.hh" |
24 | | #include <boost/algorithm/string.hpp> |
25 | | #include <boost/format.hpp> |
26 | | #include <cstdint> |
27 | | #include <stdexcept> |
28 | | #include <string> |
29 | | |
30 | | #include "dns_random.hh" |
31 | | #include "namespaces.hh" |
32 | | #include "noinitvector.hh" |
33 | | |
34 | | std::atomic<bool> DNSRecordContent::d_locked{false}; |
35 | | |
36 | | UnknownRecordContent::UnknownRecordContent(const string& zone) |
37 | 323 | { |
38 | | // parse the input |
39 | 323 | vector<string> parts; |
40 | 323 | stringtok(parts, zone); |
41 | | // we need exactly 3 parts, except if the length field is set to 0 then we only need 2 |
42 | 323 | if (parts.size() != 3 && !(parts.size() == 2 && boost::equals(parts.at(1), "0"))) { |
43 | 149 | throw MOADNSException("Unknown record was stored incorrectly, need 3 fields, got " + std::to_string(parts.size()) + ": " + zone); |
44 | 149 | } |
45 | | |
46 | 174 | if (parts.at(0) != "\\#") { |
47 | 75 | throw MOADNSException("Unknown record was stored incorrectly, first part should be '\\#', got '" + parts.at(0) + "'"); |
48 | 75 | } |
49 | | |
50 | 99 | const string& relevant = (parts.size() > 2) ? parts.at(2) : ""; |
51 | 99 | auto total = pdns::checked_stoi<unsigned int>(parts.at(1)); |
52 | 99 | if (relevant.size() % 2 || (relevant.size() / 2) != total) { |
53 | 55 | throw MOADNSException((boost::format("invalid unknown record length: size not equal to length field (%d != 2 * %d)") % relevant.size() % total).str()); |
54 | 55 | } |
55 | | |
56 | 44 | string out; |
57 | 44 | out.reserve(total + 1); |
58 | | |
59 | 322 | for (unsigned int n = 0; n < total; ++n) { |
60 | 282 | int c; |
61 | 282 | if (sscanf(&relevant.at(2*n), "%02x", &c) != 1) { |
62 | 4 | throw MOADNSException("unable to read data at position " + std::to_string(2 * n) + " from unknown record of size " + std::to_string(relevant.size())); |
63 | 4 | } |
64 | 278 | out.append(1, (char)c); |
65 | 278 | } |
66 | | |
67 | 40 | d_record.insert(d_record.end(), out.begin(), out.end()); |
68 | 40 | } |
69 | | |
70 | | string UnknownRecordContent::getZoneRepresentation(bool /* noDot */) const |
71 | 365 | { |
72 | 365 | ostringstream str; |
73 | 365 | str<<"\\# "<<(unsigned int)d_record.size(); |
74 | 365 | if (!d_record.empty()) { |
75 | 362 | std::array<char,4> hex{}; |
76 | 362 | str << " "; |
77 | 879k | for (auto byte : d_record) { |
78 | 879k | snprintf(hex.data(), hex.size(), "%02x", byte); |
79 | 879k | str << hex.data(); |
80 | 879k | } |
81 | 362 | } |
82 | 365 | return str.str(); |
83 | 365 | } |
84 | | |
85 | | void UnknownRecordContent::toPacket(DNSPacketWriter& pw) const |
86 | 22 | { |
87 | 22 | pw.xfrBlob(string(d_record.begin(),d_record.end())); |
88 | 22 | } |
89 | | |
90 | | shared_ptr<DNSRecordContent> DNSRecordContent::deserialize(const DNSName& qname, uint16_t qtype, const string& serialized, uint16_t qclass, bool internalRepresentation) |
91 | 12.8k | { |
92 | 12.8k | dnsheader dnsheader; |
93 | 12.8k | memset(&dnsheader, 0, sizeof(dnsheader)); |
94 | 12.8k | dnsheader.qdcount=htons(1); |
95 | 12.8k | dnsheader.ancount=htons(1); |
96 | | |
97 | 12.8k | PacketBuffer packet; // build pseudo packet |
98 | | /* will look like: dnsheader, 5 bytes, encoded qname, dns record header, serialized data */ |
99 | 12.8k | const auto& encoded = qname.getStorage(); |
100 | 12.8k | packet.resize(sizeof(dnsheader) + 5 + encoded.size() + sizeof(struct dnsrecordheader) + serialized.size()); |
101 | | |
102 | 12.8k | uint16_t pos=0; |
103 | 12.8k | memcpy(&packet[0], &dnsheader, sizeof(dnsheader)); pos+=sizeof(dnsheader); |
104 | | |
105 | 12.8k | constexpr std::array<uint8_t, 5> tmp= {'\x0', '\x0', '\x1', '\x0', '\x1' }; // root question for ns_t_a |
106 | 12.8k | memcpy(&packet[pos], tmp.data(), tmp.size()); pos += tmp.size(); |
107 | | |
108 | 12.8k | memcpy(&packet[pos], encoded.c_str(), encoded.size()); pos+=(uint16_t)encoded.size(); |
109 | | |
110 | 12.8k | struct dnsrecordheader drh; |
111 | 12.8k | drh.d_type=htons(qtype); |
112 | 12.8k | drh.d_class=htons(qclass); |
113 | 12.8k | drh.d_ttl=0; |
114 | 12.8k | drh.d_clen=htons(serialized.size()); |
115 | | |
116 | 12.8k | memcpy(&packet[pos], &drh, sizeof(drh)); pos+=sizeof(drh); |
117 | 12.8k | if (!serialized.empty()) { |
118 | 12.7k | memcpy(&packet[pos], serialized.c_str(), serialized.size()); |
119 | 12.7k | pos += (uint16_t) serialized.size(); |
120 | 12.7k | (void) pos; |
121 | 12.7k | } |
122 | | |
123 | 12.8k | DNSRecord dr; |
124 | 12.8k | dr.d_class = qclass; |
125 | 12.8k | dr.d_type = qtype; |
126 | 12.8k | dr.d_name = qname; |
127 | 12.8k | dr.d_clen = serialized.size(); |
128 | | // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast): packet.data() is uint8_t * |
129 | 12.8k | PacketReader reader(std::string_view(reinterpret_cast<const char*>(packet.data()), packet.size()), packet.size() - serialized.size() - sizeof(dnsrecordheader), internalRepresentation); |
130 | | /* needed to get the record boundaries right */ |
131 | 12.8k | reader.getDnsrecordheader(drh); |
132 | 12.8k | auto content = DNSRecordContent::make(dr, reader, Opcode::Query); |
133 | 12.8k | return content; |
134 | 12.8k | } |
135 | | |
136 | | std::shared_ptr<DNSRecordContent> DNSRecordContent::make(const DNSRecord& dr, |
137 | | PacketReader& pr) |
138 | 0 | { |
139 | 0 | uint16_t searchclass = (dr.d_type == QType::OPT) ? 1 : dr.d_class; // class is invalid for OPT |
140 | |
|
141 | 0 | auto i = getTypemap().find(pair(searchclass, dr.d_type)); |
142 | 0 | if(i==getTypemap().end() || !i->second) { |
143 | 0 | return std::make_shared<UnknownRecordContent>(dr, pr); |
144 | 0 | } |
145 | | |
146 | 0 | return i->second(dr, pr); |
147 | 0 | } |
148 | | |
149 | | std::shared_ptr<DNSRecordContent> DNSRecordContent::make(uint16_t qtype, uint16_t qclass, |
150 | | const string& content) |
151 | 9.33k | { |
152 | 9.33k | auto i = getZmakermap().find(pair(qclass, qtype)); |
153 | 9.33k | if(i==getZmakermap().end()) { |
154 | 323 | return std::make_shared<UnknownRecordContent>(content); |
155 | 323 | } |
156 | | |
157 | 9.00k | return i->second(content); |
158 | 9.33k | } |
159 | | |
160 | | std::shared_ptr<DNSRecordContent> DNSRecordContent::make(const DNSRecord& dr, PacketReader& pr, uint16_t oc) |
161 | 188k | { |
162 | | // For opcode UPDATE and where the DNSRecord is an answer record, we don't care about content, because this is |
163 | | // not used within the prerequisite section of RFC2136, so - we can simply use unknownrecordcontent. |
164 | | // For section 3.2.3, we do need content so we need to get it properly. But only for the correct QClasses. |
165 | 188k | if (oc == Opcode::Update && dr.d_place == DNSResourceRecord::ANSWER && dr.d_class != 1) |
166 | 3.03k | return std::make_shared<UnknownRecordContent>(dr, pr); |
167 | | |
168 | 185k | uint16_t searchclass = (dr.d_type == QType::OPT) ? 1 : dr.d_class; // class is invalid for OPT |
169 | | |
170 | 185k | auto i = getTypemap().find(pair(searchclass, dr.d_type)); |
171 | 185k | if(i==getTypemap().end() || !i->second) { |
172 | 57.6k | return std::make_shared<UnknownRecordContent>(dr, pr); |
173 | 57.6k | } |
174 | | |
175 | 127k | return i->second(dr, pr); |
176 | 185k | } |
177 | | |
178 | 0 | string DNSRecordContent::upgradeContent(const DNSName& qname, const QType& qtype, const string& content) { |
179 | | // seamless upgrade for previously unsupported but now implemented types. |
180 | 0 | UnknownRecordContent unknown_content(content); |
181 | 0 | shared_ptr<DNSRecordContent> rc = DNSRecordContent::deserialize(qname, qtype.getCode(), unknown_content.serialize(qname)); |
182 | 0 | return rc->getZoneRepresentation(); |
183 | 0 | } |
184 | | |
185 | | DNSRecordContent::typemap_t& DNSRecordContent::getTypemap() |
186 | 371k | { |
187 | 371k | static DNSRecordContent::typemap_t typemap; |
188 | 371k | return typemap; |
189 | 371k | } |
190 | | |
191 | | DNSRecordContent::n2typemap_t& DNSRecordContent::getN2Typemap() |
192 | 351k | { |
193 | 351k | static DNSRecordContent::n2typemap_t n2typemap; |
194 | 351k | return n2typemap; |
195 | 351k | } |
196 | | |
197 | | DNSRecordContent::t2namemap_t& DNSRecordContent::getT2Namemap() |
198 | 38.0k | { |
199 | 38.0k | static DNSRecordContent::t2namemap_t t2namemap; |
200 | 38.0k | return t2namemap; |
201 | 38.0k | } |
202 | | |
203 | | DNSRecordContent::zmakermap_t& DNSRecordContent::getZmakermap() |
204 | 19.1k | { |
205 | 19.1k | static DNSRecordContent::zmakermap_t zmakermap; |
206 | 19.1k | return zmakermap; |
207 | 19.1k | } |
208 | | |
209 | | bool DNSRecordContent::isRegisteredType(uint16_t rtype, uint16_t rclass) |
210 | 0 | { |
211 | 0 | return getTypemap().count(pair(rclass, rtype)) != 0; |
212 | 0 | } |
213 | | |
214 | 0 | DNSRecord::DNSRecord(const DNSResourceRecord& rr): d_name(rr.qname) |
215 | 0 | { |
216 | 0 | d_type = rr.qtype.getCode(); |
217 | 0 | d_ttl = rr.ttl; |
218 | 0 | d_class = rr.qclass; |
219 | 0 | d_place = DNSResourceRecord::ANSWER; |
220 | 0 | d_clen = 0; |
221 | 0 | d_content = DNSRecordContent::make(d_type, rr.qclass, rr.content); |
222 | 0 | } |
223 | | |
224 | | // If you call this and you are not parsing a packet coming from a socket, you are doing it wrong. |
225 | | DNSResourceRecord DNSResourceRecord::fromWire(const DNSRecord& wire) |
226 | 0 | { |
227 | 0 | DNSResourceRecord resourceRecord; |
228 | 0 | resourceRecord.qname = wire.d_name; |
229 | 0 | resourceRecord.qtype = QType(wire.d_type); |
230 | 0 | resourceRecord.ttl = wire.d_ttl; |
231 | 0 | resourceRecord.content = wire.getContent()->getZoneRepresentation(true); |
232 | 0 | resourceRecord.auth = false; |
233 | 0 | resourceRecord.qclass = wire.d_class; |
234 | 0 | return resourceRecord; |
235 | 0 | } |
236 | | |
237 | | void MOADNSParser::init(bool query, const std::string_view& packet) |
238 | 15.1k | { |
239 | 15.1k | if (packet.size() < sizeof(dnsheader)) |
240 | 16 | throw MOADNSException("Packet shorter than minimal header"); |
241 | | |
242 | 15.1k | memcpy(&d_header, packet.data(), sizeof(dnsheader)); |
243 | | |
244 | 15.1k | if(d_header.opcode != Opcode::Query && d_header.opcode != Opcode::Notify && d_header.opcode != Opcode::Update) |
245 | 8 | throw MOADNSException("Can't parse non-query packet with opcode="+ std::to_string(d_header.opcode)); |
246 | | |
247 | 15.1k | d_header.qdcount=ntohs(d_header.qdcount); |
248 | 15.1k | d_header.ancount=ntohs(d_header.ancount); |
249 | 15.1k | d_header.nscount=ntohs(d_header.nscount); |
250 | 15.1k | d_header.arcount=ntohs(d_header.arcount); |
251 | | |
252 | 15.1k | if (query && (d_header.qdcount > 1)) |
253 | 281 | throw MOADNSException("Query with QD > 1 ("+std::to_string(d_header.qdcount)+")"); |
254 | | |
255 | 14.8k | unsigned int n=0; |
256 | | |
257 | 14.8k | PacketReader pr(packet); |
258 | 14.8k | bool validPacket=false; |
259 | 14.8k | try { |
260 | 14.8k | d_qtype = d_qclass = 0; // sometimes replies come in with no question, don't present garbage then |
261 | | |
262 | 59.1k | for(n=0;n < d_header.qdcount; ++n) { |
263 | 44.3k | d_qname=pr.getName(); |
264 | 44.3k | d_qtype=pr.get16BitInt(); |
265 | 44.3k | d_qclass=pr.get16BitInt(); |
266 | 44.3k | } |
267 | | |
268 | 14.8k | struct dnsrecordheader ah; |
269 | 14.8k | vector<unsigned char> record; |
270 | 14.8k | bool seenTSIG = false; |
271 | 14.8k | validPacket=true; |
272 | 14.8k | unsigned int supposedRecordCount = d_header.ancount + d_header.nscount + d_header.arcount; |
273 | | // No need to reserve more memory for more records than the request can |
274 | | // contain. We could try to be smarter and actually count the records |
275 | | // by doing getDnsrecordheader and skip the payload in a loop, but all |
276 | | // we really want here is to avoid reserving too much memory in case of |
277 | | // maliciously high record counts. |
278 | 14.8k | auto reserveRecordCount = std::min(1 + (packet.size() - sizeof(dnsheader)) / sizeof(dnsrecordheader), static_cast<size_t>(supposedRecordCount)); |
279 | 14.8k | d_answers.reserve(reserveRecordCount); |
280 | 250k | for (n = 0; n < supposedRecordCount; ++n) { |
281 | 236k | DNSRecord dr; |
282 | | |
283 | 236k | if(n < d_header.ancount) |
284 | 131k | dr.d_place=DNSResourceRecord::ANSWER; |
285 | 105k | else if(n < d_header.ancount + d_header.nscount) |
286 | 69.5k | dr.d_place=DNSResourceRecord::AUTHORITY; |
287 | 35.6k | else |
288 | 35.6k | dr.d_place=DNSResourceRecord::ADDITIONAL; |
289 | | |
290 | 236k | unsigned int recordStartPos=pr.getPosition(); |
291 | | |
292 | 236k | DNSName name=pr.getName(); |
293 | | |
294 | 236k | pr.getDnsrecordheader(ah); |
295 | 236k | dr.d_ttl=ah.d_ttl; |
296 | 236k | dr.d_type=ah.d_type; |
297 | 236k | dr.d_class=ah.d_class; |
298 | | |
299 | 236k | dr.d_name = std::move(name); |
300 | 236k | dr.d_clen = ah.d_clen; |
301 | | |
302 | 236k | if (query && |
303 | 55.6k | !(d_qtype == QType::IXFR && dr.d_place == DNSResourceRecord::AUTHORITY && dr.d_type == QType::SOA) && // IXFR queries have a SOA in their AUTHORITY section |
304 | 54.6k | (dr.d_place == DNSResourceRecord::ANSWER || dr.d_place == DNSResourceRecord::AUTHORITY || (dr.d_type != QType::OPT && dr.d_type != QType::TSIG && dr.d_type != QType::SIG && dr.d_type != QType::TKEY) || ((dr.d_type == QType::TSIG || dr.d_type == QType::SIG || dr.d_type == QType::TKEY) && dr.d_class != QClass::ANY))) { |
305 | | // cerr<<"discarding RR, query is "<<query<<", place is "<<dr.d_place<<", type is "<<dr.d_type<<", class is "<<dr.d_class<<endl; |
306 | 52.9k | dr.setContent(std::make_shared<UnknownRecordContent>(dr, pr)); |
307 | 52.9k | } |
308 | 183k | else { |
309 | | // cerr<<"parsing RR, query is "<<query<<", place is "<<dr.d_place<<", type is "<<dr.d_type<<", class is "<<dr.d_class<<endl; |
310 | 183k | dr.setContent(DNSRecordContent::make(dr, pr, d_header.opcode)); |
311 | 183k | } |
312 | | |
313 | 236k | if (dr.d_place == DNSResourceRecord::ADDITIONAL && seenTSIG) { |
314 | 180 | throw MOADNSException("Packet ("+d_qname.toString()+"|#"+std::to_string(d_qtype)+") has an unexpected record ("+std::to_string(dr.d_type)+") after a TSIG one."); |
315 | 180 | } |
316 | | |
317 | 236k | if(dr.d_type == QType::TSIG && dr.d_class == QClass::ANY) { |
318 | 465 | if(seenTSIG || dr.d_place != DNSResourceRecord::ADDITIONAL) { |
319 | 235 | throw MOADNSException("Packet ("+d_qname.toLogString()+"|#"+std::to_string(d_qtype)+") has a TSIG record in an invalid position."); |
320 | 235 | } |
321 | 230 | seenTSIG = true; |
322 | 230 | d_tsigPos = recordStartPos; |
323 | 230 | } |
324 | | |
325 | 235k | d_answers.emplace_back(std::move(dr)); |
326 | 235k | } |
327 | | |
328 | | #if 0 |
329 | | if(pr.getPosition()!=packet.size()) { |
330 | | throw MOADNSException("Packet ("+d_qname+"|#"+std::to_string(d_qtype)+") has trailing garbage ("+ std::to_string(pr.getPosition()) + " < " + |
331 | | std::to_string(packet.size()) + ")"); |
332 | | } |
333 | | #endif |
334 | 14.8k | } |
335 | 14.8k | catch(const std::out_of_range &re) { |
336 | 10.6k | if(validPacket && d_header.tc) { // don't sweat it over truncated packets, but do adjust an, ns and arcount |
337 | 2.30k | if(n < d_header.ancount) { |
338 | 1.36k | d_header.ancount=n; d_header.nscount = d_header.arcount = 0; |
339 | 1.36k | } |
340 | 940 | else if(n < d_header.ancount + d_header.nscount) { |
341 | 503 | d_header.nscount = n - d_header.ancount; d_header.arcount=0; |
342 | 503 | } |
343 | 437 | else { |
344 | 437 | d_header.arcount = n - d_header.ancount - d_header.nscount; |
345 | 437 | } |
346 | 2.30k | } |
347 | 8.35k | else { |
348 | 8.35k | throw MOADNSException("Error parsing packet of "+std::to_string(packet.size())+" bytes (rd="+ |
349 | 8.35k | std::to_string(d_header.rd)+ |
350 | 8.35k | "), out of bounds: "+string(re.what())); |
351 | 8.35k | } |
352 | 10.6k | } |
353 | 14.8k | } |
354 | | |
355 | | bool MOADNSParser::hasEDNS() const |
356 | 0 | { |
357 | 0 | if (d_header.arcount == 0 || d_answers.empty()) { |
358 | 0 | return false; |
359 | 0 | } |
360 | | |
361 | 0 | for (const auto& record : d_answers) { |
362 | 0 | if (record.d_place == DNSResourceRecord::ADDITIONAL && record.d_type == QType::OPT) { |
363 | 0 | return true; |
364 | 0 | } |
365 | 0 | } |
366 | | |
367 | 0 | return false; |
368 | 0 | } |
369 | | |
370 | | void PacketReader::getDnsrecordheader(struct dnsrecordheader &ah) |
371 | 243k | { |
372 | 243k | unsigned char *p = reinterpret_cast<unsigned char*>(&ah); |
373 | | |
374 | 2.67M | for(unsigned int n = 0; n < sizeof(dnsrecordheader); ++n) { |
375 | 2.43M | p[n] = d_content.at(d_pos++); |
376 | 2.43M | } |
377 | | |
378 | 243k | ah.d_type = ntohs(ah.d_type); |
379 | 243k | ah.d_class = ntohs(ah.d_class); |
380 | 243k | ah.d_clen = ntohs(ah.d_clen); |
381 | 243k | ah.d_ttl = ntohl(ah.d_ttl); |
382 | | |
383 | 243k | d_startrecordpos = d_pos; // needed for getBlob later on |
384 | 243k | d_recordlen = ah.d_clen; |
385 | 243k | if (d_pos > d_content.size() || (d_content.size() - d_pos) < (d_recordlen)) { |
386 | 1.23k | throw std::out_of_range("DNS record length (" + std::to_string(d_recordlen) + " starting at " + std::to_string(d_pos) + ") goes beyond the packet's content (" + std::to_string(d_content.size()) + ")"); |
387 | 1.23k | } |
388 | 243k | } |
389 | | |
390 | | |
391 | | void PacketReader::copyRecord(vector<unsigned char>& dest, uint16_t len) |
392 | 113k | { |
393 | 113k | if (len == 0) { |
394 | 69.2k | return; |
395 | 69.2k | } |
396 | 44.3k | if ((d_pos + len) > d_content.size()) { |
397 | 0 | throw std::out_of_range("Attempt to copy outside of packet"); |
398 | 0 | } |
399 | | |
400 | 44.3k | dest.resize(len); |
401 | | |
402 | 12.2M | for (uint16_t n = 0; n < len; ++n) { |
403 | 12.2M | dest.at(n) = d_content.at(d_pos++); |
404 | 12.2M | } |
405 | 44.3k | } |
406 | | |
407 | | void PacketReader::copyRecord(unsigned char* dest, uint16_t len) |
408 | 1.78k | { |
409 | 1.78k | if (d_pos + len > d_content.size()) { |
410 | 0 | throw std::out_of_range("Attempt to copy outside of packet"); |
411 | 0 | } |
412 | | |
413 | 1.78k | memcpy(dest, &d_content.at(d_pos), len); |
414 | 1.78k | d_pos += len; |
415 | 1.78k | } |
416 | | |
417 | | void PacketReader::xfrNodeOrLocatorID(NodeOrLocatorID& ret) |
418 | 2.09k | { |
419 | 2.09k | if (d_pos + sizeof(ret) > d_content.size()) { |
420 | 49 | throw std::out_of_range("Attempt to read 64 bit value outside of packet"); |
421 | 49 | } |
422 | 2.04k | memcpy(&ret.content, &d_content.at(d_pos), sizeof(ret.content)); |
423 | 2.04k | d_pos += sizeof(ret); |
424 | 2.04k | } |
425 | | |
426 | | void PacketReader::xfr48BitInt(uint64_t& ret) |
427 | 611 | { |
428 | 611 | ret=0; |
429 | 611 | ret+=static_cast<uint8_t>(d_content.at(d_pos++)); |
430 | 611 | ret<<=8; |
431 | 611 | ret+=static_cast<uint8_t>(d_content.at(d_pos++)); |
432 | 611 | ret<<=8; |
433 | 611 | ret+=static_cast<uint8_t>(d_content.at(d_pos++)); |
434 | 611 | ret<<=8; |
435 | 611 | ret+=static_cast<uint8_t>(d_content.at(d_pos++)); |
436 | 611 | ret<<=8; |
437 | 611 | ret+=static_cast<uint8_t>(d_content.at(d_pos++)); |
438 | 611 | ret<<=8; |
439 | 611 | ret+=static_cast<uint8_t>(d_content.at(d_pos++)); |
440 | 611 | } |
441 | | |
442 | | uint32_t PacketReader::get32BitInt() |
443 | 37.4k | { |
444 | 37.4k | uint32_t ret=0; |
445 | 37.4k | ret+=static_cast<uint8_t>(d_content.at(d_pos++)); |
446 | 37.4k | ret<<=8; |
447 | 37.4k | ret+=static_cast<uint8_t>(d_content.at(d_pos++)); |
448 | 37.4k | ret<<=8; |
449 | 37.4k | ret+=static_cast<uint8_t>(d_content.at(d_pos++)); |
450 | 37.4k | ret<<=8; |
451 | 37.4k | ret+=static_cast<uint8_t>(d_content.at(d_pos++)); |
452 | | |
453 | 37.4k | return ret; |
454 | 37.4k | } |
455 | | |
456 | | |
457 | | uint16_t PacketReader::get16BitInt() |
458 | 1.06M | { |
459 | 1.06M | uint16_t ret=0; |
460 | 1.06M | ret+=static_cast<uint8_t>(d_content.at(d_pos++)); |
461 | 1.06M | ret<<=8; |
462 | 1.06M | ret+=static_cast<uint8_t>(d_content.at(d_pos++)); |
463 | | |
464 | 1.06M | return ret; |
465 | 1.06M | } |
466 | | |
467 | | uint8_t PacketReader::get8BitInt() |
468 | 505k | { |
469 | 505k | return d_content.at(d_pos++); |
470 | 505k | } |
471 | | |
472 | | DNSName PacketReader::getName() |
473 | 347k | { |
474 | 347k | unsigned int consumed; |
475 | 347k | try { |
476 | 347k | DNSName dn((const char*) d_content.data(), d_content.size(), d_pos, true /* uncompress */, nullptr /* qtype */, nullptr /* qclass */, &consumed, sizeof(dnsheader)); |
477 | | |
478 | 347k | d_pos+=consumed; |
479 | 347k | return dn; |
480 | 347k | } |
481 | 347k | catch(const std::range_error& re) { |
482 | 8.89k | throw std::out_of_range(string("dnsname issue: ")+re.what()); |
483 | 8.89k | } |
484 | 347k | catch(...) { |
485 | 0 | throw std::out_of_range("dnsname issue"); |
486 | 0 | } |
487 | 0 | throw PDNSException("PacketReader::getName(): name is empty"); |
488 | 347k | } |
489 | | |
490 | | // FIXME see #6010 and #3503 if you want a proper solution |
491 | | string txtEscape(const string &name) |
492 | 635k | { |
493 | 635k | string ret; |
494 | 635k | std::array<char, 5> ebuf{}; |
495 | | |
496 | 8.81M | for (char letter : name) { |
497 | 8.81M | const unsigned uch = static_cast<unsigned char>(letter); |
498 | 8.81M | if (uch >= 127 || uch < 32) { |
499 | 2.09M | snprintf(ebuf.data(), ebuf.size(), "\\%03u", uch); |
500 | 2.09M | ret += ebuf.data(); |
501 | 2.09M | } |
502 | 6.72M | else if (letter == '"' || letter == '\\'){ |
503 | 681k | ret += '\\'; |
504 | 681k | ret += letter; |
505 | 681k | } |
506 | 6.04M | else { |
507 | 6.04M | ret += letter; |
508 | 6.04M | } |
509 | 8.81M | } |
510 | 635k | return ret; |
511 | 635k | } |
512 | | |
513 | | // exceptions thrown here do not result in logging in the main pdns auth server - just so you know! |
514 | | string PacketReader::getText(bool multi, bool lenField) |
515 | 19.1k | { |
516 | 19.1k | string ret; |
517 | 19.1k | ret.reserve(40); |
518 | 418k | while (d_pos < d_startrecordpos + d_recordlen ) { |
519 | 401k | if (!ret.empty()) { |
520 | 393k | ret.append(1,' '); |
521 | 393k | } |
522 | 401k | uint16_t labellen; |
523 | 401k | if (lenField) { |
524 | 398k | labellen = static_cast<uint8_t>(d_content.at(d_pos++)); |
525 | 398k | } |
526 | 3.65k | else { |
527 | 3.65k | labellen = d_recordlen - (d_pos - d_startrecordpos); |
528 | 3.65k | } |
529 | | |
530 | 401k | const uint16_t remaining = (d_startrecordpos + d_recordlen) - d_pos; |
531 | 401k | if (labellen > remaining) { |
532 | 621 | throw std::out_of_range("label length in text record exceeds record boundary"); |
533 | 621 | } |
534 | | |
535 | 401k | ret.append(1, '"'); |
536 | 401k | if (labellen) { // no need to do anything for an empty string |
537 | 71.7k | string val(&d_content.at(d_pos), &d_content.at(d_pos + labellen - 1) + 1); |
538 | 71.7k | ret.append(txtEscape(val)); // the end is one beyond the packet |
539 | 71.7k | } |
540 | 401k | ret.append(1, '"'); |
541 | 401k | d_pos += labellen; |
542 | 401k | if (!multi) { |
543 | 1.84k | break; |
544 | 1.84k | } |
545 | 401k | } |
546 | | |
547 | 18.5k | if (ret.empty() && !lenField) { |
548 | | // all lenField == false cases (CAA and URI at the time of this writing) want that emptiness to be explicit |
549 | 1.64k | return "\"\""; |
550 | 1.64k | } |
551 | 16.8k | return ret; |
552 | 18.5k | } |
553 | | |
554 | | string PacketReader::getUnquotedText(bool lenField) |
555 | 3.85k | { |
556 | 3.85k | uint16_t stop_at{}; |
557 | 3.85k | if (lenField) { |
558 | 3.85k | stop_at = static_cast<uint8_t>(d_content.at(d_pos)) + d_pos + 1; |
559 | 3.85k | } |
560 | 0 | else { |
561 | 0 | stop_at = d_recordlen; |
562 | 0 | } |
563 | | |
564 | | /* think unsigned overflow */ |
565 | 3.85k | if (stop_at < d_pos) { |
566 | 1 | throw std::out_of_range("getUnquotedText out of record range"); |
567 | 1 | } |
568 | | |
569 | | /* Validate against record boundary */ |
570 | 3.85k | const uint16_t recordEnd = d_startrecordpos + d_recordlen; |
571 | 3.85k | if (stop_at > recordEnd) { |
572 | 131 | throw std::out_of_range("getUnquotedText: length exceeds record boundary"); |
573 | 131 | } |
574 | | |
575 | 3.72k | if (stop_at == d_pos) { |
576 | 0 | return ""; |
577 | 0 | } |
578 | | |
579 | 3.72k | d_pos++; |
580 | 3.72k | string ret(d_content.substr(d_pos, stop_at-d_pos)); |
581 | 3.72k | d_pos = stop_at; |
582 | 3.72k | return ret; |
583 | 3.72k | } |
584 | | |
585 | | void PacketReader::xfrBlob(string& blob) |
586 | 45.9k | { |
587 | 45.9k | try { |
588 | 45.9k | if(d_recordlen && !(d_pos == (d_startrecordpos + d_recordlen))) { |
589 | 15.0k | if (d_pos > (d_startrecordpos + d_recordlen)) { |
590 | 29 | throw std::out_of_range("xfrBlob out of record range"); |
591 | 29 | } |
592 | 14.9k | blob.assign(&d_content.at(d_pos), &d_content.at(d_startrecordpos + d_recordlen - 1 ) + 1); |
593 | 14.9k | } |
594 | 30.9k | else { |
595 | 30.9k | blob.clear(); |
596 | 30.9k | } |
597 | | |
598 | 45.9k | d_pos = d_startrecordpos + d_recordlen; |
599 | 45.9k | } |
600 | 45.9k | catch(...) |
601 | 45.9k | { |
602 | 29 | throw std::out_of_range("xfrBlob out of range"); |
603 | 29 | } |
604 | 45.9k | } |
605 | | |
606 | 2.16k | void PacketReader::xfrBlobNoSpaces(string& blob, int length) { |
607 | 2.16k | xfrBlob(blob, length); |
608 | 2.16k | } |
609 | | |
610 | | void PacketReader::xfrBlob(string& blob, int length) |
611 | 612k | { |
612 | 612k | if(length) { |
613 | 606k | if (length < 0) { |
614 | 0 | throw std::out_of_range("xfrBlob out of range (negative length)"); |
615 | 0 | } |
616 | 606k | auto available = (d_startrecordpos + d_recordlen) - d_pos; |
617 | 606k | if (available < length) { |
618 | 529 | throw std::out_of_range("xfrBlob out of range (excessive length)"); |
619 | 529 | } |
620 | | |
621 | 605k | blob.assign(&d_content.at(d_pos), &d_content.at(d_pos + length - 1 ) + 1 ); |
622 | | |
623 | 605k | d_pos += length; |
624 | 605k | } |
625 | 6.16k | else { |
626 | 6.16k | blob.clear(); |
627 | 6.16k | } |
628 | 612k | } |
629 | | |
630 | 12.1k | void PacketReader::xfrSvcParamKeyVals(set<SvcParam> &kvs) { |
631 | 12.1k | int32_t lastKey{-1}; // Keep track of the last key, as ordering should be strict |
632 | | |
633 | 27.4k | while (d_pos < (d_startrecordpos + d_recordlen)) { |
634 | 16.4k | if (d_pos + 2 > (d_startrecordpos + d_recordlen)) { |
635 | 82 | throw std::out_of_range("incomplete key"); |
636 | 82 | } |
637 | 16.3k | uint16_t keyInt; |
638 | 16.3k | xfr16BitInt(keyInt); |
639 | | |
640 | 16.3k | if (keyInt <= lastKey) { |
641 | 294 | throw std::out_of_range("Found SVCParamKey " + std::to_string(keyInt) + " after SVCParamKey " + std::to_string(lastKey)); |
642 | 294 | } |
643 | 16.0k | lastKey = keyInt; |
644 | | |
645 | 16.0k | auto key = static_cast<SvcParam::SvcParamKey>(keyInt); |
646 | | |
647 | 16.0k | uint16_t len; |
648 | 16.0k | xfr16BitInt(len); |
649 | | |
650 | 16.0k | if (d_pos + len > (d_startrecordpos + d_recordlen)) { |
651 | 448 | throw std::out_of_range("record is shorter than SVCB lengthfield implies"); |
652 | 448 | } |
653 | | |
654 | 15.6k | switch (key) |
655 | 15.6k | { |
656 | 1.58k | case SvcParam::mandatory: { |
657 | 1.58k | if (len % 2 != 0) { |
658 | 8 | throw std::out_of_range("mandatory SvcParam has invalid length"); |
659 | 8 | } |
660 | 1.57k | if (len == 0) { |
661 | 32 | throw std::out_of_range("empty 'mandatory' values"); |
662 | 32 | } |
663 | 1.54k | std::set<SvcParam::SvcParamKey> paramKeys; |
664 | 1.54k | size_t stop = d_pos + len; |
665 | 632k | while (d_pos < stop) { |
666 | 630k | uint16_t keyval; |
667 | 630k | xfr16BitInt(keyval); |
668 | 630k | paramKeys.insert(static_cast<SvcParam::SvcParamKey>(keyval)); |
669 | 630k | } |
670 | 1.54k | kvs.insert(SvcParam(key, std::move(paramKeys))); |
671 | 1.54k | break; |
672 | 1.57k | } |
673 | 2.33k | case SvcParam::alpn: { |
674 | 2.33k | size_t stop = d_pos + len; |
675 | 2.33k | std::vector<string> alpns; |
676 | 297k | while (d_pos < stop) { |
677 | 295k | string alpn; |
678 | 295k | uint8_t alpnLen = 0; |
679 | 295k | xfr8BitInt(alpnLen); |
680 | 295k | if (alpnLen == 0) { |
681 | 65 | throw std::out_of_range("alpn length of 0"); |
682 | 65 | } |
683 | 295k | if (d_pos + alpnLen > stop) { |
684 | 120 | throw std::out_of_range("alpn length is larger than rest of alpn SVC Param"); |
685 | 120 | } |
686 | 295k | xfrBlob(alpn, alpnLen); |
687 | 295k | alpns.push_back(std::move(alpn)); |
688 | 295k | } |
689 | 2.15k | kvs.insert(SvcParam(key, std::move(alpns))); |
690 | 2.15k | break; |
691 | 2.33k | } |
692 | 458 | case SvcParam::ohttp: |
693 | 755 | case SvcParam::no_default_alpn: { |
694 | 755 | if (len != 0) { |
695 | 7 | throw std::out_of_range("invalid length for " + SvcParam::keyToString(key)); |
696 | 7 | } |
697 | 748 | kvs.insert(SvcParam(key)); |
698 | 748 | break; |
699 | 755 | } |
700 | 888 | case SvcParam::port: { |
701 | 888 | if (len != 2) { |
702 | 24 | throw std::out_of_range("invalid length for port"); |
703 | 24 | } |
704 | 864 | uint16_t port; |
705 | 864 | xfr16BitInt(port); |
706 | 864 | kvs.insert(SvcParam(key, port)); |
707 | 864 | break; |
708 | 888 | } |
709 | 859 | case SvcParam::ipv4hint: |
710 | 1.53k | case SvcParam::ipv6hint: { |
711 | 1.53k | size_t addrLen = (key == SvcParam::ipv4hint ? 4 : 16); |
712 | 1.53k | if (len % addrLen != 0) { |
713 | 17 | throw std::out_of_range("invalid length for " + SvcParam::keyToString(key)); |
714 | 17 | } |
715 | 1.51k | vector<ComboAddress> addresses; |
716 | 1.51k | auto stop = d_pos + len; |
717 | 305k | while (d_pos < stop) |
718 | 303k | { |
719 | 303k | ComboAddress addr; |
720 | 303k | xfrCAWithoutPort(key, addr); |
721 | 303k | addresses.push_back(addr); |
722 | 303k | } |
723 | | // If there were no addresses, and the input comes from internal |
724 | | // representation, we can reasonably assume this is the serialization |
725 | | // of "auto". |
726 | 1.51k | bool doAuto{d_internal && len == 0}; |
727 | 1.51k | auto param = SvcParam(key, std::move(addresses)); |
728 | 1.51k | param.setAutoHint(doAuto); |
729 | 1.51k | kvs.insert(std::move(param)); |
730 | 1.51k | break; |
731 | 1.53k | } |
732 | 1.03k | case SvcParam::ech: { |
733 | 1.03k | std::string blob; |
734 | 1.03k | blob.reserve(len); |
735 | 1.03k | xfrBlobNoSpaces(blob, len); |
736 | 1.03k | kvs.insert(SvcParam(key, blob)); |
737 | 1.03k | break; |
738 | 1.53k | } |
739 | 1.28k | case SvcParam::tls_supported_groups: { |
740 | 1.28k | if (len % 2 != 0) { |
741 | 3 | throw std::out_of_range("invalid length for " + SvcParam::keyToString(key)); |
742 | 3 | } |
743 | 1.27k | vector<uint16_t> groups; |
744 | 1.27k | groups.reserve(len / 2); |
745 | 1.27k | auto stop = d_pos + len; |
746 | 207k | while (d_pos < stop) |
747 | 206k | { |
748 | 206k | uint16_t group = 0; |
749 | 206k | xfr16BitInt(group); |
750 | 206k | groups.push_back(group); |
751 | 206k | } |
752 | 1.27k | auto param = SvcParam(key, std::move(groups)); |
753 | 1.27k | kvs.insert(std::move(param)); |
754 | 1.27k | break; |
755 | 1.28k | } |
756 | 6.12k | default: { |
757 | 6.12k | std::string blob; |
758 | 6.12k | blob.reserve(len); |
759 | 6.12k | xfrBlob(blob, len); |
760 | 6.12k | kvs.insert(SvcParam(key, blob)); |
761 | 6.12k | break; |
762 | 1.28k | } |
763 | 15.6k | } |
764 | 15.6k | } |
765 | 12.1k | } |
766 | | |
767 | | |
768 | | void PacketReader::xfrHexBlob(string& blob, bool /* keepReading */) |
769 | 15.2k | { |
770 | 15.2k | xfrBlob(blob); |
771 | 15.2k | } |
772 | | |
773 | | //FIXME400 remove this method completely |
774 | | string simpleCompress(const string& elabel, const string& root) |
775 | 0 | { |
776 | 0 | string label=elabel; |
777 | | // FIXME400: this relies on the semi-canonical escaped output from getName |
778 | 0 | if(strchr(label.c_str(), '\\')) { |
779 | 0 | boost::replace_all(label, "\\.", "."); |
780 | 0 | boost::replace_all(label, "\\032", " "); |
781 | 0 | boost::replace_all(label, "\\\\", "\\"); |
782 | 0 | } |
783 | 0 | typedef vector<pair<unsigned int, unsigned int> > parts_t; |
784 | 0 | parts_t parts; |
785 | 0 | vstringtok(parts, label, "."); |
786 | 0 | string ret; |
787 | 0 | ret.reserve(label.size()+4); |
788 | 0 | for(const auto & part : parts) { |
789 | 0 | if(!root.empty() && !strncasecmp(root.c_str(), label.c_str() + part.first, 1 + label.length() - part.first)) { // also match trailing 0, hence '1 +' |
790 | 0 | const unsigned char rootptr[2]={0xc0,0x11}; |
791 | 0 | ret.append((const char *) rootptr, 2); |
792 | 0 | return ret; |
793 | 0 | } |
794 | 0 | ret.append(1, (char)(part.second - part.first)); |
795 | 0 | ret.append(label.c_str() + part.first, part.second - part.first); |
796 | 0 | } |
797 | 0 | ret.append(1, (char)0); |
798 | 0 | return ret; |
799 | 0 | } |
800 | | |
801 | | // method of operation: silently fail if it doesn't work - we're only trying to be nice, don't fall over on it |
802 | | void editDNSPacketTTL(char* packet, size_t length, const std::function<uint32_t(uint8_t, uint16_t, uint16_t, uint32_t)>& visitor) |
803 | 0 | { |
804 | 0 | if(length < sizeof(dnsheader)) |
805 | 0 | return; |
806 | 0 | try |
807 | 0 | { |
808 | 0 | dnsheader dh; |
809 | 0 | memcpy((void*)&dh, (const dnsheader*)packet, sizeof(dh)); |
810 | 0 | uint64_t numrecords = ntohs(dh.ancount) + ntohs(dh.nscount) + ntohs(dh.arcount); |
811 | 0 | DNSPacketMangler dpm(packet, length); |
812 | |
|
813 | 0 | uint64_t n; |
814 | 0 | for(n=0; n < ntohs(dh.qdcount) ; ++n) { |
815 | 0 | dpm.skipDomainName(); |
816 | | /* type and class */ |
817 | 0 | dpm.skipBytes(4); |
818 | 0 | } |
819 | |
|
820 | 0 | for(n=0; n < numrecords; ++n) { |
821 | 0 | dpm.skipDomainName(); |
822 | |
|
823 | 0 | uint8_t section = n < ntohs(dh.ancount) ? 1 : (n < (ntohs(dh.ancount) + ntohs(dh.nscount)) ? 2 : 3); |
824 | 0 | uint16_t dnstype = dpm.get16BitInt(); |
825 | 0 | uint16_t dnsclass = dpm.get16BitInt(); |
826 | |
|
827 | 0 | if(dnstype == QType::OPT) // not getting near that one with a stick |
828 | 0 | break; |
829 | | |
830 | 0 | uint32_t dnsttl = dpm.get32BitInt(); |
831 | 0 | uint32_t newttl = visitor(section, dnsclass, dnstype, dnsttl); |
832 | 0 | if (newttl) { |
833 | 0 | dpm.rewindBytes(sizeof(newttl)); |
834 | 0 | dpm.setAndSkip32BitInt(newttl); |
835 | 0 | } |
836 | 0 | dpm.skipRData(); |
837 | 0 | } |
838 | 0 | } |
839 | 0 | catch(...) |
840 | 0 | { |
841 | 0 | return; |
842 | 0 | } |
843 | 0 | } |
844 | | |
845 | | static bool checkIfPacketContainsRecords(const PacketBuffer& packet, const std::unordered_set<QType>& qtypes) |
846 | 0 | { |
847 | 0 | auto length = packet.size(); |
848 | 0 | if (length < sizeof(dnsheader)) { |
849 | 0 | return false; |
850 | 0 | } |
851 | | |
852 | 0 | try { |
853 | 0 | const dnsheader_aligned dh(packet.data()); |
854 | 0 | DNSPacketMangler dpm(const_cast<char*>(reinterpret_cast<const char*>(packet.data())), length); |
855 | |
|
856 | 0 | const uint16_t qdcount = ntohs(dh->qdcount); |
857 | 0 | for (size_t n = 0; n < qdcount; ++n) { |
858 | 0 | dpm.skipDomainName(); |
859 | | /* type and class */ |
860 | 0 | dpm.skipBytes(4); |
861 | 0 | } |
862 | 0 | const size_t recordsCount = static_cast<size_t>(ntohs(dh->ancount)) + ntohs(dh->nscount) + ntohs(dh->arcount); |
863 | 0 | for (size_t n = 0; n < recordsCount; ++n) { |
864 | 0 | dpm.skipDomainName(); |
865 | 0 | uint16_t dnstype = dpm.get16BitInt(); |
866 | 0 | uint16_t dnsclass = dpm.get16BitInt(); |
867 | 0 | if (dnsclass == QClass::IN && qtypes.count(dnstype) > 0) { |
868 | 0 | return true; |
869 | 0 | } |
870 | | /* ttl */ |
871 | 0 | dpm.skipBytes(4); |
872 | 0 | dpm.skipRData(); |
873 | 0 | } |
874 | 0 | } |
875 | 0 | catch (...) { |
876 | 0 | } |
877 | | |
878 | 0 | return false; |
879 | 0 | } |
880 | | |
881 | | static int rewritePacketWithoutRecordTypes(const PacketBuffer& initialPacket, PacketBuffer& newContent, const std::unordered_set<QType>& qtypes) |
882 | 0 | { |
883 | 0 | static const std::unordered_set<QType>& safeTypes{QType::A, QType::AAAA, QType::DHCID, QType::TXT, QType::OPT, QType::HINFO, QType::DNSKEY, QType::CDNSKEY, QType::DS, QType::CDS, QType::DLV, QType::SSHFP, QType::KEY, QType::CERT, QType::TLSA, QType::SMIMEA, QType::OPENPGPKEY, QType::SVCB, QType::HTTPS, QType::NSEC3, QType::CSYNC, QType::NSEC3PARAM, QType::LOC, QType::NID, QType::L32, QType::L64, QType::EUI48, QType::EUI64, QType::URI, QType::CAA}; |
884 | |
|
885 | 0 | if (initialPacket.size() < sizeof(dnsheader)) { |
886 | 0 | return EINVAL; |
887 | 0 | } |
888 | 0 | try { |
889 | 0 | const dnsheader_aligned dh(initialPacket.data()); |
890 | |
|
891 | 0 | if (ntohs(dh->qdcount) == 0) |
892 | 0 | return ENOENT; |
893 | 0 | auto packetView = std::string_view(reinterpret_cast<const char*>(initialPacket.data()), initialPacket.size()); |
894 | |
|
895 | 0 | PacketReader pr(packetView); |
896 | |
|
897 | 0 | size_t idx = 0; |
898 | 0 | DNSName rrname; |
899 | 0 | uint16_t qdcount = ntohs(dh->qdcount); |
900 | 0 | uint16_t ancount = ntohs(dh->ancount); |
901 | 0 | uint16_t nscount = ntohs(dh->nscount); |
902 | 0 | uint16_t arcount = ntohs(dh->arcount); |
903 | 0 | uint16_t rrtype; |
904 | 0 | uint16_t rrclass; |
905 | 0 | string blob; |
906 | 0 | struct dnsrecordheader ah; |
907 | |
|
908 | 0 | rrname = pr.getName(); |
909 | 0 | rrtype = pr.get16BitInt(); |
910 | 0 | rrclass = pr.get16BitInt(); |
911 | |
|
912 | 0 | GenericDNSPacketWriter<PacketBuffer> pw(newContent, rrname, rrtype, rrclass, dh->opcode); |
913 | 0 | pw.getHeader()->id=dh->id; |
914 | 0 | pw.getHeader()->qr=dh->qr; |
915 | 0 | pw.getHeader()->aa=dh->aa; |
916 | 0 | pw.getHeader()->tc=dh->tc; |
917 | 0 | pw.getHeader()->rd=dh->rd; |
918 | 0 | pw.getHeader()->ra=dh->ra; |
919 | 0 | pw.getHeader()->ad=dh->ad; |
920 | 0 | pw.getHeader()->cd=dh->cd; |
921 | 0 | pw.getHeader()->rcode=dh->rcode; |
922 | | |
923 | | /* consume remaining qd if any */ |
924 | 0 | if (qdcount > 1) { |
925 | 0 | for(idx = 1; idx < qdcount; idx++) { |
926 | 0 | rrname = pr.getName(); |
927 | 0 | rrtype = pr.get16BitInt(); |
928 | 0 | rrclass = pr.get16BitInt(); |
929 | 0 | (void) rrtype; |
930 | 0 | (void) rrclass; |
931 | 0 | } |
932 | 0 | } |
933 | | |
934 | | /* copy AN */ |
935 | 0 | for (idx = 0; idx < ancount; idx++) { |
936 | 0 | rrname = pr.getName(); |
937 | 0 | pr.getDnsrecordheader(ah); |
938 | 0 | pr.xfrBlob(blob); |
939 | |
|
940 | 0 | if (qtypes.find(ah.d_type) == qtypes.end()) { |
941 | | // if this is not a safe type |
942 | 0 | if (safeTypes.find(ah.d_type) == safeTypes.end()) { |
943 | | // "unsafe" types might contain compressed data, so cancel rewrite |
944 | 0 | newContent.clear(); |
945 | 0 | return EIO; |
946 | 0 | } |
947 | 0 | pw.startRecord(rrname, ah.d_type, ah.d_ttl, ah.d_class, DNSResourceRecord::ANSWER, true); |
948 | 0 | pw.xfrBlob(blob); |
949 | 0 | } |
950 | 0 | } |
951 | | |
952 | | /* copy NS */ |
953 | 0 | for (idx = 0; idx < nscount; idx++) { |
954 | 0 | rrname = pr.getName(); |
955 | 0 | pr.getDnsrecordheader(ah); |
956 | 0 | pr.xfrBlob(blob); |
957 | |
|
958 | 0 | if (qtypes.find(ah.d_type) == qtypes.end()) { |
959 | 0 | if (safeTypes.find(ah.d_type) == safeTypes.end()) { |
960 | | // "unsafe" types might contain compressed data, so cancel rewrite |
961 | 0 | newContent.clear(); |
962 | 0 | return EIO; |
963 | 0 | } |
964 | 0 | pw.startRecord(rrname, ah.d_type, ah.d_ttl, ah.d_class, DNSResourceRecord::AUTHORITY, true); |
965 | 0 | pw.xfrBlob(blob); |
966 | 0 | } |
967 | 0 | } |
968 | | /* copy AR */ |
969 | 0 | for (idx = 0; idx < arcount; idx++) { |
970 | 0 | rrname = pr.getName(); |
971 | 0 | pr.getDnsrecordheader(ah); |
972 | 0 | pr.xfrBlob(blob); |
973 | |
|
974 | 0 | if (qtypes.find(ah.d_type) == qtypes.end()) { |
975 | 0 | if (safeTypes.find(ah.d_type) == safeTypes.end()) { |
976 | | // "unsafe" types might contain compressed data, so cancel rewrite |
977 | 0 | newContent.clear(); |
978 | 0 | return EIO; |
979 | 0 | } |
980 | 0 | pw.startRecord(rrname, ah.d_type, ah.d_ttl, ah.d_class, DNSResourceRecord::ADDITIONAL, true); |
981 | 0 | pw.xfrBlob(blob); |
982 | 0 | } |
983 | 0 | } |
984 | 0 | pw.commit(); |
985 | |
|
986 | 0 | } |
987 | 0 | catch (...) |
988 | 0 | { |
989 | 0 | newContent.clear(); |
990 | 0 | return EIO; |
991 | 0 | } |
992 | 0 | return 0; |
993 | 0 | } |
994 | | |
995 | | void clearDNSPacketRecordTypes(vector<uint8_t>& packet, const std::unordered_set<QType>& qtypes) |
996 | 0 | { |
997 | 0 | return clearDNSPacketRecordTypes(reinterpret_cast<PacketBuffer&>(packet), qtypes); |
998 | 0 | } |
999 | | |
1000 | | void clearDNSPacketRecordTypes(PacketBuffer& packet, const std::unordered_set<QType>& qtypes) |
1001 | 0 | { |
1002 | 0 | if (!checkIfPacketContainsRecords(packet, qtypes)) { |
1003 | 0 | return; |
1004 | 0 | } |
1005 | | |
1006 | 0 | PacketBuffer newContent; |
1007 | |
|
1008 | 0 | auto result = rewritePacketWithoutRecordTypes(packet, newContent, qtypes); |
1009 | 0 | if (!result) { |
1010 | 0 | packet = std::move(newContent); |
1011 | 0 | } |
1012 | 0 | } |
1013 | | |
1014 | | // method of operation: silently fail if it doesn't work - we're only trying to be nice, don't fall over on it |
1015 | | void ageDNSPacket(char* packet, size_t length, uint32_t seconds, const dnsheader_aligned& aligned_dh) |
1016 | 0 | { |
1017 | 0 | if (length < sizeof(dnsheader)) { |
1018 | 0 | return; |
1019 | 0 | } |
1020 | 0 | try { |
1021 | 0 | const dnsheader* dhp = aligned_dh.get(); |
1022 | 0 | const uint64_t dqcount = ntohs(dhp->qdcount); |
1023 | 0 | const uint64_t numrecords = ntohs(dhp->ancount) + ntohs(dhp->nscount) + ntohs(dhp->arcount); |
1024 | 0 | DNSPacketMangler dpm(packet, length); |
1025 | |
|
1026 | 0 | for (uint64_t rec = 0; rec < dqcount; ++rec) { |
1027 | 0 | dpm.skipDomainName(); |
1028 | | /* type and class */ |
1029 | 0 | dpm.skipBytes(4); |
1030 | 0 | } |
1031 | |
|
1032 | 0 | for(uint64_t rec = 0; rec < numrecords; ++rec) { |
1033 | 0 | dpm.skipDomainName(); |
1034 | |
|
1035 | 0 | uint16_t dnstype = dpm.get16BitInt(); |
1036 | | /* class */ |
1037 | 0 | dpm.skipBytes(2); |
1038 | |
|
1039 | 0 | if (dnstype != QType::OPT) { // not aging that one with a stick |
1040 | 0 | dpm.decreaseAndSkip32BitInt(seconds); |
1041 | 0 | } else { |
1042 | 0 | dpm.skipBytes(4); |
1043 | 0 | } |
1044 | 0 | dpm.skipRData(); |
1045 | 0 | } |
1046 | 0 | } |
1047 | 0 | catch(...) { |
1048 | 0 | } |
1049 | 0 | } |
1050 | | |
1051 | | void ageDNSPacket(std::string& packet, uint32_t seconds, const dnsheader_aligned& aligned_dh) |
1052 | 0 | { |
1053 | 0 | ageDNSPacket(packet.data(), packet.length(), seconds, aligned_dh); |
1054 | 0 | } |
1055 | | |
1056 | | void shuffleDNSPacket(char* packet, size_t length, const dnsheader_aligned& aligned_dh) |
1057 | 0 | { |
1058 | 0 | if (length < sizeof(dnsheader)) { |
1059 | 0 | return; |
1060 | 0 | } |
1061 | 0 | try { |
1062 | 0 | const dnsheader* dhp = aligned_dh.get(); |
1063 | 0 | const uint16_t ancount = ntohs(dhp->ancount); |
1064 | 0 | if (ancount == 1) { |
1065 | | // quick exit, nothing to shuffle |
1066 | 0 | return; |
1067 | 0 | } |
1068 | | |
1069 | 0 | DNSPacketMangler dpm(packet, length); |
1070 | |
|
1071 | 0 | const uint16_t qdcount = ntohs(dhp->qdcount); |
1072 | |
|
1073 | 0 | for(size_t iter = 0; iter < qdcount; ++iter) { |
1074 | 0 | dpm.skipDomainName(); |
1075 | | /* type and class */ |
1076 | 0 | dpm.skipBytes(4); |
1077 | 0 | } |
1078 | | |
1079 | | // for now shuffle only first rrset, only As and AAAAs |
1080 | 0 | uint16_t rrset_type = 0; |
1081 | 0 | DNSName rrset_dnsname{}; |
1082 | 0 | std::vector<std::pair<uint32_t, uint32_t>> rrdata_indexes; |
1083 | 0 | rrdata_indexes.reserve(ancount); |
1084 | |
|
1085 | 0 | for(size_t iter = 0; iter < ancount; ++iter) { |
1086 | 0 | auto domain_start = dpm.getOffset(); |
1087 | 0 | dpm.skipDomainName(); |
1088 | 0 | const uint16_t dnstype = dpm.get16BitInt(); |
1089 | 0 | if (dnstype == QType::A || dnstype == QType::AAAA) { |
1090 | 0 | if (rrdata_indexes.empty()) { |
1091 | 0 | rrset_type = dnstype; |
1092 | 0 | rrset_dnsname = DNSName(packet, length, domain_start, true); |
1093 | 0 | } else { |
1094 | 0 | if (dnstype != rrset_type) { |
1095 | 0 | break; |
1096 | 0 | } |
1097 | 0 | if (DNSName(packet, length, domain_start, true) != rrset_dnsname) { |
1098 | 0 | break; |
1099 | 0 | } |
1100 | 0 | } |
1101 | | /* class */ |
1102 | 0 | dpm.skipBytes(2); |
1103 | | |
1104 | | /* ttl */ |
1105 | 0 | dpm.skipBytes(4); |
1106 | 0 | rrdata_indexes.push_back(dpm.skipRDataAndReturnOffsets()); |
1107 | 0 | } else { |
1108 | 0 | if (!rrdata_indexes.empty()) { |
1109 | 0 | break; |
1110 | 0 | } |
1111 | | /* class */ |
1112 | 0 | dpm.skipBytes(2); |
1113 | | |
1114 | | /* ttl */ |
1115 | 0 | dpm.skipBytes(4); |
1116 | 0 | dpm.skipRData(); |
1117 | 0 | } |
1118 | 0 | } |
1119 | |
|
1120 | 0 | if (rrdata_indexes.size() >= 2) { |
1121 | 0 | using uid = std::uniform_int_distribution<std::vector<std::pair<uint32_t, uint32_t>>::size_type>; |
1122 | 0 | uid dist; |
1123 | |
|
1124 | 0 | pdns::dns_random_engine randomEngine; |
1125 | 0 | for (auto swapped = rrdata_indexes.size() - 1; swapped > 0; --swapped) { |
1126 | 0 | auto swapped_with = dist(randomEngine, uid::param_type(0, swapped)); |
1127 | 0 | if (swapped != swapped_with) { |
1128 | 0 | dpm.swapInPlace(rrdata_indexes.at(swapped), rrdata_indexes.at(swapped_with)); |
1129 | 0 | } |
1130 | 0 | } |
1131 | 0 | } |
1132 | 0 | } |
1133 | 0 | catch(...) { |
1134 | 0 | } |
1135 | 0 | } |
1136 | | |
1137 | | uint32_t getDNSPacketMinTTL(const char* packet, size_t length, bool* seenAuthSOA) |
1138 | 0 | { |
1139 | 0 | uint32_t result = std::numeric_limits<uint32_t>::max(); |
1140 | 0 | if(length < sizeof(dnsheader)) { |
1141 | 0 | return result; |
1142 | 0 | } |
1143 | 0 | try |
1144 | 0 | { |
1145 | 0 | const dnsheader_aligned dh(packet); |
1146 | 0 | DNSPacketMangler dpm(const_cast<char*>(packet), length); |
1147 | |
|
1148 | 0 | const uint16_t qdcount = ntohs(dh->qdcount); |
1149 | 0 | for(size_t n = 0; n < qdcount; ++n) { |
1150 | 0 | dpm.skipDomainName(); |
1151 | | /* type and class */ |
1152 | 0 | dpm.skipBytes(4); |
1153 | 0 | } |
1154 | 0 | const size_t numrecords = ntohs(dh->ancount) + ntohs(dh->nscount) + ntohs(dh->arcount); |
1155 | 0 | for(size_t n = 0; n < numrecords; ++n) { |
1156 | 0 | dpm.skipDomainName(); |
1157 | 0 | const uint16_t dnstype = dpm.get16BitInt(); |
1158 | | /* class */ |
1159 | 0 | const uint16_t dnsclass = dpm.get16BitInt(); |
1160 | |
|
1161 | 0 | if(dnstype == QType::OPT) { |
1162 | 0 | break; |
1163 | 0 | } |
1164 | | |
1165 | | /* report it if we see a SOA record in the AUTHORITY section */ |
1166 | 0 | if(dnstype == QType::SOA && dnsclass == QClass::IN && seenAuthSOA != nullptr && n >= ntohs(dh->ancount) && n < (ntohs(dh->ancount) + ntohs(dh->nscount))) { |
1167 | 0 | *seenAuthSOA = true; |
1168 | 0 | } |
1169 | |
|
1170 | 0 | const uint32_t ttl = dpm.get32BitInt(); |
1171 | 0 | result = std::min(result, ttl); |
1172 | |
|
1173 | 0 | dpm.skipRData(); |
1174 | 0 | } |
1175 | 0 | } |
1176 | 0 | catch(...) |
1177 | 0 | { |
1178 | 0 | } |
1179 | 0 | return result; |
1180 | 0 | } |
1181 | | |
1182 | | uint32_t getDNSPacketLength(const char* packet, size_t length) |
1183 | 0 | { |
1184 | 0 | uint32_t result = length; |
1185 | 0 | if(length < sizeof(dnsheader)) { |
1186 | 0 | return result; |
1187 | 0 | } |
1188 | 0 | try |
1189 | 0 | { |
1190 | 0 | const dnsheader_aligned dh(packet); |
1191 | 0 | DNSPacketMangler dpm(const_cast<char*>(packet), length); |
1192 | |
|
1193 | 0 | const uint16_t qdcount = ntohs(dh->qdcount); |
1194 | 0 | for(size_t n = 0; n < qdcount; ++n) { |
1195 | 0 | dpm.skipDomainName(); |
1196 | | /* type and class */ |
1197 | 0 | dpm.skipBytes(4); |
1198 | 0 | } |
1199 | 0 | const size_t numrecords = ntohs(dh->ancount) + ntohs(dh->nscount) + ntohs(dh->arcount); |
1200 | 0 | for(size_t n = 0; n < numrecords; ++n) { |
1201 | 0 | dpm.skipDomainName(); |
1202 | | /* type (2), class (2) and ttl (4) */ |
1203 | 0 | dpm.skipBytes(8); |
1204 | 0 | dpm.skipRData(); |
1205 | 0 | } |
1206 | 0 | result = dpm.getOffset(); |
1207 | 0 | } |
1208 | 0 | catch(...) |
1209 | 0 | { |
1210 | 0 | } |
1211 | 0 | return result; |
1212 | 0 | } |
1213 | | |
1214 | | uint16_t getRecordsOfTypeCount(const char* packet, size_t length, uint8_t section, uint16_t type) |
1215 | 0 | { |
1216 | 0 | uint16_t result = 0; |
1217 | 0 | if(length < sizeof(dnsheader)) { |
1218 | 0 | return result; |
1219 | 0 | } |
1220 | 0 | try |
1221 | 0 | { |
1222 | 0 | const dnsheader_aligned dh(packet); |
1223 | 0 | DNSPacketMangler dpm(const_cast<char*>(packet), length); |
1224 | |
|
1225 | 0 | const uint16_t qdcount = ntohs(dh->qdcount); |
1226 | 0 | for(size_t n = 0; n < qdcount; ++n) { |
1227 | 0 | dpm.skipDomainName(); |
1228 | 0 | if (section == 0) { |
1229 | 0 | uint16_t dnstype = dpm.get16BitInt(); |
1230 | 0 | if (dnstype == type) { |
1231 | 0 | result++; |
1232 | 0 | } |
1233 | | /* class */ |
1234 | 0 | dpm.skipBytes(2); |
1235 | 0 | } else { |
1236 | | /* type and class */ |
1237 | 0 | dpm.skipBytes(4); |
1238 | 0 | } |
1239 | 0 | } |
1240 | 0 | const uint16_t ancount = ntohs(dh->ancount); |
1241 | 0 | for(size_t n = 0; n < ancount; ++n) { |
1242 | 0 | dpm.skipDomainName(); |
1243 | 0 | if (section == 1) { |
1244 | 0 | uint16_t dnstype = dpm.get16BitInt(); |
1245 | 0 | if (dnstype == type) { |
1246 | 0 | result++; |
1247 | 0 | } |
1248 | | /* class */ |
1249 | 0 | dpm.skipBytes(2); |
1250 | 0 | } else { |
1251 | | /* type and class */ |
1252 | 0 | dpm.skipBytes(4); |
1253 | 0 | } |
1254 | | /* ttl */ |
1255 | 0 | dpm.skipBytes(4); |
1256 | 0 | dpm.skipRData(); |
1257 | 0 | } |
1258 | 0 | const uint16_t nscount = ntohs(dh->nscount); |
1259 | 0 | for(size_t n = 0; n < nscount; ++n) { |
1260 | 0 | dpm.skipDomainName(); |
1261 | 0 | if (section == 2) { |
1262 | 0 | uint16_t dnstype = dpm.get16BitInt(); |
1263 | 0 | if (dnstype == type) { |
1264 | 0 | result++; |
1265 | 0 | } |
1266 | | /* class */ |
1267 | 0 | dpm.skipBytes(2); |
1268 | 0 | } else { |
1269 | | /* type and class */ |
1270 | 0 | dpm.skipBytes(4); |
1271 | 0 | } |
1272 | | /* ttl */ |
1273 | 0 | dpm.skipBytes(4); |
1274 | 0 | dpm.skipRData(); |
1275 | 0 | } |
1276 | 0 | const uint16_t arcount = ntohs(dh->arcount); |
1277 | 0 | for(size_t n = 0; n < arcount; ++n) { |
1278 | 0 | dpm.skipDomainName(); |
1279 | 0 | if (section == 3) { |
1280 | 0 | uint16_t dnstype = dpm.get16BitInt(); |
1281 | 0 | if (dnstype == type) { |
1282 | 0 | result++; |
1283 | 0 | } |
1284 | | /* class */ |
1285 | 0 | dpm.skipBytes(2); |
1286 | 0 | } else { |
1287 | | /* type and class */ |
1288 | 0 | dpm.skipBytes(4); |
1289 | 0 | } |
1290 | | /* ttl */ |
1291 | 0 | dpm.skipBytes(4); |
1292 | 0 | dpm.skipRData(); |
1293 | 0 | } |
1294 | 0 | } |
1295 | 0 | catch(...) |
1296 | 0 | { |
1297 | 0 | } |
1298 | 0 | return result; |
1299 | 0 | } |
1300 | | |
1301 | | bool getEDNSUDPPayloadSizeAndZ(const char* packet, size_t length, uint16_t* payloadSize, uint16_t* z) |
1302 | 0 | { |
1303 | 0 | if (length < sizeof(dnsheader)) { |
1304 | 0 | return false; |
1305 | 0 | } |
1306 | | |
1307 | 0 | *payloadSize = 0; |
1308 | 0 | *z = 0; |
1309 | |
|
1310 | 0 | try |
1311 | 0 | { |
1312 | 0 | const dnsheader_aligned dh(packet); |
1313 | 0 | if (dh->arcount == 0) { |
1314 | | // The OPT pseudo-RR, if present, has to be in the additional section (https://datatracker.ietf.org/doc/html/rfc6891#section-6.1.1) |
1315 | 0 | return false; |
1316 | 0 | } |
1317 | | |
1318 | 0 | DNSPacketMangler dpm(const_cast<char*>(packet), length); |
1319 | |
|
1320 | 0 | const uint16_t qdcount = ntohs(dh->qdcount); |
1321 | 0 | for(size_t n = 0; n < qdcount; ++n) { |
1322 | 0 | dpm.skipDomainName(); |
1323 | | /* type and class */ |
1324 | 0 | dpm.skipBytes(4); |
1325 | 0 | } |
1326 | 0 | const size_t numrecords = ntohs(dh->ancount) + ntohs(dh->nscount) + ntohs(dh->arcount); |
1327 | 0 | for(size_t n = 0; n < numrecords; ++n) { |
1328 | 0 | dpm.skipDomainName(); |
1329 | 0 | const auto dnstype = dpm.get16BitInt(); |
1330 | |
|
1331 | 0 | if (dnstype == QType::OPT) { |
1332 | 0 | const auto dnsclass = dpm.get16BitInt(); |
1333 | | /* skip extended rcode and version */ |
1334 | 0 | dpm.skipBytes(2); |
1335 | 0 | *z = dpm.get16BitInt(); |
1336 | 0 | *payloadSize = dnsclass; |
1337 | 0 | return true; |
1338 | 0 | } |
1339 | | /* skip class */ |
1340 | 0 | dpm.skipBytes(2); |
1341 | | /* TTL */ |
1342 | 0 | dpm.skipBytes(4); |
1343 | 0 | dpm.skipRData(); |
1344 | 0 | } |
1345 | 0 | } |
1346 | 0 | catch(...) |
1347 | 0 | { |
1348 | 0 | } |
1349 | | |
1350 | 0 | return false; |
1351 | 0 | } |
1352 | | |
1353 | | bool visitDNSPacket(const std::string_view& packet, const std::function<bool(uint8_t, uint16_t, uint16_t, uint32_t, uint16_t, const char*)>& visitor) |
1354 | 0 | { |
1355 | 0 | if (packet.size() < sizeof(dnsheader)) { |
1356 | 0 | return false; |
1357 | 0 | } |
1358 | | |
1359 | 0 | try |
1360 | 0 | { |
1361 | 0 | const dnsheader_aligned dh(packet.data()); |
1362 | 0 | uint64_t numrecords = ntohs(dh->ancount) + ntohs(dh->nscount) + ntohs(dh->arcount); |
1363 | 0 | PacketReader reader(packet); |
1364 | |
|
1365 | 0 | uint64_t n; |
1366 | 0 | for (n = 0; n < ntohs(dh->qdcount) ; ++n) { |
1367 | 0 | (void) reader.getName(); |
1368 | | /* type and class */ |
1369 | 0 | reader.skip(4); |
1370 | 0 | } |
1371 | |
|
1372 | 0 | for (n = 0; n < numrecords; ++n) { |
1373 | 0 | (void) reader.getName(); |
1374 | |
|
1375 | 0 | uint8_t section = n < ntohs(dh->ancount) ? 1 : (n < (ntohs(dh->ancount) + ntohs(dh->nscount)) ? 2 : 3); |
1376 | 0 | uint16_t dnstype = reader.get16BitInt(); |
1377 | 0 | uint16_t dnsclass = reader.get16BitInt(); |
1378 | |
|
1379 | 0 | if (dnstype == QType::OPT) { |
1380 | | // not getting near that one with a stick |
1381 | 0 | break; |
1382 | 0 | } |
1383 | | |
1384 | 0 | uint32_t dnsttl = reader.get32BitInt(); |
1385 | 0 | uint16_t contentLength = reader.get16BitInt(); |
1386 | 0 | uint16_t pos = reader.getPosition(); |
1387 | 0 | reader.skip(contentLength); |
1388 | |
|
1389 | 0 | bool done = visitor(section, dnsclass, dnstype, dnsttl, contentLength, &packet.at(pos)); |
1390 | 0 | if (done) { |
1391 | 0 | return true; |
1392 | 0 | } |
1393 | 0 | } |
1394 | 0 | } |
1395 | 0 | catch (...) { |
1396 | 0 | return false; |
1397 | 0 | } |
1398 | | |
1399 | 0 | return true; |
1400 | 0 | } |