| 1 | #include "dnswriter.hh" |
|---|
| 2 | #include "misc.hh" |
|---|
| 3 | #include "dnsparser.hh" |
|---|
| 4 | #include <boost/foreach.hpp> |
|---|
| 5 | #include <limits.h> |
|---|
| 6 | |
|---|
| 7 | DNSPacketWriter::DNSPacketWriter(vector<uint8_t>& content, const string& qname, uint16_t qtype, uint16_t qclass, uint8_t opcode) |
|---|
| 8 | : d_pos(0), d_content(content), d_qname(qname), d_qtype(qtype), d_qclass(qclass), d_canonic(false), d_lowerCase(false) |
|---|
| 9 | { |
|---|
| 10 | d_content.clear(); |
|---|
| 11 | dnsheader dnsheader; |
|---|
| 12 | |
|---|
| 13 | memset(&dnsheader, 0, sizeof(dnsheader)); |
|---|
| 14 | dnsheader.id=0; |
|---|
| 15 | dnsheader.qdcount=htons(1); |
|---|
| 16 | dnsheader.opcode=opcode; |
|---|
| 17 | |
|---|
| 18 | const uint8_t* ptr=(const uint8_t*)&dnsheader; |
|---|
| 19 | uint32_t len=d_content.size(); |
|---|
| 20 | d_content.resize(len + sizeof(dnsheader)); |
|---|
| 21 | uint8_t* dptr=(&*d_content.begin()) + len; |
|---|
| 22 | |
|---|
| 23 | memcpy(dptr, ptr, sizeof(dnsheader)); |
|---|
| 24 | d_stuff=0; |
|---|
| 25 | |
|---|
| 26 | xfrLabel(qname, false); |
|---|
| 27 | |
|---|
| 28 | len=d_content.size(); |
|---|
| 29 | d_content.resize(len + d_record.size() + 4); |
|---|
| 30 | |
|---|
| 31 | ptr=&*d_record.begin(); |
|---|
| 32 | dptr=(&*d_content.begin()) + len; |
|---|
| 33 | |
|---|
| 34 | memcpy(dptr, ptr, d_record.size()); |
|---|
| 35 | |
|---|
| 36 | len+=d_record.size(); |
|---|
| 37 | d_record.clear(); |
|---|
| 38 | |
|---|
| 39 | qtype=htons(qtype); |
|---|
| 40 | qclass=htons(qclass); |
|---|
| 41 | |
|---|
| 42 | vector<uint8_t>::iterator i=d_content.begin()+len; // this works around a gcc 3.4 bug |
|---|
| 43 | memcpy(&*i, &qtype, 2); |
|---|
| 44 | i+=2; |
|---|
| 45 | memcpy(&*i, &qclass, 2); |
|---|
| 46 | |
|---|
| 47 | d_stuff=0xffff; |
|---|
| 48 | d_labelmap.reserve(16); |
|---|
| 49 | } |
|---|
| 50 | |
|---|
| 51 | dnsheader* DNSPacketWriter::getHeader() |
|---|
| 52 | { |
|---|
| 53 | return (dnsheader*)&*d_content.begin(); |
|---|
| 54 | } |
|---|
| 55 | |
|---|
| 56 | void DNSPacketWriter::startRecord(const string& name, uint16_t qtype, uint32_t ttl, uint16_t qclass, Place place) |
|---|
| 57 | { |
|---|
| 58 | if(!d_record.empty()) |
|---|
| 59 | commit(); |
|---|
| 60 | |
|---|
| 61 | d_recordqname=name; |
|---|
| 62 | d_recordqtype=qtype; |
|---|
| 63 | d_recordqclass=qclass; |
|---|
| 64 | d_recordttl=ttl; |
|---|
| 65 | d_recordplace=place; |
|---|
| 66 | |
|---|
| 67 | d_stuff = 0; |
|---|
| 68 | d_rollbackmarker=d_content.size(); |
|---|
| 69 | |
|---|
| 70 | if(pdns_iequals(d_qname, d_recordqname)) { // don't do the whole label compression thing if we *know* we can get away with "see question" |
|---|
| 71 | static char marker[2]={0xc0, 0x0c}; |
|---|
| 72 | d_content.insert(d_content.end(), &marker[0], &marker[2]); |
|---|
| 73 | } |
|---|
| 74 | else { |
|---|
| 75 | xfrLabel(d_recordqname, true); |
|---|
| 76 | d_content.insert(d_content.end(), d_record.begin(), d_record.end()); |
|---|
| 77 | d_record.clear(); |
|---|
| 78 | } |
|---|
| 79 | |
|---|
| 80 | d_stuff = sizeof(dnsrecordheader); // this is needed to get compressed label offsets right, the dnsrecordheader will be interspersed |
|---|
| 81 | d_sor=d_content.size() + d_stuff; // start of real record |
|---|
| 82 | } |
|---|
| 83 | |
|---|
| 84 | void DNSPacketWriter::addOpt(int udpsize, int extRCode, int Z, const vector<pair<uint16_t,string> >& options) |
|---|
| 85 | { |
|---|
| 86 | uint32_t ttl=0; |
|---|
| 87 | |
|---|
| 88 | EDNS0Record stuff; |
|---|
| 89 | |
|---|
| 90 | stuff.extRCode=extRCode; |
|---|
| 91 | stuff.version=0; |
|---|
| 92 | stuff.Z=htons(Z); |
|---|
| 93 | |
|---|
| 94 | memcpy(&ttl, &stuff, sizeof(stuff)); |
|---|
| 95 | |
|---|
| 96 | ttl=ntohl(ttl); // will be reversed later on |
|---|
| 97 | |
|---|
| 98 | startRecord("", ns_t_opt, ttl, udpsize, ADDITIONAL); |
|---|
| 99 | for(optvect_t::const_iterator iter = options.begin(); iter != options.end(); ++iter) { |
|---|
| 100 | xfr16BitInt(iter->first); |
|---|
| 101 | xfr16BitInt(iter->second.length()); |
|---|
| 102 | xfrBlob(iter->second); |
|---|
| 103 | } |
|---|
| 104 | } |
|---|
| 105 | |
|---|
| 106 | void DNSPacketWriter::xfr48BitInt(uint64_t val) |
|---|
| 107 | { |
|---|
| 108 | unsigned char bytes[6]; |
|---|
| 109 | uint16_t theLeft = htons((val >> 32)&0xffffU); |
|---|
| 110 | uint32_t theRight = htonl(val & 0xffffffffU); |
|---|
| 111 | memcpy(bytes, (void*)&theLeft, 2); |
|---|
| 112 | memcpy(bytes+2, (void*)&theRight, 4); |
|---|
| 113 | |
|---|
| 114 | d_record.insert(d_record.end(), bytes, bytes + 6); |
|---|
| 115 | } |
|---|
| 116 | |
|---|
| 117 | |
|---|
| 118 | void DNSPacketWriter::xfr32BitInt(uint32_t val) |
|---|
| 119 | { |
|---|
| 120 | int rval=htonl(val); |
|---|
| 121 | uint8_t* ptr=reinterpret_cast<uint8_t*>(&rval); |
|---|
| 122 | d_record.insert(d_record.end(), ptr, ptr+4); |
|---|
| 123 | } |
|---|
| 124 | |
|---|
| 125 | void DNSPacketWriter::xfr16BitInt(uint16_t val) |
|---|
| 126 | { |
|---|
| 127 | uint16_t rval=htons(val); |
|---|
| 128 | uint8_t* ptr=reinterpret_cast<uint8_t*>(&rval); |
|---|
| 129 | d_record.insert(d_record.end(), ptr, ptr+2); |
|---|
| 130 | } |
|---|
| 131 | |
|---|
| 132 | void DNSPacketWriter::xfr8BitInt(uint8_t val) |
|---|
| 133 | { |
|---|
| 134 | d_record.push_back(val); |
|---|
| 135 | } |
|---|
| 136 | |
|---|
| 137 | |
|---|
| 138 | /* input: |
|---|
| 139 | "" -> 0 |
|---|
| 140 | "blah" -> 4blah |
|---|
| 141 | "blah" "blah" -> output 4blah4blah |
|---|
| 142 | "verylongstringlongerthan256....characters" \xffverylongstring\x23characters (autosplit) |
|---|
| 143 | "blah\"blah" -> 9blah"blah |
|---|
| 144 | "blah\97" -> 5blahb |
|---|
| 145 | */ |
|---|
| 146 | void DNSPacketWriter::xfrText(const string& text, bool) |
|---|
| 147 | { |
|---|
| 148 | if(text.empty()) { |
|---|
| 149 | d_record.push_back(0); |
|---|
| 150 | return; |
|---|
| 151 | } |
|---|
| 152 | vector<string> segments = segmentDNSText(text); |
|---|
| 153 | BOOST_FOREACH(const string& str, segments) { |
|---|
| 154 | d_record.push_back(str.length()); |
|---|
| 155 | d_record.insert(d_record.end(), str.c_str(), str.c_str() + str.length()); |
|---|
| 156 | } |
|---|
| 157 | } |
|---|
| 158 | |
|---|
| 159 | DNSPacketWriter::lmap_t::iterator find(DNSPacketWriter::lmap_t& lmap, const string& label) |
|---|
| 160 | { |
|---|
| 161 | DNSPacketWriter::lmap_t::iterator ret; |
|---|
| 162 | for(ret=lmap.begin(); ret != lmap.end(); ++ret) |
|---|
| 163 | if(pdns_iequals(ret->first ,label)) |
|---|
| 164 | break; |
|---|
| 165 | return ret; |
|---|
| 166 | } |
|---|
| 167 | |
|---|
| 168 | //! tokenize a label into parts, the parts describe a begin offset and an end offset |
|---|
| 169 | bool labeltokUnescape(labelparts_t& parts, const string& label) |
|---|
| 170 | { |
|---|
| 171 | string::size_type epos = label.size(), lpos(0), pos; |
|---|
| 172 | bool unescapedSomething = false; |
|---|
| 173 | const char* ptr=label.c_str(); |
|---|
| 174 | |
|---|
| 175 | parts.clear(); |
|---|
| 176 | |
|---|
| 177 | for(pos = 0 ; pos < epos; ++pos) { |
|---|
| 178 | if(ptr[pos]=='\\') { |
|---|
| 179 | pos++; |
|---|
| 180 | unescapedSomething = true; |
|---|
| 181 | continue; |
|---|
| 182 | } |
|---|
| 183 | if(ptr[pos]=='.') { |
|---|
| 184 | parts.push_back(make_pair(lpos, pos)); |
|---|
| 185 | lpos=pos+1; |
|---|
| 186 | } |
|---|
| 187 | } |
|---|
| 188 | |
|---|
| 189 | if(lpos < pos) |
|---|
| 190 | parts.push_back(make_pair(lpos, pos)); |
|---|
| 191 | return unescapedSomething; |
|---|
| 192 | } |
|---|
| 193 | |
|---|
| 194 | // this is the absolute hottest function in the pdns recursor |
|---|
| 195 | void DNSPacketWriter::xfrLabel(const string& Label, bool compress) |
|---|
| 196 | { |
|---|
| 197 | string label = d_lowerCase ? toLower(Label) : Label; |
|---|
| 198 | labelparts_t parts; |
|---|
| 199 | |
|---|
| 200 | if(d_canonic) |
|---|
| 201 | compress=false; |
|---|
| 202 | |
|---|
| 203 | string::size_type labellen = label.size(); |
|---|
| 204 | if(labellen==1 && label[0]=='.') { // otherwise we encode '..' |
|---|
| 205 | d_record.push_back(0); |
|---|
| 206 | return; |
|---|
| 207 | } |
|---|
| 208 | bool unescaped=labeltokUnescape(parts, label); |
|---|
| 209 | |
|---|
| 210 | // d_stuff is amount of stuff that is yet to be written out - the dnsrecordheader for example |
|---|
| 211 | unsigned int pos=d_content.size() + d_record.size() + d_stuff; |
|---|
| 212 | string chopped; |
|---|
| 213 | bool deDot = labellen && (label[labellen-1]=='.'); // make sure we don't store trailing dots in the labelmap |
|---|
| 214 | |
|---|
| 215 | for(labelparts_t::const_iterator i=parts.begin(); i!=parts.end(); ++i) { |
|---|
| 216 | if(deDot) |
|---|
| 217 | chopped.assign(label.c_str() + i->first, labellen - i->first -1); |
|---|
| 218 | else |
|---|
| 219 | chopped.assign(label.c_str() + i->first); |
|---|
| 220 | |
|---|
| 221 | lmap_t::iterator li=d_labelmap.end(); |
|---|
| 222 | // see if we've written out this domain before |
|---|
| 223 | // cerr<<"Searching for compression pointer to '"<<chopped<<"', "<<d_labelmap.size()<<" cmp-records"<<endl; |
|---|
| 224 | if(compress && (li=find(d_labelmap, chopped))!=d_labelmap.end()) { |
|---|
| 225 | // cerr<<"\tFound a compression pointer to '"<<chopped<<"': "<<li->second<<endl; |
|---|
| 226 | uint16_t offset=li->second; |
|---|
| 227 | offset|=0xc000; |
|---|
| 228 | d_record.push_back((char)(offset >> 8)); |
|---|
| 229 | d_record.push_back((char)(offset & 0xff)); |
|---|
| 230 | goto out; // skip trailing 0 in case of compression |
|---|
| 231 | } |
|---|
| 232 | |
|---|
| 233 | if(li==d_labelmap.end() && pos< 16384) { |
|---|
| 234 | // cerr<<"\tStoring a compression pointer to '"<<chopped<<"': "<<pos<<endl; |
|---|
| 235 | d_labelmap.push_back(make_pair(chopped, pos)); // if untrue, we need to count - also, don't store offsets > 16384, won't work |
|---|
| 236 | } |
|---|
| 237 | |
|---|
| 238 | if(unescaped) { |
|---|
| 239 | string part(label.c_str() + i -> first, i->second - i->first); |
|---|
| 240 | boost::replace_all(part, "\\.", "."); |
|---|
| 241 | boost::replace_all(part, "\\032", " "); |
|---|
| 242 | boost::replace_all(part, "\\\\", "\\"); |
|---|
| 243 | if(part.size() > 255) |
|---|
| 244 | throw MOADNSException("DNSPacketWriter::xfrLabel() tried to write an overly large label"); |
|---|
| 245 | d_record.push_back(part.size()); |
|---|
| 246 | unsigned int len=d_record.size(); |
|---|
| 247 | d_record.resize(len + part.size()); |
|---|
| 248 | |
|---|
| 249 | memcpy(((&*d_record.begin()) + len), part.c_str(), part.size()); |
|---|
| 250 | pos+=(part.size())+1; |
|---|
| 251 | } |
|---|
| 252 | else { |
|---|
| 253 | char labelsize=(char)(i->second - i->first); |
|---|
| 254 | if(!labelsize) // empty label in the middle of name |
|---|
| 255 | throw MOADNSException("DNSPacketWriter::xfrLabel() found empty label in the middle of name"); |
|---|
| 256 | d_record.push_back(labelsize); |
|---|
| 257 | unsigned int len=d_record.size(); |
|---|
| 258 | d_record.resize(len + i->second - i->first); |
|---|
| 259 | memcpy(((&*d_record.begin()) + len), label.c_str() + i-> first, i->second - i->first); |
|---|
| 260 | pos+=(i->second - i->first)+1; |
|---|
| 261 | } |
|---|
| 262 | } |
|---|
| 263 | d_record.push_back(0); |
|---|
| 264 | |
|---|
| 265 | out:; |
|---|
| 266 | } |
|---|
| 267 | |
|---|
| 268 | void DNSPacketWriter::xfrBlob(const string& blob, int ) |
|---|
| 269 | { |
|---|
| 270 | const uint8_t* ptr=reinterpret_cast<const uint8_t*>(blob.c_str()); |
|---|
| 271 | d_record.insert(d_record.end(), ptr, ptr+blob.size()); |
|---|
| 272 | } |
|---|
| 273 | |
|---|
| 274 | void DNSPacketWriter::xfrHexBlob(const string& blob, bool keepReading) |
|---|
| 275 | { |
|---|
| 276 | xfrBlob(blob); |
|---|
| 277 | } |
|---|
| 278 | |
|---|
| 279 | void DNSPacketWriter::getRecords(string& records) |
|---|
| 280 | { |
|---|
| 281 | records.assign(d_content.begin() + d_sor, d_content.end()); |
|---|
| 282 | } |
|---|
| 283 | |
|---|
| 284 | uint32_t DNSPacketWriter::size() |
|---|
| 285 | { |
|---|
| 286 | return d_content.size() + d_stuff + d_record.size(); |
|---|
| 287 | } |
|---|
| 288 | |
|---|
| 289 | void DNSPacketWriter::rollback() |
|---|
| 290 | { |
|---|
| 291 | d_content.resize(d_rollbackmarker); |
|---|
| 292 | d_record.clear(); |
|---|
| 293 | d_stuff=0; |
|---|
| 294 | } |
|---|
| 295 | |
|---|
| 296 | void DNSPacketWriter::commit() |
|---|
| 297 | { |
|---|
| 298 | if(d_stuff==0xffff && (d_content.size()!=d_sor || !d_record.empty())) |
|---|
| 299 | throw MOADNSException("DNSPacketWriter::commit() called without startRecord ever having been called, but a record was added"); |
|---|
| 300 | // build dnsrecordheader |
|---|
| 301 | struct dnsrecordheader drh; |
|---|
| 302 | drh.d_type=htons(d_recordqtype); |
|---|
| 303 | drh.d_class=htons(d_recordqclass); |
|---|
| 304 | drh.d_ttl=htonl(d_recordttl); |
|---|
| 305 | drh.d_clen=htons(d_record.size()); |
|---|
| 306 | |
|---|
| 307 | // and write out the header |
|---|
| 308 | const uint8_t* ptr=(const uint8_t*)&drh; |
|---|
| 309 | d_content.insert(d_content.end(), ptr, ptr+sizeof(drh)); |
|---|
| 310 | |
|---|
| 311 | d_stuff=0; |
|---|
| 312 | |
|---|
| 313 | // write out pending d_record |
|---|
| 314 | d_content.insert(d_content.end(), d_record.begin(), d_record.end()); |
|---|
| 315 | |
|---|
| 316 | dnsheader* dh=reinterpret_cast<dnsheader*>( &*d_content.begin()); |
|---|
| 317 | switch(d_recordplace) { |
|---|
| 318 | case ANSWER: |
|---|
| 319 | dh->ancount = htons(ntohs(dh->ancount) + 1); |
|---|
| 320 | break; |
|---|
| 321 | case AUTHORITY: |
|---|
| 322 | dh->nscount = htons(ntohs(dh->nscount) + 1); |
|---|
| 323 | break; |
|---|
| 324 | case ADDITIONAL: |
|---|
| 325 | dh->arcount = htons(ntohs(dh->arcount) + 1); |
|---|
| 326 | break; |
|---|
| 327 | } |
|---|
| 328 | |
|---|
| 329 | d_record.clear(); // clear d_record, ready for next record |
|---|
| 330 | } |
|---|
| 331 | |
|---|
| 332 | |
|---|
| 333 | |
|---|
| 334 | |
|---|
| 335 | |
|---|
| 336 | |
|---|