From d03d16cbdfd92f433c0fea2be2dcb283974c6cdb Mon Sep 17 00:00:00 2001 From: bert hubert Date: Mon, 16 Apr 2018 00:14:18 +0200 Subject: [PATCH] dnsdist can now retrieve zones over AXFR, including the root. This shook out some compression bugs, plus an off by one on serving the root zone. With this commit, tdns can also parse DNS Messages (which it needs for AXFR). This also introduces some 'reflection' support that will eventually unify message generation/parting and zone file format input/output --- tdns/contents.cc | 4 +- tdns/dns-storage.cc | 31 +++++----- tdns/dns-storage.hh | 10 ++-- tdns/dnsmessages.cc | 136 ++++++++++++++++++++++++++++--------------- tdns/dnsmessages.hh | 73 ++++++++++++++++------- tdns/record-types.cc | 70 ++++++++++++++++------ tdns/record-types.hh | 36 ++++++++++++ tdns/tdns.cc | 100 ++++++++++++++++++++++++------- 8 files changed, 334 insertions(+), 126 deletions(-) diff --git a/tdns/contents.cc b/tdns/contents.cc index 051111c..d8dcd2a 100644 --- a/tdns/contents.cc +++ b/tdns/contents.cc @@ -4,7 +4,7 @@ void loadZones(DNSNode& zones) { auto zone = zones.add({"tdns", "powerdns", "org"}); - auto newzone = zone->zone = new DNSNode(); // XXX ICK + auto newzone = std::make_unique(); newzone->addRRs(SOAGen::make({"ns1", "tdns", "powerdns", "org"}, {"admin", "powerdns", "org"}, 1), NSGen::make({"ns1", "tdns", "powerdns", "org"}), @@ -53,4 +53,6 @@ void loadZones(DNSNode& zones) newzone->add({"some host"})->addRRs(AGen::make("192.0.0.2")); newzone->add({"some.host"})->addRRs(AGen::make("192.0.0.3")); + + zone->zone = std::move(newzone); } diff --git a/tdns/dns-storage.cc b/tdns/dns-storage.cc index 3f46735..632772b 100644 --- a/tdns/dns-storage.cc +++ b/tdns/dns-storage.cc @@ -27,11 +27,13 @@ DNSName operator+(const DNSName& a, const DNSName& b) return ret; } +DNSNode::~DNSNode() = default; + const DNSNode* DNSNode::find(DNSName& name, DNSName& last, bool wildcard, const DNSNode** passedZonecut, DNSName* zonecutname) const { - cout<<"find called for '"< 1) { last.push_front(name.back()); name.pop_back(); } } } - cout<<" Had match at this node , continuing to child '"<first<<"'"<first<<"'"<second.find(name, last, wildcard, passedZonecut, zonecutname); @@ -75,9 +78,7 @@ const DNSNode* DNSNode::find(DNSName& name, DNSName& last, bool wildcard, const DNSNode* DNSNode::add(DNSName name) { - if(name.size() == 1) { // this is our home node - return &children[name.front()]; - } + if(name.empty()) return this; auto back = name.back(); name.pop_back(); return children[back].add(name); // will make child node if needed diff --git a/tdns/dns-storage.hh b/tdns/dns-storage.hh index 30ed317..84ca084 100644 --- a/tdns/dns-storage.hh +++ b/tdns/dns-storage.hh @@ -53,12 +53,13 @@ SMARTENUMEND(RCode) enum class DNSType : uint16_t { - A = 1, NS = 2, CNAME = 5, SOA=6, PTR=12, MX=15, TXT=16, AAAA = 28, SRV=33, OPT=41, IXFR = 251, AXFR = 252, ANY = 255 + A = 1, NS = 2, CNAME = 5, SOA=6, PTR=12, MX=15, TXT=16, AAAA = 28, SRV=33, DS=43, RRSIG=46, + NSEC=47, OPT=41, IXFR = 251, AXFR = 252, ANY = 255 }; SMARTENUMSTART(DNSType) -SENUM13(DNSType, A, NS, CNAME, SOA, PTR, MX, TXT, AAAA, IXFR, AAAA, SRV, OPT, IXFR) -SENUM2(DNSType, AXFR, ANY) +SENUM13(DNSType, A, NS, CNAME, SOA, PTR, MX, TXT, AAAA, SRV,DS, RRSIG, NSEC, OPT) +SENUM3(DNSType, IXFR, AXFR, ANY) SMARTENUMEND(DNSType) enum class DNSClass : uint16_t @@ -114,6 +115,7 @@ struct RRSet struct DNSNode { + ~DNSNode(); const DNSNode* find(DNSName& name, DNSName& last, bool wildcards=false, const DNSNode** passedZonecut=0, DNSName* zonecutname=0) const; DNSNode* add(DNSName name); std::map children; @@ -129,7 +131,7 @@ struct DNSNode void visit(std::function visitor, DNSName name) const; std::map rrsets; - DNSNode* zone{0}; // if this is set, this node is a zone + std::unique_ptr zone; // if this is set, this node is a zone uint16_t namepos{0}; }; diff --git a/tdns/dnsmessages.cc b/tdns/dnsmessages.cc index 4779c48..8b499c6 100644 --- a/tdns/dnsmessages.cc +++ b/tdns/dnsmessages.cc @@ -11,15 +11,17 @@ DNSMessageReader::DNSMessageReader(const char* in, uint16_t size) payload.reserve(size-12); payload.insert(payload.begin(), (const unsigned char*)in + 12, (const unsigned char*)in + size); - d_qname = getName(); - d_qtype = (DNSType) getUInt16(); - d_qclass = (DNSClass) getUInt16(); + if(dh.qdcount) { // AXFR can skip this + xfrName(d_qname); + d_qtype = (DNSType) getUInt16(); + d_qclass = (DNSClass) getUInt16(); + } if(dh.arcount) { if(getUInt8() == 0 && getUInt16() == (uint16_t)DNSType::OPT) { - d_bufsize=getUInt16(); + xfrUInt16(d_bufsize); getUInt8(); // extended RCODE - d_ednsVersion = getUInt8(); - auto flags = getUInt8(); + d_ednsVersion = getUInt8(); + auto flags=getUInt8(); d_doBit = flags & 0x80; getUInt8(); getUInt16(); // ignore rest cout<<" There was an EDNS section, size supported: "<< d_bufsize< 63) - throw std::runtime_error("Got a compressed label"); + uint8_t labellen= getUInt8(pos); + if(labellen & 0xc0) { + uint16_t labellen2 = getUInt8(pos); + uint16_t newpos = ((labellen & ~0xc0) << 8) | labellen2; + newpos -= sizeof(dnsheader); // includes struct dnsheader + if(newpos < *pos) { + res=res+getName(&newpos); + return; + } + else { + throw std::runtime_error("forward compression: " + std::to_string(newpos) + " >= " + std::to_string(*pos)); + } + } if(!labellen) // end of DNSName break; - DNSLabel label = getBlob(labellen); - name.push_back(label); + DNSLabel label = getBlob(labellen, pos); + res.push_back(label); } - return name; } void DNSMessageReader::getQuestion(DNSName& name, DNSType& type) const @@ -57,36 +69,67 @@ bool DNSMessageReader::getEDNS(uint16_t* bufsize, bool* doBit) const return true; } +bool DNSMessageReader::getRR(DNSSection& section, DNSName& name, DNSType& type, uint32_t& ttl, std::unique_ptr& content) +{ + if(payloadpos == payload.size()) + return false; + name = getName(); + type=(DNSType)getUInt16(); + /* uint16_t lclass = */ getUInt16(); // class + xfrUInt32(ttl); + auto len = getUInt16(); + if(type == DNSType::NS) { + content = std::make_unique(*this); + } + else if(type == DNSType::SOA) { + content = std::make_unique(*this); + } + else if(type == DNSType::MX) { + content = std::make_unique(*this); + } + else if(type == DNSType::CNAME) { + content = std::make_unique(*this); + } + else if(type == DNSType::PTR) { + content = std::make_unique(*this); + } + else { + content = UnknownGen::make(type, getBlob(len)); + } + return true; +} + +// this is required to make the std::unique_ptr to DNSZone work. Long story. DNSMessageWriter::~DNSMessageWriter() = default; -void DNSMessageWriter::putName(const DNSName& name, bool compress) +void DNSMessageWriter::xfrName(const DNSName& name, bool compress) { DNSName oname(name); - cout<<"Attempt to emit "<find(fname, flast); if(node) { - cout<<" Did lookup for "<namepos<namepos< 1) { uint16_t pos = node->namepos; - cout<<" Using the pointer we found to pos "<>8) | 0xc0 ); - putUInt8(pos & 0xff); - if(!fname.empty()) { // worth it to save the full name for future reference - auto anode = d_comptree->add(oname); + auto anode = d_comptree->add(sname); if(!anode->namepos) { - cout<<"Storing that "<namepos = opayloadpos + 12; + // cout<<"Storing that "<namepos = payloadpos + 12; } + sname.pop_front(); + xfrUInt8(lab.size()); + xfrBlob(lab.d_s); } + xfrUInt8((pos>>8) | (uint8_t)0xc0 ); + xfrUInt8(pos & 0xff); return; } } @@ -96,15 +139,15 @@ void DNSMessageWriter::putName(const DNSName& name, bool compress) if(!d_nocompress) { // even with compress=false, we want to store this name, unless this is a nocompress message (AXFR) auto anode = d_comptree->add(oname); if(!anode->namepos) { - // cout<<"Storing that "<namepos = payloadpos + 12; } } oname.pop_front(); - putUInt8(l.size()); - putBlob(l.d_s); + xfrUInt8(l.size()); + xfrBlob(l.d_s); } - putUInt8(0); + xfrUInt8(0); } static void nboInc(uint16_t& counter) // network byte order inc @@ -116,12 +159,12 @@ void DNSMessageWriter::putRR(DNSSection section, const DNSName& name, DNSType ty { auto cursize = payloadpos; try { - putName(name); - putUInt16((int)type); putUInt16(1); - putUInt32(ttl); - auto pos = putUInt16(0); // placeholder + xfrName(name); + xfrUInt16((int)type); xfrUInt16(1); + xfrUInt32(ttl); + auto pos = xfrUInt16(0); // placeholder content->toMessage(*this); - putUInt16At(pos, payloadpos-pos-2); + xfrUInt16At(pos, payloadpos-pos-2); } catch(...) { payloadpos = cursize; @@ -148,9 +191,9 @@ void DNSMessageWriter::putEDNS(uint16_t bufsize, RCode ercode, bool doBit) { auto cursize = payloadpos; try { - putUInt8(0); putUInt16((uint16_t)DNSType::OPT); // 'root' name, our type - putUInt16(bufsize); putUInt8(((int)ercode)>>4); putUInt8(0); putUInt8(doBit ? 0x80 : 0); putUInt8(0); - putUInt16(0); + xfrUInt8(0); xfrUInt16((uint16_t)DNSType::OPT); // 'root' name, our type + xfrUInt16(bufsize); xfrUInt8(((int)ercode)>>4); xfrUInt8(0); xfrUInt8(doBit ? 0x80 : 0); xfrUInt8(0); + xfrUInt16(0); } catch(...) { // went beyond message size, roll it all back payloadpos = cursize; @@ -162,7 +205,7 @@ void DNSMessageWriter::putEDNS(uint16_t bufsize, RCode ercode, bool doBit) DNSMessageWriter::DNSMessageWriter(const DNSName& name, DNSType type, int maxsize) : d_qname(name), d_qtype(type) { memset(&dh, 0, sizeof(dh)); - payload.resize(maxsize); + payload.resize(maxsize - sizeof(dh)); clearRRs(); } @@ -171,9 +214,9 @@ void DNSMessageWriter::clearRRs() d_comptree = std::make_unique(); dh.qdcount = htons(1) ; dh.ancount = dh.arcount = dh.nscount = 0; payloadpos=0; - putName(d_qname, false); - putUInt16((uint16_t)d_qtype); - putUInt16(1); // class + xfrName(d_qname, false); + xfrUInt16((uint16_t)d_qtype); + xfrUInt16(1); // class } string DNSMessageWriter::serialize() @@ -184,11 +227,12 @@ string DNSMessageWriter::serialize() putEDNS(payload.size() + sizeof(dnsheader), d_ercode, d_doBit); } std::string ret((const char*)&dh, ((const char*)&dh) + sizeof(dnsheader)); - ret.append((const unsigned char*)&payload.at(0), (const unsigned char*)&payload.at(payloadpos)); + if(payloadpos) + ret.append((const unsigned char*)&payload.at(0), (const unsigned char*)&payload.at(payloadpos-1)+1); return ret; } catch(std::out_of_range& e) { - cout<<"Got truncated while adding EDNS! Truncating"< AGen::make(const ComboAddress& ca) { return std::make_unique(ntohl(ca.sin4.sin_addr.s_addr)); @@ -7,7 +12,7 @@ std::unique_ptr AGen::make(const ComboAddress& ca) void AGen::toMessage(DNSMessageWriter& dmw) { - dmw.putUInt32(d_ip); + dmw.xfrUInt32(d_ip); } std::unique_ptr AAAAGen::make(const ComboAddress& ca) @@ -23,39 +28,70 @@ std::unique_ptr AAAAGen::make(const ComboAddress& ca) void AAAAGen::toMessage(DNSMessageWriter& dmw) { - dmw.putBlob(d_ip, 16); + dmw.xfrBlob(d_ip, 16); +} + +SOAGen::SOAGen(DNSMessageReader& dmr) +{ + dmr.xfrName(d_mname); dmr.xfrName(d_rname); + dmr.xfrUInt32(d_serial); dmr.xfrUInt32(d_refresh); + dmr.xfrUInt32(d_retry); dmr.xfrUInt32(d_expire); + dmr.xfrUInt32(d_minimum); } void SOAGen::toMessage(DNSMessageWriter& dmw) { - dmw.putName(d_mname); dmw.putName(d_rname); - dmw.putUInt32(d_serial); dmw.putUInt32(d_refresh); - dmw.putUInt32(d_retry); dmw.putUInt32(d_expire); - dmw.putUInt32(d_minimum); + dmw.xfrName(d_mname); dmw.xfrName(d_rname); + dmw.xfrUInt32(d_serial); dmw.xfrUInt32(d_refresh); + dmw.xfrUInt32(d_retry); dmw.xfrUInt32(d_expire); + dmw.xfrUInt32(d_minimum); } -void CNAMEGen::toMessage(DNSMessageWriter& dmw) +CNAMEGen::CNAMEGen(DNSMessageReader& x) { - dmw.putName(d_name); + x.xfrName(d_name); +} +void CNAMEGen::toMessage(DNSMessageWriter& x) +{ + x.xfrName(d_name); } -void NSGen::toMessage(DNSMessageWriter& dmw) +PTRGen::PTRGen(DNSMessageReader& x) { - dmw.putName(d_name); + x.xfrName(d_name); +} +void PTRGen::toMessage(DNSMessageWriter& x) +{ + x.xfrName(d_name); } - -void MXGen::toMessage(DNSMessageWriter& dmw) +NSGen::NSGen(DNSMessageReader& x) { - dmw.putUInt16(d_prio); - dmw.putName(d_name); + x.xfrName(d_name); +} + +void NSGen::toMessage(DNSMessageWriter& x) +{ + x.xfrName(d_name); +} + +MXGen::MXGen(DNSMessageReader& x) +{ + x.xfrUInt16(d_prio); + x.xfrName(d_name); +} + +void MXGen::toMessage(DNSMessageWriter& x) +{ + x.xfrUInt16(d_prio); + x.xfrName(d_name); } void TXTGen::toMessage(DNSMessageWriter& dmw) { - // XXX should autosplit - dmw.putUInt8(d_txt.length()); - dmw.putBlob(d_txt); + // XXX should autosplit or throw + dmw.xfrUInt8(d_txt.length()); + dmw.xfrBlob(d_txt); } void ClockTXTGen::toMessage(DNSMessageWriter& dmw) diff --git a/tdns/record-types.hh b/tdns/record-types.hh index e68458d..acb515f 100644 --- a/tdns/record-types.hh +++ b/tdns/record-types.hh @@ -4,6 +4,20 @@ #include "dnsmessages.hh" #include "comboaddress.hh" +struct UnknownGen : RRGen +{ + UnknownGen(DNSType type, const std::string& rr) : d_type(type), d_rr(rr) {} + DNSType d_type; + std::string d_rr; + static std::unique_ptr make(DNSType type, const std::string& rr) + { + return std::make_unique(type, rr); + } + void toMessage(DNSMessageWriter& dpw) override; + DNSType getType() const override { return d_type; } +}; + + struct AGen : RRGen { AGen(uint32_t ip) : d_ip(ip) {} @@ -33,17 +47,22 @@ struct AAAAGen : RRGen unsigned char d_ip[16]; }; +class DNSMessageReader; + struct SOAGen : RRGen { SOAGen(const DNSName& mname, const DNSName& rname, uint32_t serial, uint32_t minimum=3600, uint32_t refresh=10800, uint32_t retry=3600, uint32_t expire=604800) : d_mname(mname), d_rname(rname), d_serial(serial), d_minimum(minimum), d_refresh(refresh), d_retry(retry), d_expire(expire) {} + SOAGen(DNSMessageReader& dmr); + template static std::unique_ptr make(const DNSName& mname, const DNSName& rname, Targs&& ... fargs) { return std::make_unique(mname, rname, std::forward(fargs)...); } + void toMessage(DNSMessageWriter& dpw) override; DNSType getType() const override { return DNSType::SOA; } DNSName d_mname, d_rname; @@ -53,6 +72,7 @@ struct SOAGen : RRGen struct CNAMEGen : RRGen { CNAMEGen(const DNSName& name) : d_name(name) {} + CNAMEGen(DNSMessageReader& dmr); static std::unique_ptr make(const DNSName& mname) { return std::make_unique(mname); @@ -62,9 +82,23 @@ struct CNAMEGen : RRGen DNSName d_name; }; +struct PTRGen : RRGen +{ + PTRGen(const DNSName& name) : d_name(name) {} + PTRGen(DNSMessageReader& dmr); + static std::unique_ptr make(const DNSName& mname) + { + return std::make_unique(mname); + } + void toMessage(DNSMessageWriter& dpw) override; + DNSType getType() const override { return DNSType::PTR; } + DNSName d_name; +}; + struct NSGen : RRGen { NSGen(const DNSName& name) : d_name(name) {} + NSGen(DNSMessageReader& dmr); static std::unique_ptr make(const DNSName& mname) { return std::make_unique(mname); @@ -78,6 +112,8 @@ struct NSGen : RRGen struct MXGen : RRGen { MXGen(uint16_t prio, const DNSName& name) : d_prio(prio), d_name(name) {} + MXGen(DNSMessageReader& dmr); + static std::unique_ptr make(uint16_t prio, const DNSName& name) { return std::make_unique(prio, name); diff --git a/tdns/tdns.cc b/tdns/tdns.cc index a7549bb..5b269dd 100644 --- a/tdns/tdns.cc +++ b/tdns/tdns.cc @@ -94,7 +94,7 @@ bool processQuestion(const DNSNode& zones, DNSMessageReader& dm, const ComboAddr cout<<"---\nFound best zone: "<zone; + auto bestzone = fnd->zone.get(); DNSName searchname(qname), lastnode, zonecutname; const DNSNode* passedZonecut=0; int CNAMELoopCount = 0; @@ -207,29 +207,37 @@ void udpThread(ComboAddress local, Socket* sock, const DNSNode* zones) } } -static void writeTCPResponse(int sock, DNSMessageWriter& response) +static void writeTCPMessage(int sock, DNSMessageWriter& response) { string ser="00"+response.serialize(); - cout<<"Sending a message of "< 512) { cerr<<"Remote "<::max()); - response.d_nocompress = true; + DNSMessageWriter response(name, type, 16384); + // response.d_nocompress = true; if(type == DNSType::AXFR || type == DNSType::IXFR) { if(dm.dh.opcode || dm.dh.qr) { cerr<<"Dropping non-query AXFR from "<zone || !name.empty() || !fnd->zone->rrsets.count(DNSType::SOA)) { cout<<" This was not a zone, or zone had no SOA"<zone; + auto node = fnd->zone.get(); // send SOA response.putRR(DNSSection::Answer, zone, DNSType::SOA, node->rrsets[DNSType::SOA].ttl, node->rrsets[DNSType::SOA].contents[0]); - writeTCPResponse(sock, response); + writeTCPMessage(sock, response); response.clearRRs(); // send all other records @@ -290,7 +298,7 @@ void tcpClientThread(ComboAddress local, ComboAddress remote, int s, const DNSNo response.putRR(DNSSection::Answer, nname, p.first, p.second.ttl, rr); } catch(std::out_of_range& e) { // exceeded packet size - writeTCPResponse(sock, response); + writeTCPMessage(sock, response); response.clearRRs(); goto retry; } @@ -298,18 +306,18 @@ void tcpClientThread(ComboAddress local, ComboAddress remote, int s, const DNSNo } }, zone); - writeTCPResponse(sock, response); + writeTCPMessage(sock, response); response.clearRRs(); // send SOA again response.putRR(DNSSection::Answer, zone, DNSType::SOA, node->rrsets[DNSType::SOA].ttl, node->rrsets[DNSType::SOA].contents[0]); - writeTCPResponse(sock, response); + writeTCPMessage(sock, response); return; } else { if(processQuestion(*zones, dm, local, remote, response)) { - writeTCPResponse(sock, response); + writeTCPMessage(sock, response); } else return; @@ -317,6 +325,49 @@ void tcpClientThread(ComboAddress local, ComboAddress remote, int s, const DNSNo } } +std::unique_ptr retrieveZone(const ComboAddress& remote, const DNSName& zone) +{ + cout<<"Attempting to retrieve zone "<(); + + int soaCount=0; + for(;;) { + uint16_t len = tcpGetLen(tcp); + string message = SRead(tcp, len); + + cout<<"Got "<(); + } + + DNSName rrname; + DNSType rrtype; + DNSSection rrsection; + uint32_t ttl; + std::unique_ptr rr; + while(dmr.getRR(rrsection, rrname, rrtype, ttl, rr)) { + if(!rrname.makeRelative(zone)) + continue; + if(rrtype == DNSType::SOA && ++soaCount==2) + goto done; + ret->add(rrname)->addRRs(std::move(rr)); + ret->add(rrname)->rrsets[rrtype].ttl = ttl; + } + } + done: + cout<<"Done"<zone=retrieveZone(ComboAddress("2001:500:2f::f", 53), {}); + zones.add({"hubertnet", "nl"})->zone=retrieveZone(ComboAddress("52.48.64.3", 53), {"hubertnet", "nl"}); + zones.add({"ds9a", "nl"})->zone=retrieveZone(ComboAddress("52.48.64.3", 53), {"ds9a", "nl"}); + */ thread udpServer(udpThread, local, &udplistener, &zones);