]>
Commit | Line | Data |
---|---|---|
12471842 PL |
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 | */ | |
870a0fe4 AT |
22 | #ifdef HAVE_CONFIG_H |
23 | #include "config.h" | |
24 | #endif | |
8c0139b0 | 25 | #include <boost/container/static_vector.hpp> |
a0a276c2 BH |
26 | #include "dnswriter.hh" |
27 | #include "misc.hh" | |
28 | #include "dnsparser.hh" | |
8c0139b0 | 29 | |
a6c51664 | 30 | #include <limits.h> |
a0a276c2 | 31 | |
c2f3be9d | 32 | DNSPacketWriter::DNSPacketWriter(vector<uint8_t>& content, const DNSName& qname, uint16_t qtype, uint16_t qclass, uint8_t opcode) |
fea4599a | 33 | : d_content(content), d_qname(qname), d_canonic(false), d_lowerCase(false) |
a0a276c2 BH |
34 | { |
35 | d_content.clear(); | |
36 | dnsheader dnsheader; | |
950cfe0f | 37 | |
a0a276c2 | 38 | memset(&dnsheader, 0, sizeof(dnsheader)); |
8e97e9a3 | 39 | dnsheader.id=0; |
a0a276c2 | 40 | dnsheader.qdcount=htons(1); |
88c1bc50 | 41 | dnsheader.opcode=opcode; |
950cfe0f | 42 | |
a0a276c2 | 43 | const uint8_t* ptr=(const uint8_t*)&dnsheader; |
790e7c1b BH |
44 | uint32_t len=d_content.size(); |
45 | d_content.resize(len + sizeof(dnsheader)); | |
46 | uint8_t* dptr=(&*d_content.begin()) + len; | |
950cfe0f | 47 | |
790e7c1b | 48 | memcpy(dptr, ptr, sizeof(dnsheader)); |
2f4c3abb | 49 | d_stuff=0; |
fea4599a | 50 | d_namepositions.reserve(16); |
ad8fa726 | 51 | xfrName(qname, false); |
950cfe0f | 52 | |
790e7c1b | 53 | len=d_content.size(); |
678ce973 | 54 | d_content.resize(len + d_record.size() + 4); |
e984b465 | 55 | |
790e7c1b BH |
56 | ptr=&*d_record.begin(); |
57 | dptr=(&*d_content.begin()) + len; | |
950cfe0f | 58 | |
790e7c1b | 59 | memcpy(dptr, ptr, d_record.size()); |
790e7c1b | 60 | |
678ce973 | 61 | len+=d_record.size(); |
bca6643b BH |
62 | d_record.clear(); |
63 | ||
a0a276c2 | 64 | qtype=htons(qtype); |
a0a276c2 | 65 | qclass=htons(qclass); |
e984b465 BH |
66 | |
67 | vector<uint8_t>::iterator i=d_content.begin()+len; // this works around a gcc 3.4 bug | |
68 | memcpy(&*i, &qtype, 2); | |
69 | i+=2; | |
70 | memcpy(&*i, &qclass, 2); | |
ea634573 BH |
71 | |
72 | d_stuff=0xffff; | |
add935a2 | 73 | d_truncatemarker=d_content.size(); |
0eabbd4b AT |
74 | d_sor = 0; |
75 | d_rollbackmarker = 0; | |
76 | d_recordttl = 0; | |
77 | d_recordqtype = 0; | |
78 | d_recordqclass = QClass::IN; | |
e693ff5a | 79 | d_recordplace = DNSResourceRecord::ANSWER; |
a0a276c2 BH |
80 | } |
81 | ||
5a57d2ea | 82 | dnsheader* DNSPacketWriter::getHeader() |
8e97e9a3 | 83 | { |
b346905c | 84 | return reinterpret_cast<dnsheader*>(&*d_content.begin()); |
8e97e9a3 BH |
85 | } |
86 | ||
e693ff5a | 87 | void DNSPacketWriter::startRecord(const DNSName& name, uint16_t qtype, uint32_t ttl, uint16_t qclass, DNSResourceRecord::Place place, bool compress) |
a0a276c2 | 88 | { |
950cfe0f | 89 | if(!d_record.empty()) |
a0a276c2 | 90 | commit(); |
878435ce | 91 | |
a0a276c2 BH |
92 | d_recordqname=name; |
93 | d_recordqtype=qtype; | |
94 | d_recordqclass=qclass; | |
878435ce | 95 | d_recordttl=ttl; |
10321a98 | 96 | d_recordplace=place; |
878435ce | 97 | |
f4d26b4f | 98 | d_stuff = 0; |
10321a98 | 99 | d_rollbackmarker=d_content.size(); |
bca6643b | 100 | |
c2f3be9d | 101 | if(compress && d_recordqname.countLabels() && d_qname==d_recordqname) { // don't do the whole label compression thing if we *know* we can get away with "see question" - except when compressing the root |
6f724e8b PD |
102 | static unsigned char marker[2]={0xc0, 0x0c}; |
103 | d_content.insert(d_content.end(), (const char *) &marker[0], (const char *) &marker[2]); | |
a2ce25e4 BH |
104 | } |
105 | else { | |
ad8fa726 | 106 | xfrName(d_recordqname, compress); |
a2ce25e4 BH |
107 | d_content.insert(d_content.end(), d_record.begin(), d_record.end()); |
108 | d_record.clear(); | |
109 | } | |
f4d26b4f | 110 | |
bca6643b | 111 | d_stuff = sizeof(dnsrecordheader); // this is needed to get compressed label offsets right, the dnsrecordheader will be interspersed |
950cfe0f | 112 | d_sor=d_content.size() + d_stuff; // start of real record |
878435ce | 113 | } |
a0a276c2 | 114 | |
a683e8bd | 115 | void DNSPacketWriter::addOpt(uint16_t udpsize, int extRCode, int Z, const vector<pair<uint16_t,string> >& options) |
878435ce BH |
116 | { |
117 | uint32_t ttl=0; | |
705f31ae | 118 | |
009f9f55 | 119 | EDNS0Record stuff; |
878435ce BH |
120 | |
121 | stuff.extRCode=extRCode; | |
122 | stuff.version=0; | |
123 | stuff.Z=htons(Z); | |
7f7b8d55 | 124 | |
a683e8bd | 125 | static_assert(sizeof(EDNS0Record) == sizeof(ttl), "sizeof(EDNS0Record) must match sizeof(ttl)"); |
878435ce | 126 | memcpy(&ttl, &stuff, sizeof(stuff)); |
8a63d3ce | 127 | |
878435ce | 128 | ttl=ntohl(ttl); // will be reversed later on |
950cfe0f | 129 | |
12c06211 | 130 | startRecord(g_rootdnsname, QType::OPT, ttl, udpsize, DNSResourceRecord::ADDITIONAL, false); |
7f7b8d55 BH |
131 | for(optvect_t::const_iterator iter = options.begin(); iter != options.end(); ++iter) { |
132 | xfr16BitInt(iter->first); | |
133 | xfr16BitInt(iter->second.length()); | |
134 | xfrBlob(iter->second); | |
950cfe0f | 135 | } |
a0a276c2 BH |
136 | } |
137 | ||
341930bb BH |
138 | void DNSPacketWriter::xfr48BitInt(uint64_t val) |
139 | { | |
140 | unsigned char bytes[6]; | |
a6e93c0f | 141 | uint16_t theLeft = htons((val >> 32)&0xffffU); |
0407751c | 142 | uint32_t theRight = htonl(val & 0xffffffffU); |
a683e8bd RG |
143 | memcpy(bytes, (void*)&theLeft, sizeof(theLeft)); |
144 | memcpy(bytes+2, (void*)&theRight, sizeof(theRight)); | |
341930bb | 145 | |
a683e8bd | 146 | d_record.insert(d_record.end(), bytes, bytes + sizeof(bytes)); |
341930bb BH |
147 | } |
148 | ||
149 | ||
a0a276c2 BH |
150 | void DNSPacketWriter::xfr32BitInt(uint32_t val) |
151 | { | |
ea634573 BH |
152 | int rval=htonl(val); |
153 | uint8_t* ptr=reinterpret_cast<uint8_t*>(&rval); | |
a0a276c2 BH |
154 | d_record.insert(d_record.end(), ptr, ptr+4); |
155 | } | |
156 | ||
157 | void DNSPacketWriter::xfr16BitInt(uint16_t val) | |
158 | { | |
96aed220 | 159 | uint16_t rval=htons(val); |
ea634573 | 160 | uint8_t* ptr=reinterpret_cast<uint8_t*>(&rval); |
a0a276c2 BH |
161 | d_record.insert(d_record.end(), ptr, ptr+2); |
162 | } | |
163 | ||
164 | void DNSPacketWriter::xfr8BitInt(uint8_t val) | |
165 | { | |
166 | d_record.push_back(val); | |
167 | } | |
168 | ||
bac8f21b | 169 | |
950cfe0f | 170 | /* input: |
84e1142d | 171 | if lenField is true |
bac8f21b BH |
172 | "" -> 0 |
173 | "blah" -> 4blah | |
174 | "blah" "blah" -> output 4blah4blah | |
175 | "verylongstringlongerthan256....characters" \xffverylongstring\x23characters (autosplit) | |
176 | "blah\"blah" -> 9blah"blah | |
177 | "blah\97" -> 5blahb | |
84e1142d PL |
178 | |
179 | if lenField is false | |
180 | "blah" -> blah | |
181 | "blah\"blah" -> blah"blah | |
bac8f21b | 182 | */ |
84e1142d | 183 | void DNSPacketWriter::xfrText(const string& text, bool, bool lenField) |
a0a276c2 | 184 | { |
bac8f21b | 185 | if(text.empty()) { |
ef6a78d5 | 186 | d_record.push_back(0); |
bac8f21b BH |
187 | return; |
188 | } | |
189 | vector<string> segments = segmentDNSText(text); | |
ef7cd021 | 190 | for(const string& str : segments) { |
84e1142d PL |
191 | if(lenField) |
192 | d_record.push_back(str.length()); | |
bac8f21b | 193 | d_record.insert(d_record.end(), str.c_str(), str.c_str() + str.length()); |
ef6a78d5 | 194 | } |
a0a276c2 BH |
195 | } |
196 | ||
948a927f PL |
197 | void DNSPacketWriter::xfrUnquotedText(const string& text, bool lenField) |
198 | { | |
199 | if(text.empty()) { | |
200 | d_record.push_back(0); | |
201 | return; | |
202 | } | |
203 | if(lenField) | |
204 | d_record.push_back(text.length()); | |
205 | d_record.insert(d_record.end(), text.c_str(), text.c_str() + text.length()); | |
206 | } | |
207 | ||
802a93d0 | 208 | |
209 | static constexpr bool l_verbose=false; | |
210 | uint16_t DNSPacketWriter::lookupName(const DNSName& name, uint16_t* matchLen) | |
a2ce25e4 | 211 | { |
802a93d0 | 212 | // iterate over the written labels, see if we find a match |
213 | const auto& raw = name.getStorage(); | |
214 | ||
215 | /* name might be a.root-servers.net, we need to be able to benefit from finding: | |
216 | b.root-servers.net, or even: | |
217 | b\xc0\x0c | |
218 | */ | |
219 | unsigned int bestpos=0; | |
220 | *matchLen=0; | |
221 | boost::container::static_vector<uint16_t, 34> nvect, pvect; | |
222 | ||
223 | for(auto riter= raw.cbegin(); riter < raw.cend(); ) { | |
224 | if(!*riter) | |
a2ce25e4 | 225 | break; |
802a93d0 | 226 | nvect.push_back(riter - raw.cbegin()); |
227 | riter+=*riter+1; | |
228 | } | |
229 | ||
230 | if(l_verbose) { | |
231 | cout<<"Input vector for lookup "<<name<<": "; | |
232 | for(const auto n : nvect) | |
233 | cout << n<<" "; | |
234 | cout<<endl; | |
235 | cout<<makeHexDump(string(raw.c_str(), raw.c_str()+raw.size()))<<endl; | |
236 | } | |
a2ce25e4 | 237 | |
802a93d0 | 238 | if(l_verbose) |
fea4599a | 239 | cout<<"Have "<<d_namepositions.size()<<" to ponder"<<endl; |
802a93d0 | 240 | int counter=1; |
fea4599a | 241 | for(auto p : d_namepositions) { |
802a93d0 | 242 | vector<uint8_t>* source=0; |
243 | if(p < d_content.size()) | |
244 | source = &d_content; | |
245 | else { | |
246 | source = &d_record; | |
247 | p-= (d_content.size() + d_stuff); | |
248 | ||
249 | } | |
250 | if(l_verbose) { | |
251 | if(source == &d_content) { | |
252 | DNSName pname((const char*)&(*source)[0], (*source).size(), p, true); // only for debugging | |
fea4599a | 253 | cout<<"Looking at '"<<pname<<"' in packet at position "<<p<<"/"<<(*source).size()<<", option "<<counter<<"/"<<d_namepositions.size()<<endl; |
802a93d0 | 254 | } |
255 | else | |
256 | { | |
fea4599a | 257 | cout<<"Looking at *record* at position "<<p<<"/"<<(*source).size()<<", option "<<counter<<"/"<<d_namepositions.size()<<endl; |
802a93d0 | 258 | } |
259 | ++counter; | |
260 | } | |
261 | // memcmp here makes things _slower_ | |
262 | pvect.clear(); | |
263 | for(auto iter = (*source).cbegin() + p; iter < (*source).cend();) { | |
264 | ||
265 | uint8_t c=*iter; | |
266 | if(l_verbose) | |
267 | cout<<"Found label length: "<<(int)c<<endl; | |
268 | if(c & 0xc0) { | |
269 | ||
270 | uint16_t npos = 0x100*(c & (~0xc0)) + *++iter; | |
271 | iter = (*source).begin() + npos; | |
272 | if(l_verbose) | |
273 | cout<<"Is compressed label to newpos "<<npos<<", going there"<<endl; | |
274 | // check against going forward here | |
275 | continue; | |
276 | } | |
277 | if(!c) | |
278 | break; | |
279 | pvect.push_back(iter - (*source).cbegin()); | |
280 | iter+=*iter+1; | |
281 | } | |
282 | if(l_verbose) { | |
283 | cout<<"Packet vector: "<<endl; | |
284 | for(const auto n : pvect) | |
285 | cout << n<<" "; | |
286 | cout<<endl; | |
287 | } | |
288 | auto niter=nvect.crbegin(), piter=pvect.crbegin(); | |
289 | unsigned int cmatchlen=1; | |
290 | for(; niter != nvect.crend() && piter != pvect.crend(); ++niter, ++piter) { | |
291 | // niter is an offset in raw, pvect an offset in packet | |
292 | uint8_t nlen = raw[*niter], plen=(*source)[*piter]; | |
293 | if(l_verbose) | |
294 | cout<<"nlnen="<<(int)nlen<<", plen="<<(int)plen<<endl; | |
295 | if(nlen != plen) | |
296 | break; | |
297 | if(strncasecmp(raw.c_str()+*niter+1, (const char*)&(*source)[*piter]+1, nlen)) { | |
298 | if(l_verbose) | |
299 | cout<<"Mismatch: "<<string(raw.c_str()+*niter+1, raw.c_str()+*niter+nlen+1)<< " != "<<string((const char*)&(*source)[*piter]+1, (const char*)&(*source)[*piter]+nlen+1)<<endl; | |
300 | break; | |
301 | } | |
302 | cmatchlen+=nlen+1; | |
303 | if(cmatchlen == raw.length()) { // have matched all of it, can't improve | |
304 | if(l_verbose) | |
305 | cout<<"Stopping search, matched whole name"<<endl; | |
306 | *matchLen = cmatchlen; | |
307 | return *piter; | |
308 | } | |
309 | } | |
310 | if(piter != pvect.crbegin() && *matchLen < cmatchlen) { | |
311 | *matchLen = cmatchlen; | |
312 | bestpos=*--piter; | |
313 | } | |
314 | } | |
315 | return bestpos; | |
316 | } | |
38e655b6 | 317 | |
950cfe0f | 318 | // this is the absolute hottest function in the pdns recursor |
f21fc0aa | 319 | void DNSPacketWriter::xfrName(const DNSName& name, bool compress, bool) |
38e655b6 | 320 | { |
802a93d0 | 321 | if(l_verbose) |
322 | cout<<"Wants to write "<<name<<", compress="<<compress<<", canonic="<<d_canonic<<", LC="<<d_lowerCase<<endl; | |
323 | if(d_canonic || d_lowerCase) // d_lowerCase implies canonic | |
f3f4938f BH |
324 | compress=false; |
325 | ||
802a93d0 | 326 | if(name.empty() || name.isRoot()) { // for speed |
c1d02c0d BH |
327 | d_record.push_back(0); |
328 | return; | |
329 | } | |
950cfe0f | 330 | |
802a93d0 | 331 | uint16_t li=0; |
332 | uint16_t matchlen=0; | |
333 | if(compress && (li=lookupName(name, &matchlen))) { | |
334 | const auto& dns=name.getStorage(); | |
335 | if(l_verbose) | |
336 | cout<<"Found a substring of "<<matchlen<<" bytes from the back, offset: "<<li<<", dnslen: "<<dns.size()<<endl; | |
337 | // found a substring, if www.powerdns.com matched powerdns.com, we get back matchlen = 13 | |
338 | ||
339 | unsigned int pos=d_content.size() + d_record.size() + d_stuff; | |
340 | if(pos < 16384 && matchlen != dns.size()) { | |
341 | if(l_verbose) | |
342 | cout<<"Inserting pos "<<pos<<" for "<<name<<" for compressed case"<<endl; | |
fea4599a | 343 | d_namepositions.push_back(pos); |
bca6643b | 344 | } |
2f4c3abb | 345 | |
802a93d0 | 346 | if(l_verbose) |
347 | cout<<"Going to write unique part: '"<<makeHexDump(string(dns.c_str(), dns.c_str() + dns.size() - matchlen)) <<"'"<<endl; | |
348 | d_record.insert(d_record.end(), (const unsigned char*)dns.c_str(), (const unsigned char*)dns.c_str() + dns.size() - matchlen); | |
349 | uint16_t offset=li; | |
350 | offset|=0xc000; | |
a2ce25e4 | 351 | |
802a93d0 | 352 | d_record.push_back((char)(offset >> 8)); |
353 | d_record.push_back((char)(offset & 0xff)); | |
bca6643b | 354 | } |
802a93d0 | 355 | else { |
356 | unsigned int pos=d_content.size() + d_record.size() + d_stuff; | |
357 | if(l_verbose) | |
358 | cout<<"Found nothing, we are at pos "<<pos<<", inserting whole name"<<endl; | |
359 | if(pos < 16384) { | |
360 | if(l_verbose) | |
361 | cout<<"Inserting pos "<<pos<<" for "<<name<<" for uncompressed case"<<endl; | |
fea4599a | 362 | d_namepositions.push_back(pos); |
802a93d0 | 363 | } |
5be843df | 364 | |
802a93d0 | 365 | std::unique_ptr<DNSName> lc; |
366 | if(d_lowerCase) | |
367 | lc = make_unique<DNSName>(name.makeLowerCase()); | |
a0a276c2 | 368 | |
802a93d0 | 369 | const DNSName::string_t& raw = (lc ? *lc : name).getStorage(); |
370 | if(l_verbose) | |
371 | cout<<"Writing out the whole thing "<<makeHexDump(string(raw.c_str(), raw.c_str() + raw.length()))<<endl; | |
372 | d_record.insert(d_record.end(), raw.c_str(), raw.c_str() + raw.size()); | |
373 | } | |
a0a276c2 BH |
374 | } |
375 | ||
06ffdc52 | 376 | void DNSPacketWriter::xfrBlob(const string& blob, int ) |
8c1c9170 BH |
377 | { |
378 | const uint8_t* ptr=reinterpret_cast<const uint8_t*>(blob.c_str()); | |
8c1c9170 BH |
379 | d_record.insert(d_record.end(), ptr, ptr+blob.size()); |
380 | } | |
381 | ||
2fe9d6f7 AT |
382 | void DNSPacketWriter::xfrBlobNoSpaces(const string& blob, int ) |
383 | { | |
384 | xfrBlob(blob); | |
385 | } | |
386 | ||
e4090157 | 387 | void DNSPacketWriter::xfrHexBlob(const string& blob, bool keepReading) |
59a0f653 BH |
388 | { |
389 | xfrBlob(blob); | |
390 | } | |
391 | ||
ea634573 BH |
392 | void DNSPacketWriter::getRecords(string& records) |
393 | { | |
394 | records.assign(d_content.begin() + d_sor, d_content.end()); | |
395 | } | |
a0a276c2 | 396 | |
dffbaa08 | 397 | uint32_t DNSPacketWriter::size() |
10321a98 BH |
398 | { |
399 | return d_content.size() + d_stuff + d_record.size(); | |
400 | } | |
401 | ||
402 | void DNSPacketWriter::rollback() | |
403 | { | |
404 | d_content.resize(d_rollbackmarker); | |
405 | d_record.clear(); | |
406 | d_stuff=0; | |
407 | } | |
408 | ||
add935a2 PD |
409 | void DNSPacketWriter::truncate() |
410 | { | |
411 | d_content.resize(d_truncatemarker); | |
412 | d_record.clear(); | |
413 | d_stuff=0; | |
414 | dnsheader* dh=reinterpret_cast<dnsheader*>( &*d_content.begin()); | |
415 | dh->ancount = dh->nscount = dh->arcount = 0; | |
416 | } | |
417 | ||
a0a276c2 BH |
418 | void DNSPacketWriter::commit() |
419 | { | |
ea634573 BH |
420 | if(d_stuff==0xffff && (d_content.size()!=d_sor || !d_record.empty())) |
421 | throw MOADNSException("DNSPacketWriter::commit() called without startRecord ever having been called, but a record was added"); | |
bca6643b | 422 | // build dnsrecordheader |
a0a276c2 BH |
423 | struct dnsrecordheader drh; |
424 | drh.d_type=htons(d_recordqtype); | |
425 | drh.d_class=htons(d_recordqclass); | |
878435ce | 426 | drh.d_ttl=htonl(d_recordttl); |
a0a276c2 | 427 | drh.d_clen=htons(d_record.size()); |
950cfe0f | 428 | |
bca6643b BH |
429 | // and write out the header |
430 | const uint8_t* ptr=(const uint8_t*)&drh; | |
a0a276c2 BH |
431 | d_content.insert(d_content.end(), ptr, ptr+sizeof(drh)); |
432 | ||
10321a98 BH |
433 | d_stuff=0; |
434 | ||
6f8b3628 | 435 | // write out pending d_record |
a0a276c2 BH |
436 | d_content.insert(d_content.end(), d_record.begin(), d_record.end()); |
437 | ||
10321a98 BH |
438 | dnsheader* dh=reinterpret_cast<dnsheader*>( &*d_content.begin()); |
439 | switch(d_recordplace) { | |
e693ff5a AT |
440 | case DNSResourceRecord::QUESTION: |
441 | dh->qdcount = htons(ntohs(dh->qdcount) + 1); | |
442 | break; | |
443 | case DNSResourceRecord::ANSWER: | |
10321a98 BH |
444 | dh->ancount = htons(ntohs(dh->ancount) + 1); |
445 | break; | |
e693ff5a | 446 | case DNSResourceRecord::AUTHORITY: |
10321a98 BH |
447 | dh->nscount = htons(ntohs(dh->nscount) + 1); |
448 | break; | |
e693ff5a | 449 | case DNSResourceRecord::ADDITIONAL: |
10321a98 BH |
450 | dh->arcount = htons(ntohs(dh->arcount) + 1); |
451 | break; | |
452 | } | |
453 | ||
bca6643b | 454 | d_record.clear(); // clear d_record, ready for next record |
a0a276c2 | 455 | } |