From: Remi Gacogne Date: Thu, 9 Feb 2017 14:01:41 +0000 (+0100) Subject: rec: Add `use-incoming-edns-subnet` to process and pass along ECS X-Git-Tag: rec-4.0.5-rc2^2 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=refs%2Fpull%2F5355%2Fhead;p=thirdparty%2Fpdns.git rec: Add `use-incoming-edns-subnet` to process and pass along ECS If set, the recusor will process and pass along a received EDNS Client Subnet to authoritative servers. The ECS information will only be sent for netmasks and domains listed in `edns-subnet-whitelist`, and will be truncated if the received scope exceeds `ecs-ipv4-bits` for IPv4 or `ecs-ipv6-bits` for IPv6. An incoming ECS source prefix-length of 0 can also be used to request that no ECS value be sent to the authoritative servers, in accordance with RFC7871. (cherry picked from commit b40562da39e3be0dcf193163c386eef369dcc4af) --- diff --git a/docs/markdown/recursor/settings.md b/docs/markdown/recursor/settings.md index f7c2557cb3..e1c0e2de69 100644 --- a/docs/markdown/recursor/settings.md +++ b/docs/markdown/recursor/settings.md @@ -847,6 +847,15 @@ performance. Large responses however also have downsides in terms of reflection attacks. This setting limits the accepted size. Maximum value is 65535, but values above 4096 should probably not be attempted. +## `use-incoming-edns-subnet` +* Boolean +* Default: no + +Whether to process and pass along a received EDNS Client Subnet to authoritative +servers. The ECS information will only be sent for netmasks and domains listed +in `edns-subnet-whitelist`, and will be truncated if the received scope exceeds +`ecs-ipv4-bits` for IPv4 or `ecs-ipv6-bits` for IPv6. + ## `version` Print version of this binary. Useful for checking which version of the PowerDNS recursor is installed on a system. Available since version 3.1.5. diff --git a/pdns/ednssubnet.hh b/pdns/ednssubnet.hh index aee0641aed..0220bd0f52 100644 --- a/pdns/ednssubnet.hh +++ b/pdns/ednssubnet.hh @@ -28,6 +28,8 @@ extern NetmaskGroup g_ednssubnets; extern SuffixMatchNode g_ednsdomains; +extern bool g_useIncomingECS; + struct EDNSSubnetOpts { Netmask source; diff --git a/pdns/lua-recursor4.cc b/pdns/lua-recursor4.cc index 4a9cbde071..e0120edfa1 100644 --- a/pdns/lua-recursor4.cc +++ b/pdns/lua-recursor4.cc @@ -25,7 +25,8 @@ #include "dnsparser.hh" #include "syncres.hh" #include "namespaces.hh" -#include "rec_channel.hh" +#include "rec_channel.hh" +#include "ednsoptions.hh" #include "ednssubnet.hh" #include "filterpo.hh" #include @@ -182,7 +183,7 @@ boost::optional RecursorLua4::DNSQuestion::getEDNSSubnet() if(ednsOptions) { for(const auto& o : *ednsOptions) { - if(o.first==8) { + if(o.first==EDNSOptionCode::ECS) { EDNSSubnetOpts eso; if(getEDNSSubnetOptsFromString(o.second, &eso)) return eso.source; diff --git a/pdns/pdns_recursor.cc b/pdns/pdns_recursor.cc index 5f8d09673b..b3b0fe205c 100644 --- a/pdns/pdns_recursor.cc +++ b/pdns/pdns_recursor.cc @@ -197,8 +197,10 @@ struct DNSComboWriter { ComboAddress d_remote, d_local; #ifdef HAVE_PROTOBUF boost::uuids::uuid d_uuid; - Netmask d_ednssubnet; #endif + EDNSSubnetOpts d_ednssubnet; + bool d_ecsFound{false}; + bool d_ecsParsed{false}; bool d_tcp; int d_socket; int d_tag{0}; @@ -700,7 +702,18 @@ void startDoResolve(void *p) maxanswersize = min(edo.d_packetsize, g_udpTruncationThreshold); dc->d_ednsOpts = edo.d_options; haveEDNS=true; + + if (g_useIncomingECS && !dc->d_ecsParsed) { + for (const auto& o : edo.d_options) { + if (o.first == EDNSOptionCode::ECS) { + dc->d_ecsFound = getEDNSSubnetOptsFromString(o.second, &dc->d_ednssubnet); + break; + } + } + } } + /* perhaps there was no EDNS or no ECS but by now we looked */ + dc->d_ecsParsed = true; vector ret; vector packet; @@ -713,7 +726,7 @@ void startDoResolve(void *p) Netmask requestorNM(dc->d_remote, dc->d_remote.sin4.sin_family == AF_INET ? luaconfsLocal->protobufMaskV4 : luaconfsLocal->protobufMaskV6); const ComboAddress& requestor = requestorNM.getMaskedNetwork(); pbMessage.update(dc->d_uuid, &requestor, &dc->d_local, dc->d_tcp, dc->d_mdp.d_header.id); - pbMessage.setEDNSSubnet(dc->d_ednssubnet, dc->d_ednssubnet.isIpv4() ? luaconfsLocal->protobufMaskV4 : luaconfsLocal->protobufMaskV6); + pbMessage.setEDNSSubnet(dc->d_ednssubnet.source, dc->d_ednssubnet.source.isIpv4() ? luaconfsLocal->protobufMaskV4 : luaconfsLocal->protobufMaskV6); pbMessage.setQuestion(dc->d_mdp.d_qname, dc->d_mdp.d_qtype, dc->d_mdp.d_qclass); } #endif /* HAVE_PROTOBUF */ @@ -756,6 +769,12 @@ void startDoResolve(void *p) #ifdef HAVE_PROTOBUF sr.d_initialRequestId = dc->d_uuid; #endif + if (g_useIncomingECS) { + sr.d_incomingECSFound = dc->d_ecsFound; + if (dc->d_ecsFound) { + sr.d_incomingECS = dc->d_ednssubnet; + } + } bool tracedQuery=false; // we could consider letting Lua know about this too bool variableAnswer = false; @@ -781,11 +800,9 @@ void startDoResolve(void *p) if(!g_quiet || tracedQuery) { L<getTid()<<"/"<numProcesses()<<"] " << (dc->d_tcp ? "TCP " : "") << "question for '"<d_mdp.d_qname<<"|" <d_mdp.d_qtype)<<"' from "<getRemote(); -#ifdef HAVE_PROTOBUF - if(!dc->d_ednssubnet.empty()) { - L<<" (ecs "<d_ednssubnet.toString()<<")"; + if(!dc->d_ednssubnet.source.empty()) { + L<<" (ecs "<d_ednssubnet.source.toString()<<")"; } -#endif L< 4) { EDNSSubnetOpts eso; if(getEDNSSubnetOptsFromString(ecsStart + 4, ecsLen - 4, &eso)) { - *ednssubnet=eso.source; + *ednssubnet=eso; + found = true; } } } } + return found; } void handleRunningTCPQuestion(int fd, FDMultiplexer::funcparam_t& var) @@ -1329,7 +1349,6 @@ void handleRunningTCPQuestion(int fd, FDMultiplexer::funcparam_t& var) socklen_t len = dest.getSocklen(); getsockname(conn->getFD(), (sockaddr*)&dest, &len); // if this fails, we're ok with it dc->setLocal(dest); - Netmask ednssubnet; DNSName qname; uint16_t qtype=0; uint16_t qclass=0; @@ -1344,11 +1363,12 @@ void handleRunningTCPQuestion(int fd, FDMultiplexer::funcparam_t& var) if(needECS || (t_pdl->get() && (*t_pdl)->d_gettag)) { try { - getQNameAndSubnet(std::string(conn->data, conn->qlen), &qname, &qtype, &qclass, &ednssubnet); + dc->d_ecsParsed = true; + dc->d_ecsFound = getQNameAndSubnet(std::string(conn->data, conn->qlen), &qname, &qtype, &qclass, &dc->d_ednssubnet); if(t_pdl->get() && (*t_pdl)->d_gettag) { try { - dc->d_tag = (*t_pdl)->gettag(conn->d_remote, ednssubnet, dest, qname, qtype, &dc->d_policyTags); + dc->d_tag = (*t_pdl)->gettag(conn->d_remote, dc->d_ednssubnet.source, dest, qname, qtype, &dc->d_policyTags); } catch(std::exception& e) { if(g_logCommonErrors) @@ -1370,9 +1390,8 @@ void handleRunningTCPQuestion(int fd, FDMultiplexer::funcparam_t& var) if(luaconfsLocal->protobufServer) { try { const struct dnsheader* dh = (const struct dnsheader*) conn->data; - dc->d_ednssubnet = ednssubnet; - protobufLogQuery(luaconfsLocal->protobufServer, luaconfsLocal->protobufMaskV4, luaconfsLocal->protobufMaskV6, dc->d_uuid, conn->d_remote, dest, ednssubnet, true, dh->id, conn->qlen, qname, qtype, qclass, dc->d_policyTags); + protobufLogQuery(luaconfsLocal->protobufServer, luaconfsLocal->protobufMaskV4, luaconfsLocal->protobufMaskV6, dc->d_uuid, conn->d_remote, dest, dc->d_ednssubnet.source, true, dh->id, conn->qlen, qname, qtype, qclass, dc->d_policyTags); } catch(std::exception& e) { if(g_logCommonErrors) @@ -1488,7 +1507,9 @@ string* doProcessUDPQuestion(const std::string& question, const ComboAddress& fr uniqueId = (*t_uuidGenerator)(); } #endif - Netmask ednssubnet; + EDNSSubnetOpts ednssubnet; + bool ecsFound = false; + bool ecsParsed = false; try { DNSName qname; uint16_t qtype=0; @@ -1508,11 +1529,12 @@ string* doProcessUDPQuestion(const std::string& question, const ComboAddress& fr if(needECS || (t_pdl->get() && (*t_pdl)->d_gettag)) { try { - getQNameAndSubnet(question, &qname, &qtype, &qclass, &ednssubnet); + ecsParsed = true; + ecsFound = getQNameAndSubnet(question, &qname, &qtype, &qclass, &ednssubnet); if(t_pdl->get() && (*t_pdl)->d_gettag) { try { - ctag=(*t_pdl)->gettag(fromaddr, ednssubnet, destaddr, qname, qtype, &policyTags); + ctag=(*t_pdl)->gettag(fromaddr, ednssubnet.source, destaddr, qname, qtype, &policyTags); } catch(std::exception& e) { if(g_logCommonErrors) @@ -1531,7 +1553,7 @@ string* doProcessUDPQuestion(const std::string& question, const ComboAddress& fr RecProtoBufMessage pbMessage(DNSProtoBufMessage::DNSProtoBufMessageType::Response); #ifdef HAVE_PROTOBUF if(luaconfsLocal->protobufServer) { - protobufLogQuery(luaconfsLocal->protobufServer, luaconfsLocal->protobufMaskV4, luaconfsLocal->protobufMaskV6, uniqueId, fromaddr, destaddr, ednssubnet, false, dh->id, question.size(), qname, qtype, qclass, policyTags); + protobufLogQuery(luaconfsLocal->protobufServer, luaconfsLocal->protobufMaskV4, luaconfsLocal->protobufMaskV6, uniqueId, fromaddr, destaddr, ednssubnet.source, false, dh->id, question.size(), qname, qtype, qclass, policyTags); } #endif /* HAVE_PROTOBUF */ @@ -1542,7 +1564,7 @@ string* doProcessUDPQuestion(const std::string& question, const ComboAddress& fr Netmask requestorNM(fromaddr, fromaddr.sin4.sin_family == AF_INET ? luaconfsLocal->protobufMaskV4 : luaconfsLocal->protobufMaskV6); const ComboAddress& requestor = requestorNM.getMaskedNetwork(); pbMessage.update(uniqueId, &requestor, &destaddr, false, dh->id); - pbMessage.setEDNSSubnet(ednssubnet, ednssubnet.isIpv4() ? luaconfsLocal->protobufMaskV4 : luaconfsLocal->protobufMaskV6); + pbMessage.setEDNSSubnet(ednssubnet.source, ednssubnet.source.isIpv4() ? luaconfsLocal->protobufMaskV4 : luaconfsLocal->protobufMaskV6); pbMessage.setQueryTime(g_now.tv_sec, g_now.tv_usec); protobufLogResponse(luaconfsLocal->protobufServer, pbMessage); } @@ -1604,11 +1626,13 @@ string* doProcessUDPQuestion(const std::string& question, const ComboAddress& fr dc->setLocal(destaddr); dc->d_tcp=false; dc->d_policyTags = policyTags; + dc->d_ecsFound = ecsFound; + dc->d_ecsParsed = ecsParsed; + dc->d_ednssubnet = ednssubnet; #ifdef HAVE_PROTOBUF if (luaconfsLocal->protobufServer || luaconfsLocal->outgoingProtobufServer) { dc->d_uuid = uniqueId; } - dc->d_ednssubnet = ednssubnet; #endif MT->makeThread(startDoResolve, (void*) dc); // deletes dc @@ -2715,6 +2739,7 @@ int serviceMain(int argc, char*argv[]) makeTCPServerSockets(); parseEDNSSubnetWhitelist(::arg()["edns-subnet-whitelist"]); + g_useIncomingECS = ::arg().mustDo("use-incoming-edns-subnet"); int forks; for(forks = 0; forks < ::arg().asNum("processes") - 1; ++forks) { @@ -3055,6 +3080,7 @@ int main(int argc, char **argv) ::arg().set("ecs-ipv4-bits", "Number of bits of IPv4 address to pass for EDNS Client Subnet")="24"; ::arg().set("ecs-ipv6-bits", "Number of bits of IPv6 address to pass for EDNS Client Subnet")="56"; ::arg().set("edns-subnet-whitelist", "List of netmasks and domains that we should enable EDNS subnet for")=""; + ::arg().setSwitch( "use-incoming-edns-subnet", "Pass along received EDNS Client Subnet information")=""; ::arg().setSwitch( "pdns-distributes-queries", "If PowerDNS itself should distribute queries over threads")=""; ::arg().setSwitch( "root-nx-trust", "If set, believe that an NXDOMAIN from the root means the TLD does not exist")="yes"; ::arg().setSwitch( "any-to-tcp","Answer ANY queries with tc=1, shunting to TCP" )="no"; diff --git a/pdns/recursordist/ecs.cc b/pdns/recursordist/ecs.cc index 431daf4500..f70e732729 100644 --- a/pdns/recursordist/ecs.cc +++ b/pdns/recursordist/ecs.cc @@ -3,23 +3,41 @@ NetmaskGroup g_ednssubnets; SuffixMatchNode g_ednsdomains; +bool g_useIncomingECS; -boost::optional getEDNSSubnetMask(const ComboAddress& local, const DNSName&dn, const ComboAddress& rem) +boost::optional getEDNSSubnetMask(const ComboAddress& local, const DNSName&dn, const ComboAddress& rem, boost::optional incomingECS) { - static int l_ipv4limit, l_ipv6limit; + static uint8_t l_ipv4limit, l_ipv6limit; if(!l_ipv4limit) { l_ipv4limit = ::arg().asNum("ecs-ipv4-bits"); l_ipv6limit = ::arg().asNum("ecs-ipv6-bits"); } - if(local.sin4.sin_family != AF_INET || local.sin4.sin_addr.s_addr) { // detect unset 'requestor' - if(g_ednsdomains.check(dn) || g_ednssubnets.match(rem)) { - int bits = local.sin4.sin_family == AF_INET ? l_ipv4limit : l_ipv6limit; - ComboAddress trunc(local); - trunc.truncate(bits); - return boost::optional(Netmask(trunc, bits)); + boost::optional result; + ComboAddress trunc; + uint8_t bits; + if(incomingECS) { + if (incomingECS->source.getBits() == 0) { + /* RFC7871 says we MUST NOT send any ECS if the source scope is 0 */ + return result; } + trunc = incomingECS->source.getMaskedNetwork(); + bits = incomingECS->source.getBits(); } - return boost::optional(); + else if(!local.isIPv4() || local.sin4.sin_addr.s_addr) { // detect unset 'requestor' + trunc = local; + bits = local.isIPv4() ? 32 : 128; + } + else { + /* nothing usable */ + return result; + } + + if(g_ednsdomains.check(dn) || g_ednssubnets.match(rem)) { + bits = std::min(bits, (trunc.isIPv4() ? l_ipv4limit : l_ipv6limit)); + trunc.truncate(bits); + return boost::optional(Netmask(trunc, bits)); + } + return result; } void parseEDNSSubnetWhitelist(const std::string& wlist) diff --git a/pdns/syncres.cc b/pdns/syncres.cc index 7ec195aefa..2f44c25b7a 100644 --- a/pdns/syncres.cc +++ b/pdns/syncres.cc @@ -1131,7 +1131,7 @@ int SyncRes::doResolveAt(NsSet &nameservers, DNSName auth, bool flawedNSSet, con LOG(prefix<toString()<<" to query"< d_discardedPolicies; DNSFilterEngine::Policy d_appliedPolicy; + boost::optional d_incomingECS; #ifdef HAVE_PROTOBUF boost::optional d_initialRequestId; #endif @@ -389,6 +390,7 @@ public: bool d_wasOutOfBand{false}; bool d_wantsRPZ{true}; bool d_skipCNAMECheck{false}; + bool d_incomingECSFound{false}; typedef multi_index_container < NegCacheEntry, @@ -735,7 +737,7 @@ uint64_t* pleaseWipeCache(const DNSName& canon, bool subtree=false); uint64_t* pleaseWipePacketCache(const DNSName& canon, bool subtree); uint64_t* pleaseWipeAndCountNegCache(const DNSName& canon, bool subtree=false); void doCarbonDump(void*); -boost::optional getEDNSSubnetMask(const ComboAddress& local, const DNSName&dn, const ComboAddress& rem); +boost::optional getEDNSSubnetMask(const ComboAddress& local, const DNSName&dn, const ComboAddress& rem, boost::optional incomingECS); void parseEDNSSubnetWhitelist(const std::string& wlist); extern __thread struct timeval g_now; @@ -743,7 +745,6 @@ extern __thread struct timeval g_now; extern NetmaskGroup g_ednssubnets; extern SuffixMatchNode g_ednsdomains; - #ifdef HAVE_PROTOBUF extern __thread boost::uuids::random_generator* t_uuidGenerator; #endif