From 5c3b5e7f0a31c008a7cb1f5f97ff88e68b27fd4d Mon Sep 17 00:00:00 2001 From: Remi Gacogne Date: Thu, 10 Mar 2016 15:25:20 +0100 Subject: [PATCH] rec: Handle multiple EDNS0 Options in gettag Refactor the handling of EDNS0 Options parsing to use the same code in dnsdist and the recursor (packet cache and gettag). Closes #3516. --- pdns/Makefile.am | 1 + pdns/dnsdist-ecs.cc | 70 ++++---------------------------- pdns/dnsdist-ecs.hh | 4 -- pdns/dnsdist.hh | 5 --- pdns/dnsdistdist/Makefile.am | 2 + pdns/dnsdistdist/ednsoptions.cc | 1 + pdns/dnsdistdist/ednsoptions.hh | 1 + pdns/dnsrulactions.hh | 1 + pdns/ednsoptions.cc | 56 +++++++++++++++++++++++++ pdns/ednsoptions.hh | 36 ++++++++++++++++ pdns/pdns_recursor.cc | 26 ++++++++---- pdns/recpacketcache.cc | 32 ++++----------- pdns/recursordist/Makefile.am | 1 + pdns/recursordist/ednsoptions.cc | 1 + pdns/recursordist/ednsoptions.hh | 1 + pdns/test-dnsdist_cc.cc | 7 ++-- 16 files changed, 138 insertions(+), 107 deletions(-) create mode 120000 pdns/dnsdistdist/ednsoptions.cc create mode 120000 pdns/dnsdistdist/ednsoptions.hh create mode 100644 pdns/ednsoptions.cc create mode 100644 pdns/ednsoptions.hh create mode 120000 pdns/recursordist/ednsoptions.cc create mode 120000 pdns/recursordist/ednsoptions.hh diff --git a/pdns/Makefile.am b/pdns/Makefile.am index 20c363865..37a2cd92d 100644 --- a/pdns/Makefile.am +++ b/pdns/Makefile.am @@ -1021,6 +1021,7 @@ testrunner_SOURCES = \ dnsrecords.cc \ dnssecinfra.cc \ dnswriter.cc \ + ednsoptions.cc \ ednssubnet.cc \ gss_context.cc gss_context.hh \ iputils.cc \ diff --git a/pdns/dnsdist-ecs.cc b/pdns/dnsdist-ecs.cc index dc29efd48..cf9fdaaf9 100644 --- a/pdns/dnsdist-ecs.cc +++ b/pdns/dnsdist-ecs.cc @@ -4,6 +4,7 @@ #include "dnsdist-ecs.hh" #include "dnsparser.hh" #include "dnswriter.hh" +#include "ednsoptions.hh" #include "ednssubnet.hh" /* when we add EDNS to a query, we don't want to advertise @@ -174,10 +175,10 @@ int locateEDNSOptRR(const char * packet, const size_t len, const char ** optStar } /* extract the start of the OPT RR in a QUERY packet if any */ -static int getEDNSOptionsStart(char* packet, const size_t offset, const size_t len, char ** optStart, size_t * remaining, unsigned char ** optRDLen) +static int getEDNSOptionsStart(char* packet, const size_t offset, const size_t len, char ** optRDLen, size_t * remaining) { assert(packet != NULL); - assert(optStart != NULL); + assert(optRDLen != NULL); assert(remaining != NULL); const struct dnsheader* dh = (const struct dnsheader*) packet; @@ -201,72 +202,20 @@ static int getEDNSOptionsStart(char* packet, const size_t offset, const size_t l if(qtype != QType::OPT || (len - pos) < (DNS_TTL_SIZE + DNS_RDLENGTH_SIZE)) return ENOENT; - *optStart = packet + pos; + pos += DNS_TTL_SIZE; + *optRDLen = packet + pos; *remaining = len - pos; - if (optRDLen) { - *optRDLen = ((unsigned char*) packet + pos + DNS_TTL_SIZE); - } - return 0; } -/* extract a specific EDNS0 option from a pointer on the beginning of the OPT RR */ -static int getEDNSOption(char* optRR, const size_t len, const uint16_t wantedOption, char ** optionValue, size_t * optionValueSize) -{ - assert(optRR != NULL); - assert(optionValue != NULL); - assert(optionValueSize != NULL); - size_t pos = 0; - - pos += DNS_TTL_SIZE; - const uint16_t rdLen = (((unsigned char) optRR[pos]) * 256) + ((unsigned char) optRR[pos+1]); - size_t rdPos = 0; - pos += DNS_RDLENGTH_SIZE; - - while(pos < (len - ((size_t) EDNS_OPTION_CODE_SIZE + EDNS_OPTION_LENGTH_SIZE)) && - rdPos < (rdLen - ((size_t) EDNS_OPTION_CODE_SIZE + EDNS_OPTION_LENGTH_SIZE))) { - const uint16_t optionCode = (((unsigned char) optRR[pos]) * 256) + ((unsigned char) optRR[pos+1]); - pos += EDNS_OPTION_CODE_SIZE; - rdPos += EDNS_OPTION_CODE_SIZE; - const uint16_t optionLen = (((unsigned char) optRR[pos]) * 256) + ((unsigned char) optRR[pos+1]); - pos += EDNS_OPTION_LENGTH_SIZE; - rdPos += EDNS_OPTION_LENGTH_SIZE; - - if (optionLen > (rdLen - rdPos) || optionLen > (len - pos)) - return EINVAL; - - if (optionCode == wantedOption) { - *optionValue = optRR + pos - (EDNS_OPTION_CODE_SIZE + EDNS_OPTION_LENGTH_SIZE); - *optionValueSize = optionLen + EDNS_OPTION_CODE_SIZE + EDNS_OPTION_LENGTH_SIZE; - return 0; - } - else { - /* skip this option */ - pos += optionLen; - rdPos += optionLen; - } - } - - return ENOENT; -} - -void generateEDNSOption(uint16_t optionCode, const std::string& payload, std::string& res) -{ - const uint16_t ecsOptionCode = htons(optionCode); - const uint16_t payloadLen = htons(payload.length()); - res.append((const char *) &ecsOptionCode, sizeof ecsOptionCode); - res.append((const char *) &payloadLen, sizeof payloadLen); - res.append(payload); -} - static void generateECSOption(const ComboAddress& source, string& res) { Netmask sourceNetmask(source, source.sin4.sin_family == AF_INET ? g_ECSSourcePrefixV4 : g_ECSSourcePrefixV6); EDNSSubnetOpts ecsOpts; ecsOpts.source = sourceNetmask; string payload = makeEDNSSubnetOptsString(ecsOpts); - generateEDNSOption(EDNS0_OPTION_CODE_ECS, payload, res); + generateEDNSOption(EDNSOptionCode::ECS, payload, res); } void generateOptRR(const std::string& optRData, string& res) @@ -343,20 +292,19 @@ void handleEDNSClientSubnet(char * const packet, const size_t packetSize, const assert(len != NULL); assert(consumed <= (size_t) *len); assert(ednsAdded != NULL); - char * optRRStart = NULL; unsigned char * optRDLen = NULL; size_t remaining = 0; - int res = getEDNSOptionsStart(packet, consumed, *len, &optRRStart, &remaining, &optRDLen); + int res = getEDNSOptionsStart(packet, consumed, *len, (char**) &optRDLen, &remaining); if (res == 0) { char * ecsOptionStart = NULL; size_t ecsOptionSize = 0; - res = getEDNSOption(optRRStart, remaining, EDNS0_OPTION_CODE_ECS, &ecsOptionStart, &ecsOptionSize); + res = getEDNSOption((char*)optRDLen, remaining, EDNSOptionCode::ECS, &ecsOptionStart, &ecsOptionSize); if (res == 0) { - /* there is already an EDNS0_OPTION_CODE_ECS value */ + /* there is already an ECS value */ if (g_ECSOverride) { replaceEDNSClientSubnetOption(packet, packetSize, len, largerPacket, remote, ecsOptionStart, ecsOptionSize, optRDLen); } diff --git a/pdns/dnsdist-ecs.hh b/pdns/dnsdist-ecs.hh index eb782968a..c28985c4f 100644 --- a/pdns/dnsdist-ecs.hh +++ b/pdns/dnsdist-ecs.hh @@ -4,7 +4,3 @@ int rewriteResponseWithoutEDNS(const char * packet, size_t len, vector& int locateEDNSOptRR(const char * packet, size_t len, const char ** optStart, size_t * optLen, bool * last); void handleEDNSClientSubnet(char * packet, size_t packetSize, unsigned int consumed, uint16_t * len, string& largerPacket, bool * ednsAdded, const ComboAddress& remote); void generateOptRR(const std::string& optRData, string& res); -void generateEDNSOption(uint16_t optionCode, const std::string& payload, std::string& res); - - - diff --git a/pdns/dnsdist.hh b/pdns/dnsdist.hh index 2a4e8b4b7..ff910ffa6 100644 --- a/pdns/dnsdist.hh +++ b/pdns/dnsdist.hh @@ -443,11 +443,6 @@ enum ednsHeaderFlags { EDNS_HEADER_FLAG_DO = 32768 }; -enum ednsOptionCodes { - EDNS0_OPTION_CODE_NONE = 0, - EDNS0_OPTION_CODE_ECS = 8, -}; - extern GlobalStateHolder g_carbon; extern GlobalStateHolder g_policy; extern GlobalStateHolder g_dstates; diff --git a/pdns/dnsdistdist/Makefile.am b/pdns/dnsdistdist/Makefile.am index 5ba6f6ccd..2b13b44ac 100644 --- a/pdns/dnsdistdist/Makefile.am +++ b/pdns/dnsdistdist/Makefile.am @@ -65,6 +65,7 @@ dnsdist_SOURCES = \ dnsrulactions.hh \ dnswriter.cc dnswriter.hh \ dolog.hh \ + ednsoptions.cc ednsoptions.hh \ ednssubnet.cc ednssubnet.hh \ iputils.cc iputils.hh \ lock.hh \ @@ -115,6 +116,7 @@ testrunner_SOURCES = \ dnsparser.hh dnsparser.cc \ dnswriter.cc dnswriter.hh \ dolog.hh \ + ednsoptions.cc ednsoptions.hh \ ednssubnet.cc ednssubnet.hh \ iputils.cc iputils.hh \ misc.cc misc.hh \ diff --git a/pdns/dnsdistdist/ednsoptions.cc b/pdns/dnsdistdist/ednsoptions.cc new file mode 120000 index 000000000..c182e6f41 --- /dev/null +++ b/pdns/dnsdistdist/ednsoptions.cc @@ -0,0 +1 @@ +../ednsoptions.cc \ No newline at end of file diff --git a/pdns/dnsdistdist/ednsoptions.hh b/pdns/dnsdistdist/ednsoptions.hh new file mode 120000 index 000000000..e11521c41 --- /dev/null +++ b/pdns/dnsdistdist/ednsoptions.hh @@ -0,0 +1 @@ +../ednsoptions.hh \ No newline at end of file diff --git a/pdns/dnsrulactions.hh b/pdns/dnsrulactions.hh index 8ebb09dc6..3a0b857fa 100644 --- a/pdns/dnsrulactions.hh +++ b/pdns/dnsrulactions.hh @@ -2,6 +2,7 @@ #include "dnsdist-ecs.hh" #include "dnsname.hh" #include "dolog.hh" +#include "ednsoptions.hh" class MaxQPSIPRule : public DNSRule { diff --git a/pdns/ednsoptions.cc b/pdns/ednsoptions.cc new file mode 100644 index 000000000..89eaf2900 --- /dev/null +++ b/pdns/ednsoptions.cc @@ -0,0 +1,56 @@ + +#include "dns.hh" +#include "ednsoptions.hh" +#include "iputils.hh" + +/* extract a specific EDNS0 option from a pointer on the beginning rdLen of the OPT RR */ +int getEDNSOption(char* optRR, const size_t len, uint16_t wantedOption, char ** optionValue, size_t * optionValueSize) +{ + assert(optRR != NULL); + assert(optionValue != NULL); + assert(optionValueSize != NULL); + size_t pos = 0; + if (len < DNS_RDLENGTH_SIZE) + return EINVAL; + + const uint16_t rdLen = (((unsigned char) optRR[pos]) * 256) + ((unsigned char) optRR[pos+1]); + size_t rdPos = 0; + pos += DNS_RDLENGTH_SIZE; + if ((pos + rdLen) > len) { + return EINVAL; + } + + while(len >= (pos + EDNS_OPTION_CODE_SIZE + EDNS_OPTION_LENGTH_SIZE) && + rdLen >= (rdPos + EDNS_OPTION_CODE_SIZE + EDNS_OPTION_LENGTH_SIZE)) { + const uint16_t optionCode = (((unsigned char) optRR[pos]) * 256) + ((unsigned char) optRR[pos+1]); + pos += EDNS_OPTION_CODE_SIZE; + rdPos += EDNS_OPTION_CODE_SIZE; + const uint16_t optionLen = (((unsigned char) optRR[pos]) * 256) + ((unsigned char) optRR[pos+1]); + pos += EDNS_OPTION_LENGTH_SIZE; + rdPos += EDNS_OPTION_LENGTH_SIZE; + if (optionLen > (rdLen - rdPos) || optionLen > (len - pos)) + return EINVAL; + + if (optionCode == wantedOption) { + *optionValue = optRR + pos - (EDNS_OPTION_CODE_SIZE + EDNS_OPTION_LENGTH_SIZE); + *optionValueSize = optionLen + EDNS_OPTION_CODE_SIZE + EDNS_OPTION_LENGTH_SIZE; + return 0; + } + else { + /* skip this option */ + pos += optionLen; + rdPos += optionLen; + } + } + + return ENOENT; +} + +void generateEDNSOption(uint16_t optionCode, const std::string& payload, std::string& res) +{ + const uint16_t ednsOptionCode = htons(optionCode); + const uint16_t payloadLen = htons(payload.length()); + res.append((const char *) &ednsOptionCode, sizeof ednsOptionCode); + res.append((const char *) &payloadLen, sizeof payloadLen); + res.append(payload); +} diff --git a/pdns/ednsoptions.hh b/pdns/ednsoptions.hh new file mode 100644 index 000000000..7bd278f7f --- /dev/null +++ b/pdns/ednsoptions.hh @@ -0,0 +1,36 @@ +/* + PowerDNS Versatile Database Driven Nameserver + Copyright (C) 2011 - 2016 Netherlabs Computer Consulting BV + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License version 2 as + published by the Free Software Foundation + + Additionally, the license of this program contains a special + exception which allows to distribute the program in binary form when + it is linked against OpenSSL. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ +#ifndef PDNS_EDNSOPTIONS_HH +#define PDNS_EDNSOPTIONS_HH + +#include "namespaces.hh" + +struct EDNSOptionCode +{ + enum EDNSOptionCodeEnum {NSID=3, DAU=4, DHU=6, N3U=7, ECS=8, EXPIRE=9, COOKIE=10, TCPKEEPALIVE=11, PADDING=12, CHAIN=13}; +}; + +/* extract a specific EDNS0 option from a pointer on the beginning rdLen of the OPT RR */ +int getEDNSOption(char* optRR, size_t len, uint16_t wantedOption, char ** optionValue, size_t * optionValueSize); +void generateEDNSOption(uint16_t optionCode, const std::string& payload, std::string& res); + +#endif diff --git a/pdns/pdns_recursor.cc b/pdns/pdns_recursor.cc index ccc45a169..fd6e453ed 100644 --- a/pdns/pdns_recursor.cc +++ b/pdns/pdns_recursor.cc @@ -80,6 +80,7 @@ extern SortList g_sortlist; #include "rpzloader.hh" #include "validate-recursor.hh" #include "rec-lua-conf.hh" +#include "ednsoptions.hh" #ifndef RECURSOR #include "statbag.hh" @@ -1156,16 +1157,23 @@ string* doProcessUDPQuestion(const std::string& question, const ComboAddress& fr unsigned int consumed=0; uint16_t qtype=0; try { - DNSName qname(question.c_str(), question.length(), sizeof(dnsheader), false, &qtype, 0, &consumed); + size_t questionLen = question.length(); + DNSName qname(question.c_str(), questionLen, sizeof(dnsheader), false, &qtype, 0, &consumed); Netmask ednssubnet; - auto pos= sizeof(dnsheader)+consumed+4; - if(ntohs(dh->arcount) == 1 && question.length() > pos + 16) { // this code can extract one (1) EDNS Subnet option - uint16_t optlen=0x100*question.at(pos+9)+question.at(pos+10); - uint16_t optcode=0x100*question.at(pos+11)+question.at(pos+12); - if(question.at(pos)==0 && question.at(pos+1)==0 && question.at(pos+2)==QType::OPT && optlen && optcode==8) { - EDNSSubnetOpts eso; - if(getEDNSSubnetOptsFromString(question.c_str()+pos+15, question.length()-15-pos, &eso)) { - ednssubnet=eso.source; + size_t pos= sizeof(dnsheader)+consumed+4; + /* at least OPT root label (1), type (2), class (2) and ttl (4) + OPT RR rdlen (2) + = 11 */ + if(ntohs(dh->arcount) == 1 && questionLen > pos + 11) { // this code can extract one (1) EDNS Subnet option + /* OPT root label (1) followed by type (2) */ + if(question.at(pos)==0 && question.at(pos+1)==0 && question.at(pos+2)==QType::OPT) { + char* ecsStart = nullptr; + size_t ecsLen = 0; + int res = getEDNSOption((char*)question.c_str()+pos+9, questionLen - pos - 9, EDNSOptionCode::ECS, &ecsStart, &ecsLen); + if (res == 0 && ecsLen > 4) { + EDNSSubnetOpts eso; + if(getEDNSSubnetOptsFromString(ecsStart + 4, ecsLen - 4, &eso)) { + ednssubnet=eso.source; + } } } } diff --git a/pdns/recpacketcache.cc b/pdns/recpacketcache.cc index 2e91c431b..873e36962 100644 --- a/pdns/recpacketcache.cc +++ b/pdns/recpacketcache.cc @@ -10,6 +10,7 @@ #include "namespaces.hh" #include "lock.hh" #include "dnswriter.hh" +#include "ednsoptions.hh" RecursorPacketCache::RecursorPacketCache() { @@ -73,33 +74,14 @@ uint32_t RecursorPacketCache::canHashPacket(const std::string& origPacket) = 16 */ if(ntohs(dh->arcount)==1 && (p+16) < end) { + char* optionBegin = nullptr; + size_t optionLen = 0; /* skip the final empty label (1), the qtype (2), qclass (2) */ /* root label (1), type (2), class (2) and ttl (4) */ - const unsigned char *q = (const unsigned char*) p + 14; - uint16_t optRRLen = (0x100*q[0] + q[1]); - q += 2; - if ((q + optRRLen) <= (const unsigned char*) end) { - const unsigned char* optRRend = q + optRRLen; - while((q + 4) <= optRRend) { - const unsigned char* optionBegin = q; - uint16_t optionCode = 0x100*q[0] + q[1]; - //cout << "OPT RR Option Code is " << optionCode << endl; - q += 2; - uint16_t optionLen = 0x100*q[0] + q[1]; - //cout << "OPT RR Option Length is " << optionLen << endl; - q += 2; - if ((q + optionLen) > (const unsigned char*) end) { - break; - } - if (optionCode == 8) { - //cout << "Skipping OPT RR Option Client Subnet:" << endl; - //cout << makeHexDump(string((const char*)optionBegin, (const char*) q + optionLen)) << endl; - skipBegin = (const char*) optionBegin; - skipEnd = (const char*) q + optionLen; - break; - } - q += optionLen; - } + int res = getEDNSOption((char*) p + 14, end - (p + 14), EDNSOptionCode::ECS, &optionBegin, &optionLen); + if (res == 0) { + skipBegin = optionBegin; + skipEnd = optionBegin + optionLen; } } if (skipBegin > p) { diff --git a/pdns/recursordist/Makefile.am b/pdns/recursordist/Makefile.am index 929895726..7af1cf179 100644 --- a/pdns/recursordist/Makefile.am +++ b/pdns/recursordist/Makefile.am @@ -72,6 +72,7 @@ pdns_recursor_SOURCES = \ dnssecinfra.hh dnssecinfra.cc \ dnsseckeeper.hh \ dnswriter.cc dnswriter.hh \ + ednsoptions.cc ednsoptions.hh \ ednssubnet.cc ednssubnet.hh \ filterpo.cc filterpo.hh \ gss_context.cc gss_context.hh \ diff --git a/pdns/recursordist/ednsoptions.cc b/pdns/recursordist/ednsoptions.cc new file mode 120000 index 000000000..c182e6f41 --- /dev/null +++ b/pdns/recursordist/ednsoptions.cc @@ -0,0 +1 @@ +../ednsoptions.cc \ No newline at end of file diff --git a/pdns/recursordist/ednsoptions.hh b/pdns/recursordist/ednsoptions.hh new file mode 120000 index 000000000..e11521c41 --- /dev/null +++ b/pdns/recursordist/ednsoptions.hh @@ -0,0 +1 @@ +../ednsoptions.hh \ No newline at end of file diff --git a/pdns/test-dnsdist_cc.cc b/pdns/test-dnsdist_cc.cc index 7331678cd..d2bba08e8 100644 --- a/pdns/test-dnsdist_cc.cc +++ b/pdns/test-dnsdist_cc.cc @@ -32,6 +32,7 @@ #include "dnsname.hh" #include "dnsparser.hh" #include "dnswriter.hh" +#include "ednsoptions.hh" #include "ednssubnet.hh" #include @@ -164,7 +165,7 @@ BOOST_AUTO_TEST_CASE(replaceECSWithSameSize) { ecsOpts.source = Netmask(origRemote, g_ECSSourcePrefixV4); string origECSOption = makeEDNSSubnetOptsString(ecsOpts); DNSPacketWriter::optvect_t opts; - opts.push_back(make_pair(EDNS0_OPTION_CODE_ECS, origECSOption)); + opts.push_back(make_pair(EDNSOptionCode::ECS, origECSOption)); pw.addOpt(512, 0, 0, opts); pw.commit(); uint16_t len = query.size(); @@ -201,7 +202,7 @@ BOOST_AUTO_TEST_CASE(replaceECSWithSmaller) { ecsOpts.source = Netmask(origRemote, 32); string origECSOption = makeEDNSSubnetOptsString(ecsOpts); DNSPacketWriter::optvect_t opts; - opts.push_back(make_pair(EDNS0_OPTION_CODE_ECS, origECSOption)); + opts.push_back(make_pair(EDNSOptionCode::ECS, origECSOption)); pw.addOpt(512, 0, 0, opts); pw.commit(); uint16_t len = query.size(); @@ -238,7 +239,7 @@ BOOST_AUTO_TEST_CASE(replaceECSWithLarger) { ecsOpts.source = Netmask(origRemote, 8); string origECSOption = makeEDNSSubnetOptsString(ecsOpts); DNSPacketWriter::optvect_t opts; - opts.push_back(make_pair(EDNS0_OPTION_CODE_ECS, origECSOption)); + opts.push_back(make_pair(EDNSOptionCode::ECS, origECSOption)); pw.addOpt(512, 0, 0, opts); pw.commit(); uint16_t len = query.size(); -- 2.40.0