]> git.ipfire.org Git - thirdparty/pdns.git/blob - pdns/dnswriter.cc
dnsdist: Fix DNS over plain HTTP broken by `reloadAllCertificates()`
[thirdparty/pdns.git] / pdns / dnswriter.cc
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 #ifdef HAVE_CONFIG_H
23 #include "config.h"
24 #endif
25 #include <boost/version.hpp>
26 #if BOOST_VERSION >= 105400
27 #include <boost/container/static_vector.hpp>
28 #endif
29 #include "dnswriter.hh"
30 #include "misc.hh"
31 #include "dnsparser.hh"
32
33 #include <limits.h>
34
35 /* d_content: <---- d_stuff ---->
36 v d_truncatemarker
37 dnsheader | qname | qtype | qclass | {recordname| dnsrecordheader | record }
38 ^ d_rollbackmarker ^ d_sor
39
40
41 */
42
43
44 template <typename Container> GenericDNSPacketWriter<Container>::GenericDNSPacketWriter(Container& content, const DNSName& qname, uint16_t qtype, uint16_t qclass, uint8_t opcode)
45 : d_content(content), d_qname(qname), d_canonic(false), d_lowerCase(false)
46 {
47 d_content.clear();
48 dnsheader dnsheader;
49
50 memset(&dnsheader, 0, sizeof(dnsheader));
51 dnsheader.id=0;
52 dnsheader.qdcount=htons(1);
53 dnsheader.opcode=opcode;
54
55 const uint8_t* ptr=(const uint8_t*)&dnsheader;
56 d_content.reserve(sizeof(dnsheader) + qname.wirelength() + sizeof(qtype) + sizeof(qclass));
57 d_content.resize(sizeof(dnsheader));
58 uint8_t* dptr=(&*d_content.begin());
59
60 memcpy(dptr, ptr, sizeof(dnsheader));
61 d_namepositions.reserve(16);
62 xfrName(qname, false);
63 xfr16BitInt(qtype);
64 xfr16BitInt(qclass);
65
66 d_truncatemarker=d_content.size();
67 d_sor = 0;
68 d_rollbackmarker = 0;
69 }
70
71 template <typename Container> dnsheader* GenericDNSPacketWriter<Container>::getHeader()
72 {
73 return reinterpret_cast<dnsheader*>(&*d_content.begin());
74 }
75
76
77 template <typename Container> void GenericDNSPacketWriter<Container>::startRecord(const DNSName& name, uint16_t qtype, uint32_t ttl, uint16_t qclass, DNSResourceRecord::Place place, bool compress)
78 {
79 d_compress = compress;
80 commit();
81 d_rollbackmarker=d_content.size();
82
83 if(compress && !name.isRoot() && d_qname==name) { // don't do the whole label compression thing if we *know* we can get away with "see question" - except when compressing the root
84 static unsigned char marker[2]={0xc0, 0x0c};
85 d_content.insert(d_content.end(), (const char *) &marker[0], (const char *) &marker[2]);
86 }
87 else {
88 xfrName(name, compress);
89 }
90 xfr16BitInt(qtype);
91 xfr16BitInt(qclass);
92 xfr32BitInt(ttl);
93 xfr16BitInt(0); // this will be the record size
94 d_recordplace = place;
95 d_sor=d_content.size(); // this will remind us where to stuff the record size
96 }
97
98 template <typename Container> void GenericDNSPacketWriter<Container>::addOpt(const uint16_t udpsize, const uint16_t extRCode, const uint16_t ednsFlags, const optvect_t& options, const uint8_t version)
99 {
100 uint32_t ttl=0;
101
102 EDNS0Record stuff;
103
104 stuff.version = version;
105 stuff.extFlags = htons(ednsFlags);
106
107 /* RFC 6891 section 4 on the Extended RCode wire format
108 * EXTENDED-RCODE
109 * Forms the upper 8 bits of extended 12-bit RCODE (together with the
110 * 4 bits defined in [RFC1035]. Note that EXTENDED-RCODE value 0
111 * indicates that an unextended RCODE is in use (values 0 through 15).
112 */
113 // XXX Should be check for extRCode > 1<<12 ?
114 stuff.extRCode = extRCode>>4;
115 if (extRCode != 0) { // As this trumps the existing RCODE
116 getHeader()->rcode = extRCode;
117 }
118
119 static_assert(sizeof(EDNS0Record) == sizeof(ttl), "sizeof(EDNS0Record) must match sizeof(ttl)");
120 memcpy(&ttl, &stuff, sizeof(stuff));
121
122 ttl=ntohl(ttl); // will be reversed later on
123
124 startRecord(g_rootdnsname, QType::OPT, ttl, udpsize, DNSResourceRecord::ADDITIONAL, false);
125 for(auto const &option : options) {
126 xfr16BitInt(option.first);
127 xfr16BitInt(option.second.length());
128 xfrBlob(option.second);
129 }
130 }
131
132 template <typename Container> void GenericDNSPacketWriter<Container>::xfr48BitInt(uint64_t val)
133 {
134 std::array<unsigned char, 6> bytes;
135 uint16_t theLeft = htons((val >> 32)&0xffffU);
136 uint32_t theRight = htonl(val & 0xffffffffU);
137 memcpy(&bytes[0], (void*)&theLeft, sizeof(theLeft));
138 memcpy(&bytes[2], (void*)&theRight, sizeof(theRight));
139
140 d_content.insert(d_content.end(), bytes.begin(), bytes.end());
141 }
142
143 template <typename Container> void GenericDNSPacketWriter<Container>::xfrNodeOrLocatorID(const NodeOrLocatorID& val)
144 {
145 d_content.insert(d_content.end(), val.content, val.content + sizeof(val.content));
146 }
147
148 template <typename Container> void GenericDNSPacketWriter<Container>::xfr32BitInt(uint32_t val)
149 {
150 uint32_t rval=htonl(val);
151 uint8_t* ptr=reinterpret_cast<uint8_t*>(&rval);
152 d_content.insert(d_content.end(), ptr, ptr+4);
153 }
154
155 template <typename Container> void GenericDNSPacketWriter<Container>::xfr16BitInt(uint16_t val)
156 {
157 uint16_t rval=htons(val);
158 uint8_t* ptr=reinterpret_cast<uint8_t*>(&rval);
159 d_content.insert(d_content.end(), ptr, ptr+2);
160 }
161
162 template <typename Container> void GenericDNSPacketWriter<Container>::xfr8BitInt(uint8_t val)
163 {
164 d_content.push_back(val);
165 }
166
167
168 /* input:
169 if lenField is true
170 "" -> 0
171 "blah" -> 4blah
172 "blah" "blah" -> output 4blah4blah
173 "verylongstringlongerthan256....characters" \xffverylongstring\x23characters (autosplit)
174 "blah\"blah" -> 9blah"blah
175 "blah\97" -> 5blahb
176
177 if lenField is false
178 "blah" -> blah
179 "blah\"blah" -> blah"blah
180 */
181 template <typename Container> void GenericDNSPacketWriter<Container>::xfrText(const string& text, bool, bool lenField)
182 {
183 if(text.empty()) {
184 d_content.push_back(0);
185 return;
186 }
187 vector<string> segments = segmentDNSText(text);
188 for(const string& str : segments) {
189 if(lenField)
190 d_content.push_back(str.length());
191 d_content.insert(d_content.end(), str.c_str(), str.c_str() + str.length());
192 }
193 }
194
195 template <typename Container> void GenericDNSPacketWriter<Container>::xfrUnquotedText(const string& text, bool lenField)
196 {
197 if(text.empty()) {
198 d_content.push_back(0);
199 return;
200 }
201 if(lenField)
202 d_content.push_back(text.length());
203 d_content.insert(d_content.end(), text.c_str(), text.c_str() + text.length());
204 }
205
206
207 static constexpr bool l_verbose=false;
208 static constexpr uint16_t maxCompressionOffset=16384;
209 template <typename Container> uint16_t GenericDNSPacketWriter<Container>::lookupName(const DNSName& name, uint16_t* matchLen)
210 {
211 // iterate over the written labels, see if we find a match
212 const auto& raw = name.getStorage();
213
214 /* name might be a.root-servers.net, we need to be able to benefit from finding:
215 b.root-servers.net, or even:
216 b\xc0\x0c
217 */
218 unsigned int bestpos=0;
219 *matchLen=0;
220 #if BOOST_VERSION >= 105400
221 boost::container::static_vector<uint16_t, 34> nvect, pvect;
222 #else
223 vector<uint16_t> nvect, pvect;
224 #endif
225
226 try {
227 for(auto riter= raw.cbegin(); riter < raw.cend(); ) {
228 if(!*riter)
229 break;
230 nvect.push_back(riter - raw.cbegin());
231 riter+=*riter+1;
232 }
233 }
234 catch(std::bad_alloc& ba) {
235 if(l_verbose)
236 cout<<"Domain "<<name<<" too large to compress"<<endl;
237 return 0;
238 }
239
240 if(l_verbose) {
241 cout<<"Input vector for lookup "<<name<<": ";
242 for(const auto n : nvect)
243 cout << n<<" ";
244 cout<<endl;
245 cout<<makeHexDump(string(raw.c_str(), raw.c_str()+raw.size()))<<endl;
246 }
247
248 if(l_verbose)
249 cout<<"Have "<<d_namepositions.size()<<" to ponder"<<endl;
250 int counter=1;
251 for(auto p : d_namepositions) {
252 if(l_verbose) {
253 cout<<"Pos: "<<p<<", "<<d_content.size()<<endl;
254 DNSName pname((const char*)&d_content[0], d_content.size(), p, true); // only for debugging
255 cout<<"Looking at '"<<pname<<"' in packet at position "<<p<<"/"<<d_content.size()<<", option "<<counter<<"/"<<d_namepositions.size()<<endl;
256 ++counter;
257 }
258 // memcmp here makes things _slower_
259 pvect.clear();
260 try {
261 for(auto iter = d_content.cbegin() + p; iter < d_content.cend();) {
262 uint8_t c=*iter;
263 if(l_verbose)
264 cout<<"Found label length: "<<(int)c<<endl;
265 if(c & 0xc0) {
266 uint16_t npos = 0x100*(c & (~0xc0)) + *++iter;
267 iter = d_content.begin() + npos;
268 if(l_verbose)
269 cout<<"Is compressed label to newpos "<<npos<<", going there"<<endl;
270 // check against going forward here
271 continue;
272 }
273 if(!c)
274 break;
275 auto offset = iter - d_content.cbegin();
276 if (offset >= maxCompressionOffset) break; // compression pointers cannot point here
277 pvect.push_back(offset);
278 iter+=*iter+1;
279 }
280 }
281 catch(std::bad_alloc& ba) {
282 if(l_verbose)
283 cout<<"Domain "<<name<<" too large to compress"<<endl;
284 continue;
285 }
286 if(l_verbose) {
287 cout<<"Packet vector: "<<endl;
288 for(const auto n : pvect)
289 cout << n<<" ";
290 cout<<endl;
291 }
292 auto niter=nvect.crbegin(), piter=pvect.crbegin();
293 unsigned int cmatchlen=1;
294 for(; niter != nvect.crend() && piter != pvect.crend(); ++niter, ++piter) {
295 // niter is an offset in raw, pvect an offset in packet
296 uint8_t nlen = raw[*niter], plen=d_content[*piter];
297 if(l_verbose)
298 cout<<"nlnen="<<(int)nlen<<", plen="<<(int)plen<<endl;
299 if(nlen != plen)
300 break;
301 if(strncasecmp(raw.c_str()+*niter+1, (const char*)&d_content[*piter]+1, nlen)) {
302 if(l_verbose)
303 cout<<"Mismatch: "<<string(raw.c_str()+*niter+1, raw.c_str()+*niter+nlen+1)<< " != "<<string((const char*)&d_content[*piter]+1, (const char*)&d_content[*piter]+nlen+1)<<endl;
304 break;
305 }
306 cmatchlen+=nlen+1;
307 if(cmatchlen == raw.length()) { // have matched all of it, can't improve
308 if(l_verbose)
309 cout<<"Stopping search, matched whole name"<<endl;
310 *matchLen = cmatchlen;
311 return *piter;
312 }
313 }
314 if(piter != pvect.crbegin() && *matchLen < cmatchlen) {
315 *matchLen = cmatchlen;
316 bestpos=*--piter;
317 }
318 }
319 return bestpos;
320 }
321 // this is the absolute hottest function in the pdns recursor
322 template <typename Container> void GenericDNSPacketWriter<Container>::xfrName(const DNSName& name, bool compress, bool)
323 {
324 if(l_verbose)
325 cout<<"Wants to write "<<name<<", compress="<<compress<<", canonic="<<d_canonic<<", LC="<<d_lowerCase<<endl;
326 if(d_canonic || d_lowerCase) // d_lowerCase implies canonic
327 compress=false;
328
329 if(name.empty() || name.isRoot()) { // for speed
330 d_content.push_back(0);
331 return;
332 }
333
334 uint16_t li=0;
335 uint16_t matchlen=0;
336 if(d_compress && compress && (li=lookupName(name, &matchlen)) && li < maxCompressionOffset) {
337 const auto& dns=name.getStorage();
338 if(l_verbose)
339 cout<<"Found a substring of "<<matchlen<<" bytes from the back, offset: "<<li<<", dnslen: "<<dns.size()<<endl;
340 // found a substring, if www.powerdns.com matched powerdns.com, we get back matchlen = 13
341
342 unsigned int pos=d_content.size();
343 if(pos < maxCompressionOffset && matchlen != dns.size()) {
344 if(l_verbose)
345 cout<<"Inserting pos "<<pos<<" for "<<name<<" for compressed case"<<endl;
346 d_namepositions.push_back(pos);
347 }
348
349 if(l_verbose)
350 cout<<"Going to write unique part: '"<<makeHexDump(string(dns.c_str(), dns.c_str() + dns.size() - matchlen)) <<"'"<<endl;
351 d_content.insert(d_content.end(), (const unsigned char*)dns.c_str(), (const unsigned char*)dns.c_str() + dns.size() - matchlen);
352 uint16_t offset=li;
353 offset|=0xc000;
354
355 d_content.push_back((char)(offset >> 8));
356 d_content.push_back((char)(offset & 0xff));
357 }
358 else {
359 unsigned int pos=d_content.size();
360 if(l_verbose)
361 cout<<"Found nothing, we are at pos "<<pos<<", inserting whole name"<<endl;
362 if(pos < maxCompressionOffset) {
363 if(l_verbose)
364 cout<<"Inserting pos "<<pos<<" for "<<name<<" for uncompressed case"<<endl;
365 d_namepositions.push_back(pos);
366 }
367
368 std::unique_ptr<DNSName> lc;
369 if(d_lowerCase)
370 lc = make_unique<DNSName>(name.makeLowerCase());
371
372 const DNSName::string_t& raw = (lc ? *lc : name).getStorage();
373 if(l_verbose)
374 cout<<"Writing out the whole thing "<<makeHexDump(string(raw.c_str(), raw.c_str() + raw.length()))<<endl;
375 d_content.insert(d_content.end(), raw.c_str(), raw.c_str() + raw.size());
376 }
377 }
378
379 template <typename Container> void GenericDNSPacketWriter<Container>::xfrBlob(const string& blob, int )
380 {
381 const uint8_t* ptr=reinterpret_cast<const uint8_t*>(blob.c_str());
382 d_content.insert(d_content.end(), ptr, ptr+blob.size());
383 }
384
385 template <typename Container> void GenericDNSPacketWriter<Container>::xfrBlob(const std::vector<uint8_t>& blob)
386 {
387 d_content.insert(d_content.end(), blob.begin(), blob.end());
388 }
389
390 template <typename Container> void GenericDNSPacketWriter<Container>::xfrBlobNoSpaces(const string& blob, int )
391 {
392 xfrBlob(blob);
393 }
394
395 template <typename Container> void GenericDNSPacketWriter<Container>::xfrHexBlob(const string& blob, bool /* keepReading */)
396 {
397 xfrBlob(blob);
398 }
399
400 template <typename Container> void GenericDNSPacketWriter<Container>::xfrSvcParamKeyVals(const std::set<SvcParam> &kvs)
401 {
402 for (auto const &param : kvs) {
403 // Key first!
404 xfr16BitInt(param.getKey());
405
406 switch (param.getKey())
407 {
408 case SvcParam::mandatory:
409 xfr16BitInt(2 * param.getMandatory().size());
410 for (auto const &m: param.getMandatory()) {
411 xfr16BitInt(m);
412 }
413 break;
414 case SvcParam::alpn:
415 {
416 uint16_t totalSize = param.getALPN().size(); // All 1 octet size headers for each value
417 for (auto const &a : param.getALPN()) {
418 totalSize += a.length();
419 }
420 xfr16BitInt(totalSize);
421 for (auto const &a : param.getALPN()) {
422 xfrUnquotedText(a, true); // will add the 1-byte length field
423 }
424 break;
425 }
426 case SvcParam::no_default_alpn:
427 xfr16BitInt(0); // no size :)
428 break;
429 case SvcParam::port:
430 xfr16BitInt(2); // size
431 xfr16BitInt(param.getPort());
432 break;
433 case SvcParam::ipv4hint:
434 xfr16BitInt(param.getIPHints().size() * 4); // size
435 for (const auto& a: param.getIPHints()) {
436 xfrCAWithoutPort(param.getKey(), a);
437 }
438 break;
439 case SvcParam::ipv6hint:
440 xfr16BitInt(param.getIPHints().size() * 16); // size
441 for (const auto& a: param.getIPHints()) {
442 xfrCAWithoutPort(param.getKey(), a);
443 }
444 break;
445 case SvcParam::ech:
446 xfr16BitInt(param.getECH().size()); // size
447 xfrBlobNoSpaces(param.getECH());
448 break;
449 default:
450 xfr16BitInt(param.getValue().size());
451 xfrBlob(param.getValue());
452 break;
453 }
454 }
455 }
456
457 // call __before commit__
458 template <typename Container> void GenericDNSPacketWriter<Container>::getRecordPayload(string& records)
459 {
460 records.assign(d_content.begin() + d_sor, d_content.end());
461 }
462
463 template <typename Container> uint32_t GenericDNSPacketWriter<Container>::size() const
464 {
465 return d_content.size();
466 }
467
468 template <typename Container> void GenericDNSPacketWriter<Container>::rollback()
469 {
470 d_content.resize(d_rollbackmarker);
471 d_sor = 0;
472 }
473
474 template <typename Container> void GenericDNSPacketWriter<Container>::truncate()
475 {
476 d_content.resize(d_truncatemarker);
477 dnsheader* dh=reinterpret_cast<dnsheader*>( &*d_content.begin());
478 dh->ancount = dh->nscount = dh->arcount = 0;
479 }
480
481 template <typename Container> void GenericDNSPacketWriter<Container>::commit()
482 {
483 if(!d_sor)
484 return;
485 uint16_t rlen = d_content.size() - d_sor;
486 d_content[d_sor-2]=rlen >> 8;
487 d_content[d_sor-1]=rlen & 0xff;
488 d_sor=0;
489 dnsheader* dh=reinterpret_cast<dnsheader*>( &*d_content.begin());
490 switch(d_recordplace) {
491 case DNSResourceRecord::QUESTION:
492 dh->qdcount = htons(ntohs(dh->qdcount) + 1);
493 break;
494 case DNSResourceRecord::ANSWER:
495 dh->ancount = htons(ntohs(dh->ancount) + 1);
496 break;
497 case DNSResourceRecord::AUTHORITY:
498 dh->nscount = htons(ntohs(dh->nscount) + 1);
499 break;
500 case DNSResourceRecord::ADDITIONAL:
501 dh->arcount = htons(ntohs(dh->arcount) + 1);
502 break;
503 }
504
505 }
506
507 template <typename Container> size_t GenericDNSPacketWriter<Container>::getSizeWithOpts(const optvect_t& options) const
508 {
509 size_t result = size() + /* root */ 1 + DNS_TYPE_SIZE + DNS_CLASS_SIZE + DNS_TTL_SIZE + DNS_RDLENGTH_SIZE;
510
511 for(auto const &option : options) {
512 result += 4;
513 result += option.second.size();
514 }
515
516 return result;
517 }
518
519 template class GenericDNSPacketWriter<std::vector<uint8_t>>;
520 #include "noinitvector.hh"
521 template class GenericDNSPacketWriter<PacketBuffer>;