/src/pdns/pdns/dnsdistdist/dnsparser.hh
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 | | #pragma once |
23 | | #include <atomic> |
24 | | #include <map> |
25 | | #include <sstream> |
26 | | #include <stdexcept> |
27 | | #include <iostream> |
28 | | #include <unordered_set> |
29 | | #include <utility> |
30 | | #include <vector> |
31 | | #include <cerrno> |
32 | | // #include <netinet/in.h> |
33 | | #include "misc.hh" |
34 | | |
35 | | #include "dns.hh" |
36 | | #include "dnswriter.hh" |
37 | | #include "dnsname.hh" |
38 | | #include "noinitvector.hh" |
39 | | #include "pdnsexception.hh" |
40 | | #include "iputils.hh" |
41 | | #include "svc-records.hh" |
42 | | |
43 | | /** DNS records have three representations: |
44 | | 1) in the packet |
45 | | 2) parsed in a class, ready for use |
46 | | 3) in the zone |
47 | | |
48 | | We should implement bidirectional transitions between 1&2 and 2&3. |
49 | | Currently we have: 1 -> 2 |
50 | | 2 -> 3 |
51 | | |
52 | | We can add: 2 -> 1 easily by reversing the packetwriter |
53 | | And we might be able to reverse 2 -> 3 as well |
54 | | */ |
55 | | |
56 | | #include "namespaces.hh" |
57 | | |
58 | | class MOADNSException : public runtime_error |
59 | | { |
60 | | public: |
61 | 8.22k | MOADNSException(const string& str) : runtime_error(str) |
62 | 8.22k | {} |
63 | | }; |
64 | | |
65 | | |
66 | | class MOADNSParser; |
67 | | |
68 | | class PacketReader |
69 | | { |
70 | | public: |
71 | | PacketReader(const std::string_view& content, uint16_t initialPos=sizeof(dnsheader), bool internalRepresentation = false) |
72 | 10.3k | : d_pos(initialPos), d_startrecordpos(initialPos), d_content(content), d_internal(internalRepresentation) |
73 | 10.3k | { |
74 | 10.3k | if(content.size() > std::numeric_limits<uint16_t>::max()) |
75 | 0 | throw std::out_of_range("packet too large"); |
76 | | |
77 | 10.3k | d_recordlen = (uint16_t) content.size(); |
78 | 10.3k | not_used = 0; |
79 | 10.3k | } |
80 | | |
81 | | uint32_t get32BitInt(); |
82 | | uint16_t get16BitInt(); |
83 | | uint8_t get8BitInt(); |
84 | | |
85 | | void xfrNodeOrLocatorID(NodeOrLocatorID& val); |
86 | | void xfr48BitInt(uint64_t& val); |
87 | | |
88 | | void xfr32BitInt(uint32_t& val) |
89 | 7.29k | { |
90 | 7.29k | val=get32BitInt(); |
91 | 7.29k | } |
92 | | |
93 | | void xfrIP(uint32_t& val) |
94 | | { |
95 | | xfr32BitInt(val); |
96 | | val=htonl(val); |
97 | | } |
98 | | |
99 | | void xfrIP6(std::string &val) { |
100 | | xfrBlob(val, 16); |
101 | | } |
102 | | |
103 | 1.09k | void xfrCAWithoutPort(uint8_t version, ComboAddress &val) { |
104 | 1.09k | string blob; |
105 | 1.09k | if (version == 4) xfrBlob(blob, 4); |
106 | 88 | else if (version == 6) xfrBlob(blob, 16); |
107 | 0 | else throw runtime_error("invalid IP protocol"); |
108 | 1.09k | val = makeComboAddressFromRaw(version, blob); |
109 | 1.09k | } |
110 | | |
111 | 0 | void xfrCAPort(ComboAddress &val) { |
112 | 0 | uint16_t port; |
113 | 0 | xfr16BitInt(port); |
114 | 0 | val.sin4.sin_port = port; |
115 | 0 | } |
116 | | |
117 | | void xfrTime(uint32_t& val) |
118 | | { |
119 | | xfr32BitInt(val); |
120 | | } |
121 | | |
122 | | |
123 | | void xfr16BitInt(uint16_t& val) |
124 | 29.3k | { |
125 | 29.3k | val=get16BitInt(); |
126 | 29.3k | } |
127 | | |
128 | | void xfrType(uint16_t& val) |
129 | 1.14k | { |
130 | 1.14k | xfr16BitInt(val); |
131 | 1.14k | } |
132 | | |
133 | | |
134 | | void xfr8BitInt(uint8_t& val) |
135 | 23.9k | { |
136 | 23.9k | val=get8BitInt(); |
137 | 23.9k | } |
138 | | |
139 | | void xfrName(DNSName& name, bool /* compress */ = false) |
140 | 11.1k | { |
141 | 11.1k | name = getName(); |
142 | 11.1k | } |
143 | | |
144 | | void xfrText(string &text, bool multi=false, bool lenField=true) |
145 | | { |
146 | | text=getText(multi, lenField); |
147 | | } |
148 | | |
149 | | void xfrUnquotedText(string &text, bool lenField){ |
150 | | text=getUnquotedText(lenField); |
151 | | } |
152 | | |
153 | | void xfrBlob(string& blob); |
154 | | void xfrBlobNoSpaces(string& blob, int len); |
155 | | void xfrBlob(string& blob, int length); |
156 | | void xfrHexBlob(string& blob, bool keepReading=false); |
157 | | void xfrSvcParamKeyVals(set<SvcParam> &kvs); |
158 | | |
159 | | void getDnsrecordheader(struct dnsrecordheader &ah); |
160 | | void copyRecord(vector<unsigned char>& dest, uint16_t len); |
161 | | void copyRecord(unsigned char* dest, uint16_t len); |
162 | | |
163 | | DNSName getName(); |
164 | | string getText(bool multi, bool lenField); |
165 | | string getUnquotedText(bool lenField); |
166 | | |
167 | | /* Whether the current position is at (or after, which would indicate a problem |
168 | | and should have been detected earlier) the end of wire data corresponding to |
169 | | the current DNS record */ |
170 | | bool eof() const |
171 | | { |
172 | | return d_pos >= (d_startrecordpos + d_recordlen); |
173 | | } |
174 | | |
175 | | /* Returns a string, formatted for human/log consumption, containing information |
176 | | about the wire data from the current position to the end of the current DNS record */ |
177 | | std::string getRemaining() const { |
178 | | return "Remaining data from PacketReader, current position " + std::to_string(d_pos) + " in packet of size " + std::to_string(d_content.size()) + ", end of record expected at " + std::to_string(d_startrecordpos + d_recordlen) + ": " + makeHexDump(std::string(d_content.begin() + d_pos, d_content.begin() + d_startrecordpos + d_recordlen)); |
179 | | }; |
180 | | |
181 | | #if defined(PDNS_AUTH) // [ |
182 | | /* This method moves the position to the end of the current DNS record. |
183 | | The only case where it makes sense to call this method is when processing ENT |
184 | | records, and only because of a bug in the authoritative server used to insert |
185 | | non-empty content for some ENT records (see https://github.com/PowerDNS/pdns/pull/17000) |
186 | | */ |
187 | | void consumeRemaining() |
188 | | { |
189 | | d_pos = (d_startrecordpos + d_recordlen); |
190 | | } |
191 | | #endif // ] |
192 | | |
193 | | uint16_t getPosition() const |
194 | 86.3k | { |
195 | 86.3k | return d_pos; |
196 | 86.3k | } |
197 | | |
198 | | void skip(uint16_t n) |
199 | 0 | { |
200 | 0 | size_t stop = d_pos; |
201 | 0 | stop += n; |
202 | 0 | if (stop > d_content.size()) { |
203 | 0 | throw std::out_of_range("Attempt to skip bytes (" + std::to_string(n) + " starting at " + std::to_string(d_pos) + ") farther than the packet's end (" + std::to_string(d_content.size()) + ")"); |
204 | 0 | } |
205 | 0 | d_pos += n; |
206 | 0 | } |
207 | | |
208 | | private: |
209 | | uint16_t d_pos; |
210 | | uint16_t d_startrecordpos; // needed for getBlob later on |
211 | | uint16_t d_recordlen; // ditto |
212 | | uint16_t not_used; // Aligns the whole class on 8-byte boundaries |
213 | | const std::string_view d_content; |
214 | | bool d_internal; |
215 | | }; |
216 | | |
217 | | struct DNSRecord; |
218 | | |
219 | | class DNSRecordContent |
220 | | { |
221 | | public: |
222 | | static std::shared_ptr<DNSRecordContent> make(const DNSRecord& dr, PacketReader& pr); |
223 | | static std::shared_ptr<DNSRecordContent> make(const DNSRecord& dr, PacketReader& pr, uint16_t opcode); |
224 | | static std::shared_ptr<DNSRecordContent> make(uint16_t qtype, uint16_t qclass, const string& zone); |
225 | | static string upgradeContent(const DNSName& qname, const QType& qtype, const string& content); |
226 | | |
227 | | virtual std::string getZoneRepresentation(bool noDot=false) const = 0; |
228 | 77.5k | virtual ~DNSRecordContent() = default; |
229 | | virtual void toPacket(DNSPacketWriter& pw) const = 0; |
230 | | // returns the wire format of the content or the full record, possibly including compressed pointers pointing to the owner name (unless canonic or lowerCase are set) |
231 | | [[nodiscard]] string serialize(const DNSName& qname, bool canonic = false, bool lowerCase = false, bool full = false) const |
232 | 0 | { |
233 | 0 | vector<uint8_t> packet; |
234 | 0 | DNSPacketWriter packetWriter(packet, g_rootdnsname, QType::A); |
235 | |
|
236 | 0 | if (canonic) { |
237 | 0 | packetWriter.setCanonic(true); |
238 | 0 | } |
239 | 0 | if (lowerCase) { |
240 | 0 | packetWriter.setLowercase(true); |
241 | 0 | } |
242 | |
|
243 | 0 | packetWriter.startRecord(qname, getType()); |
244 | 0 | toPacket(packetWriter); |
245 | |
|
246 | 0 | string record; |
247 | 0 | if (full) { |
248 | 0 | packetWriter.getWireFormatContent(record); // needs to be called before commit() |
249 | 0 | } else { |
250 | 0 | packetWriter.getRecordPayload(record); // needs to be called before commit() |
251 | 0 | } |
252 | 0 | return record; |
253 | 0 | } |
254 | | |
255 | | virtual bool operator==(const DNSRecordContent& rhs) const |
256 | 0 | { |
257 | 0 | return typeid(*this)==typeid(rhs) && this->getZoneRepresentation() == rhs.getZoneRepresentation(); |
258 | 0 | } |
259 | | |
260 | | // parse the content in wire format, possibly including compressed pointers pointing to the owner name. |
261 | | // internalRepresentation is set when the data comes from an internal source, |
262 | | // such as the LMDB backend. |
263 | | static shared_ptr<DNSRecordContent> deserialize(const DNSName& qname, uint16_t qtype, const string& serialized, uint16_t qclass=QClass::IN, bool internalRepresentation = false); |
264 | | |
265 | | void doRecordCheck(const struct DNSRecord&){} |
266 | | |
267 | | typedef std::shared_ptr<DNSRecordContent> makerfunc_t(const struct DNSRecord& dr, PacketReader& pr); |
268 | | typedef std::shared_ptr<DNSRecordContent> zmakerfunc_t(const string& str); |
269 | | |
270 | | static void regist(uint16_t cl, uint16_t ty, makerfunc_t* f, zmakerfunc_t* z, const char* name) |
271 | | { |
272 | | assert(!d_locked.load()); // NOLINT: it's the API |
273 | | if(f) |
274 | | getTypemap()[pair(cl,ty)]=f; |
275 | | if(z) |
276 | | getZmakermap()[pair(cl,ty)]=z; |
277 | | |
278 | | getT2Namemap().emplace(pair(cl, ty), name); |
279 | | getN2Typemap().emplace(name, pair(cl, ty)); |
280 | | } |
281 | | |
282 | | static bool isUnknownType(const string& name) |
283 | | { |
284 | | return boost::starts_with(name, "TYPE") || boost::starts_with(name, "type"); |
285 | | } |
286 | | |
287 | | static uint16_t TypeToNumber(const string& name) |
288 | | { |
289 | | n2typemap_t::const_iterator iter = getN2Typemap().find(toUpper(name)); |
290 | | if(iter != getN2Typemap().end()) |
291 | | return iter->second.second; |
292 | | |
293 | | if (isUnknownType(name)) { |
294 | | return pdns::checked_stoi<uint16_t>(name.substr(4)); |
295 | | } |
296 | | |
297 | | throw runtime_error("Unknown DNS type '"+name+"'"); |
298 | | } |
299 | | |
300 | | static const string NumberToType(uint16_t num, uint16_t classnum = QClass::IN) |
301 | 0 | { |
302 | 0 | auto iter = getT2Namemap().find(pair(classnum, num)); |
303 | 0 | if(iter == getT2Namemap().end()) |
304 | 0 | return "TYPE" + std::to_string(num); |
305 | 0 | // throw runtime_error("Unknown DNS type with numerical id "+std::to_string(num)); |
306 | 0 | return iter->second; |
307 | 0 | } |
308 | | |
309 | | /** |
310 | | * \brief Return whether we have implemented a content representation for this type |
311 | | */ |
312 | | static bool isRegisteredType(uint16_t rtype, uint16_t rclass = QClass::IN); |
313 | | |
314 | | virtual uint16_t getType() const = 0; |
315 | | |
316 | | static void lock() |
317 | | { |
318 | | d_locked.store(true); |
319 | | } |
320 | | |
321 | | [[nodiscard]] virtual size_t sizeEstimate() const = 0; |
322 | | |
323 | | protected: |
324 | | typedef std::map<std::pair<uint16_t, uint16_t>, makerfunc_t* > typemap_t; |
325 | | typedef std::map<std::pair<uint16_t, uint16_t>, zmakerfunc_t* > zmakermap_t; |
326 | | typedef std::map<std::pair<uint16_t, uint16_t>, string > t2namemap_t; |
327 | | typedef std::map<string, std::pair<uint16_t, uint16_t> > n2typemap_t; |
328 | | static typemap_t& getTypemap(); |
329 | | static t2namemap_t& getT2Namemap(); |
330 | | static n2typemap_t& getN2Typemap(); |
331 | | static zmakermap_t& getZmakermap(); |
332 | | static std::atomic<bool> d_locked; |
333 | | }; |
334 | | |
335 | | struct DNSRecord |
336 | | { |
337 | | DNSRecord() : |
338 | 86.3k | d_class(QClass::IN) |
339 | 86.3k | {} |
340 | | explicit DNSRecord(const DNSResourceRecord& rr); |
341 | | DNSRecord(const std::string& name, |
342 | | std::shared_ptr<DNSRecordContent> content, |
343 | | const uint16_t type, |
344 | | const uint16_t qclass = QClass::IN, |
345 | | const uint32_t ttl = 86400, |
346 | | const uint16_t clen = 0, |
347 | | const DNSResourceRecord::Place place = DNSResourceRecord::ANSWER) : |
348 | | d_name(DNSName(name)), |
349 | | d_content(std::move(content)), |
350 | | d_type(type), |
351 | | d_class(qclass), |
352 | | d_ttl(ttl), |
353 | | d_clen(clen), |
354 | 0 | d_place(place) {} |
355 | | |
356 | | DNSName d_name; |
357 | | private: |
358 | | std::shared_ptr<const DNSRecordContent> d_content; |
359 | | public: |
360 | | uint16_t d_type{}; |
361 | | uint16_t d_class{}; |
362 | | uint32_t d_ttl{}; |
363 | | uint16_t d_clen{}; |
364 | | DNSResourceRecord::Place d_place{DNSResourceRecord::ANSWER}; |
365 | | |
366 | | [[nodiscard]] std::string print(const std::string& indent = "") const |
367 | 0 | { |
368 | 0 | std::stringstream s; |
369 | 0 | s << indent << "Content = " << d_content->getZoneRepresentation() << std::endl; |
370 | 0 | s << indent << "Type = " << d_type << std::endl; |
371 | 0 | s << indent << "Class = " << d_class << std::endl; |
372 | 0 | s << indent << "TTL = " << d_ttl << std::endl; |
373 | 0 | s << indent << "clen = " << d_clen << std::endl; |
374 | 0 | s << indent << "Place = " << std::to_string(d_place) << std::endl; |
375 | 0 | return s.str(); |
376 | 0 | } |
377 | | |
378 | | [[nodiscard]] std::string toString() const |
379 | 0 | { |
380 | 0 | std::string ret(d_name.toLogString()); |
381 | 0 | ret += '|'; |
382 | 0 | ret += QType(d_type).toString(); |
383 | 0 | ret += '|'; |
384 | 0 | ret += getContent()->getZoneRepresentation(); |
385 | 0 | return ret; |
386 | 0 | } |
387 | | |
388 | | void setContent(const std::shared_ptr<const DNSRecordContent>& content) |
389 | 0 | { |
390 | 0 | d_content = content; |
391 | 0 | } |
392 | | |
393 | | void setContent(std::shared_ptr<const DNSRecordContent>&& content) |
394 | 76.5k | { |
395 | 76.5k | d_content = std::move(content); |
396 | 76.5k | } |
397 | | |
398 | | [[nodiscard]] const std::shared_ptr<const DNSRecordContent>& getContent() const |
399 | 0 | { |
400 | 0 | return d_content; |
401 | 0 | } |
402 | | |
403 | | bool operator<(const DNSRecord& rhs) const |
404 | 0 | { |
405 | 0 | if(std::tie(d_name, d_type, d_class, d_ttl) < std::tie(rhs.d_name, rhs.d_type, rhs.d_class, rhs.d_ttl)) |
406 | 0 | return true; |
407 | 0 |
|
408 | 0 | if(std::tie(d_name, d_type, d_class, d_ttl) != std::tie(rhs.d_name, rhs.d_type, rhs.d_class, rhs.d_ttl)) |
409 | 0 | return false; |
410 | 0 |
|
411 | 0 | string lzrp, rzrp; |
412 | 0 | if(d_content) |
413 | 0 | lzrp=toLower(d_content->getZoneRepresentation()); |
414 | 0 | if(rhs.d_content) |
415 | 0 | rzrp=toLower(rhs.d_content->getZoneRepresentation()); |
416 | 0 |
|
417 | 0 | return lzrp < rzrp; |
418 | 0 | } |
419 | | |
420 | | // this orders in canonical order and keeps the SOA record on top |
421 | | static bool prettyCompare(const DNSRecord& a, const DNSRecord& b) |
422 | 0 | { |
423 | 0 | auto aType = (a.d_type == QType::SOA) ? 0 : a.d_type; |
424 | 0 | auto bType = (b.d_type == QType::SOA) ? 0 : b.d_type; |
425 | 0 |
|
426 | 0 | int res = a.d_name.canonCompare_three_way(b.d_name, true); |
427 | 0 | if (res < 0) { |
428 | 0 | return true; |
429 | 0 | } |
430 | 0 | if (res > 0) { |
431 | 0 | return false; |
432 | 0 | } |
433 | 0 |
|
434 | 0 | if(std::tie(aType, a.d_class, a.d_ttl) < std::tie(bType, b.d_class, b.d_ttl)) |
435 | 0 | return true; |
436 | 0 |
|
437 | 0 | if(std::tie(aType, a.d_class, a.d_ttl) != std::tie(bType, b.d_class, b.d_ttl)) |
438 | 0 | return false; |
439 | 0 |
|
440 | 0 | string lzrp, rzrp; |
441 | 0 | if(a.d_content) |
442 | 0 | lzrp = a.d_content->getZoneRepresentation(); |
443 | 0 | if(b.d_content) |
444 | 0 | rzrp = b.d_content->getZoneRepresentation(); |
445 | 0 |
|
446 | 0 | switch (a.d_type) { |
447 | 0 | case QType::TXT: |
448 | 0 | case QType::SPF: |
449 | 0 | #if !defined(RECURSOR) |
450 | 0 | case QType::LUA: |
451 | 0 | #endif |
452 | 0 | return lzrp < rzrp; |
453 | 0 | default: |
454 | 0 | return toLower(lzrp) < toLower(rzrp); |
455 | 0 | } |
456 | 0 | } |
457 | | |
458 | | bool operator==(const DNSRecord& rhs) const |
459 | 0 | { |
460 | 0 | if (d_type != rhs.d_type || d_class != rhs.d_class || d_name != rhs.d_name) { |
461 | 0 | return false; |
462 | 0 | } |
463 | 0 |
|
464 | 0 | return *d_content == *rhs.d_content; |
465 | 0 | } |
466 | | |
467 | | [[nodiscard]] size_t sizeEstimate() const |
468 | 0 | { |
469 | 0 | return sizeof(*this) + d_name.sizeEstimate() + (d_content ? d_content->sizeEstimate() : 0U); |
470 | 0 | } |
471 | | }; |
472 | | |
473 | | struct DNSZoneRecord |
474 | | { |
475 | | domainid_t domain_id{UnknownDomainID}; |
476 | | uint8_t scopeMask{0}; |
477 | | int signttl{0}; |
478 | | DNSName wildcardname; |
479 | | bool auth{true}; |
480 | | bool disabled{false}; |
481 | | DNSRecord dr; |
482 | | |
483 | 0 | bool operator<(const DNSZoneRecord& other) const { |
484 | 0 | return dr.d_ttl < other.dr.d_ttl; |
485 | 0 | } |
486 | | }; |
487 | | |
488 | | class UnknownRecordContent : public DNSRecordContent |
489 | | { |
490 | | public: |
491 | | UnknownRecordContent(const DNSRecord& dr, PacketReader& pr) |
492 | 51.3k | : d_dr(dr) |
493 | 51.3k | { |
494 | 51.3k | pr.copyRecord(d_record, dr.d_clen); |
495 | 51.3k | } |
496 | | |
497 | | UnknownRecordContent(const string& zone); |
498 | | |
499 | | string getZoneRepresentation(bool noDot) const override; |
500 | | void toPacket(DNSPacketWriter& pw) const override; |
501 | | uint16_t getType() const override |
502 | 0 | { |
503 | 0 | return d_dr.d_type; |
504 | 0 | } |
505 | | |
506 | | const vector<uint8_t>& getRawContent() const |
507 | 0 | { |
508 | 0 | return d_record; |
509 | 0 | } |
510 | | |
511 | | [[nodiscard]] size_t sizeEstimate() const override |
512 | 0 | { |
513 | 0 | return sizeof(*this) + d_dr.sizeEstimate() + d_record.size(); |
514 | 0 | } |
515 | | |
516 | | private: |
517 | | DNSRecord d_dr; |
518 | | vector<uint8_t> d_record; |
519 | | }; |
520 | | |
521 | | //! This class can be used to parse incoming packets, and is copyable |
522 | | class MOADNSParser : public boost::noncopyable |
523 | | { |
524 | | public: |
525 | | //! Parse from a string |
526 | | MOADNSParser(bool query, const string& buffer): d_tsigPos(0) |
527 | 0 | { |
528 | 0 | init(query, buffer); |
529 | 0 | } |
530 | | |
531 | | //! Parse from a pointer and length |
532 | | MOADNSParser(bool query, const char *packet, unsigned int len) : d_tsigPos(0) |
533 | | { |
534 | | init(query, std::string_view(packet, len)); |
535 | | } |
536 | | |
537 | | DNSName d_qname; |
538 | | uint16_t d_qclass, d_qtype; |
539 | | dnsheader d_header; |
540 | | |
541 | | using answers_t = vector<DNSRecord>; |
542 | | |
543 | | //! All answers contained in this packet (everything *but* the question section) |
544 | | answers_t d_answers; |
545 | | |
546 | | uint16_t getTSIGPos() const |
547 | 0 | { |
548 | 0 | return d_tsigPos; |
549 | 0 | } |
550 | | |
551 | | bool hasEDNS() const; |
552 | | |
553 | | private: |
554 | | void init(bool query, const std::string_view& packet); |
555 | | uint16_t d_tsigPos; |
556 | | }; |
557 | | |
558 | | string simpleCompress(const string& label, const string& root=""); |
559 | | void shuffleDNSPacket(char* packet, size_t length, const dnsheader_aligned& aligned_dh); |
560 | | void ageDNSPacket(char* packet, size_t length, uint32_t seconds, const dnsheader_aligned&); |
561 | | void ageDNSPacket(std::string& packet, uint32_t seconds, const dnsheader_aligned&); |
562 | | void editDNSPacketTTL(char* packet, size_t length, const std::function<uint32_t(uint8_t, uint16_t, uint16_t, uint32_t)>& visitor); |
563 | | void clearDNSPacketRecordTypes(vector<uint8_t>& packet, const std::unordered_set<QType>& qtypes); |
564 | | void clearDNSPacketRecordTypes(PacketBuffer& packet, const std::unordered_set<QType>& qtypes); |
565 | | void clearDNSPacketRecordTypes(char* packet, size_t& length, const std::unordered_set<QType>& qtypes); |
566 | | uint32_t getDNSPacketMinTTL(const char* packet, size_t length, bool* seenAuthSOA=nullptr); |
567 | | uint32_t getDNSPacketLength(const char* packet, size_t length); |
568 | | uint16_t getRecordsOfTypeCount(const char* packet, size_t length, uint8_t section, uint16_t type); |
569 | | bool getEDNSUDPPayloadSizeAndZ(const char* packet, size_t length, uint16_t* payloadSize, uint16_t* z); |
570 | | /* call the visitor for every records in the answer, authority and additional sections, passing the section, class, type, ttl, rdatalength and rdata |
571 | | to the visitor. Stops whenever the visitor returns false or at the end of the packet */ |
572 | | bool visitDNSPacket(const std::string_view& packet, const std::function<bool(uint8_t, uint16_t, uint16_t, uint32_t, uint16_t, const char*)>& visitor); |
573 | | |
574 | | template<typename T> |
575 | | std::shared_ptr<const T> getRR(const DNSRecord& dr) |
576 | | { |
577 | | return std::dynamic_pointer_cast<const T>(dr.getContent()); |
578 | | } |
579 | | |
580 | | /** Simple DNSPacketMangler. Ritual is: get a pointer into the packet and moveOffset() to beyond your needs |
581 | | * If you survive that, feel free to read from the pointer */ |
582 | | class DNSPacketMangler |
583 | | { |
584 | | public: |
585 | | explicit DNSPacketMangler(std::string& packet) |
586 | | : d_packet(packet.data()), d_length(packet.length()), d_notyouroffset(12), d_offset(d_notyouroffset) |
587 | 0 | {} |
588 | | DNSPacketMangler(char* packet, size_t length) |
589 | 0 | : d_packet(packet), d_length(length), d_notyouroffset(12), d_offset(d_notyouroffset) |
590 | 0 | {} |
591 | | |
592 | | /*! Advances past a wire-format domain name |
593 | | * The name is not checked for adherence to length restrictions. |
594 | | * Compression pointers are not followed. |
595 | | */ |
596 | | void skipDomainName() |
597 | 0 | { |
598 | 0 | uint8_t len; |
599 | 0 | while((len=get8BitInt())) { |
600 | 0 | if(len >= 0xc0) { // extended label |
601 | 0 | get8BitInt(); |
602 | 0 | return; |
603 | 0 | } |
604 | 0 | skipBytes(len); |
605 | 0 | } |
606 | 0 | } |
607 | | |
608 | | void skipBytes(uint16_t bytes) |
609 | 0 | { |
610 | 0 | moveOffset(bytes); |
611 | 0 | } |
612 | | void rewindBytes(uint16_t by) |
613 | 0 | { |
614 | 0 | rewindOffset(by); |
615 | 0 | } |
616 | | uint32_t get32BitInt() |
617 | 0 | { |
618 | 0 | const char* p = d_packet + d_offset; |
619 | 0 | moveOffset(4); |
620 | 0 | uint32_t ret; |
621 | 0 | memcpy(&ret, p, sizeof(ret)); |
622 | 0 | return ntohl(ret); |
623 | 0 | } |
624 | | uint16_t get16BitInt() |
625 | 0 | { |
626 | 0 | const char* p = d_packet + d_offset; |
627 | 0 | moveOffset(2); |
628 | 0 | uint16_t ret; |
629 | 0 | memcpy(&ret, p, sizeof(ret)); |
630 | 0 | return ntohs(ret); |
631 | 0 | } |
632 | | |
633 | | uint8_t get8BitInt() |
634 | 0 | { |
635 | 0 | const char* p = d_packet + d_offset; |
636 | 0 | moveOffset(1); |
637 | 0 | return *p; |
638 | 0 | } |
639 | | |
640 | | void skipRData() |
641 | 0 | { |
642 | 0 | auto toskip = get16BitInt(); |
643 | 0 | moveOffset(toskip); |
644 | 0 | } |
645 | | |
646 | | std::pair<uint32_t, uint32_t> skipRDataAndReturnOffsets() |
647 | 0 | { |
648 | 0 | auto toskip = get16BitInt(); |
649 | 0 | uint32_t start = d_offset; |
650 | 0 | moveOffset(toskip); |
651 | 0 | uint32_t end = d_offset; |
652 | 0 | return std::pair<uint32_t,uint32_t>(start, end); |
653 | 0 | } |
654 | | |
655 | | void decreaseAndSkip32BitInt(uint32_t decrease) |
656 | 0 | { |
657 | 0 | const char *p = d_packet + d_offset; |
658 | 0 | moveOffset(4); |
659 | |
|
660 | 0 | uint32_t tmp; |
661 | 0 | memcpy(&tmp, p, sizeof(tmp)); |
662 | 0 | tmp = ntohl(tmp); |
663 | 0 | if (tmp > decrease) { |
664 | 0 | tmp -= decrease; |
665 | 0 | } else { |
666 | 0 | tmp = 0; |
667 | 0 | } |
668 | 0 | tmp = htonl(tmp); |
669 | 0 | memcpy(d_packet + d_offset-4, (const char*)&tmp, sizeof(tmp)); |
670 | 0 | } |
671 | | |
672 | | void setAndSkip32BitInt(uint32_t value) |
673 | 0 | { |
674 | 0 | moveOffset(4); |
675 | |
|
676 | 0 | value = htonl(value); |
677 | 0 | memcpy(d_packet + d_offset-4, (const char*)&value, sizeof(value)); |
678 | 0 | } |
679 | | |
680 | | uint32_t getOffset() const |
681 | 0 | { |
682 | 0 | return d_offset; |
683 | 0 | } |
684 | | |
685 | 0 | void swapInPlace(std::pair<uint32_t, uint32_t> a, std::pair<uint32_t, uint32_t> b) { |
686 | | // some basic range checks |
687 | 0 | if (b.first < a.first) { |
688 | 0 | std::swap(a, b); |
689 | 0 | } |
690 | 0 | if (a.second-a.first != b.second-b.first) { |
691 | 0 | throw std::out_of_range("swap: segments have different lengths"); |
692 | 0 | } |
693 | 0 | if (a.second <= a.first) { |
694 | 0 | throw std::out_of_range("swap: ending of segment before start of segment"); |
695 | 0 | } |
696 | 0 | if (a.second > b.first) { |
697 | 0 | throw std::out_of_range("swap: overlapping segments"); |
698 | 0 | } |
699 | 0 | if (b.second > d_length) { |
700 | 0 | throw std::out_of_range("swap: ending of segment after end of array"); |
701 | 0 | } |
702 | | // don't allow to swap what we haven't read yet |
703 | 0 | if (b.second > d_offset) { |
704 | 0 | throw std::out_of_range("swap: ending of segment after current offset"); |
705 | 0 | } |
706 | 0 | std::swap_ranges(d_packet+a.first, d_packet+a.second, d_packet+b.first); |
707 | 0 | } |
708 | | |
709 | | private: |
710 | | void moveOffset(uint16_t by) |
711 | 0 | { |
712 | 0 | d_notyouroffset += by; |
713 | 0 | if(d_notyouroffset > d_length) |
714 | 0 | throw std::out_of_range("dns packet out of range: "+std::to_string(d_notyouroffset) +" > " |
715 | 0 | + std::to_string(d_length) ); |
716 | 0 | } |
717 | | |
718 | | void rewindOffset(uint16_t by) |
719 | 0 | { |
720 | 0 | if(d_notyouroffset < by) |
721 | 0 | throw std::out_of_range("Rewinding dns packet out of range: "+std::to_string(d_notyouroffset) +" < " |
722 | 0 | + std::to_string(by)); |
723 | 0 | d_notyouroffset -= by; |
724 | 0 | if(d_notyouroffset < 12) |
725 | 0 | throw std::out_of_range("Rewinding dns packet out of range: "+std::to_string(d_notyouroffset) +" < " |
726 | 0 | + std::to_string(12)); |
727 | 0 | } |
728 | | |
729 | | char* d_packet; |
730 | | size_t d_length; |
731 | | |
732 | | uint32_t d_notyouroffset; // only 'moveOffset' can touch this |
733 | | const uint32_t& d_offset; // look.. but don't touch |
734 | | }; |
735 | | |
736 | | string txtEscape(const string &name); |