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.1.0-alpha1~255^2 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=refs%2Fpull%2F4988%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. --- diff --git a/docs/markdown/recursor/settings.md b/docs/markdown/recursor/settings.md index 1a039b4b7a..d99e3258eb 100644 --- a/docs/markdown/recursor/settings.md +++ b/docs/markdown/recursor/settings.md @@ -855,6 +855,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 42d9133750..7316173e4c 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 @@ -208,7 +209,7 @@ boost::optional RecursorLua4::DNSQuestion::getEDNSSubnet() const 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 1b0f304011..a2be2192b3 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}; @@ -701,7 +703,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; @@ -714,7 +727,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 */ @@ -757,6 +770,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; @@ -793,11 +812,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) @@ -1371,7 +1391,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; @@ -1386,11 +1405,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_data); + dc->d_tag = (*t_pdl)->gettag(conn->d_remote, dc->d_ednssubnet.source, dest, qname, qtype, &dc->d_policyTags, dc->d_data); } catch(std::exception& e) { if(g_logCommonErrors) @@ -1412,10 +1432,9 @@ 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; if (!luaconfsLocal->protobufTaggedOnly) { - 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) { @@ -1533,7 +1552,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; @@ -1553,11 +1574,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, data); + ctag=(*t_pdl)->gettag(fromaddr, ednssubnet.source, destaddr, qname, qtype, &policyTags, data); } catch(std::exception& e) { if(g_logCommonErrors) @@ -1577,7 +1599,7 @@ string* doProcessUDPQuestion(const std::string& question, const ComboAddress& fr #ifdef HAVE_PROTOBUF if(luaconfsLocal->protobufServer) { if (!luaconfsLocal->protobufTaggedOnly || !policyTags.empty()) { - 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 */ @@ -1589,7 +1611,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); } @@ -1652,11 +1674,13 @@ string* doProcessUDPQuestion(const std::string& question, const ComboAddress& fr dc->d_tcp=false; dc->d_policyTags = policyTags; dc->d_data = data; + 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 @@ -2761,6 +2785,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) { @@ -3103,6 +3128,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")="yes"; ::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 df1eb46a0e..bdd157d7a6 100644 --- a/pdns/syncres.cc +++ b/pdns/syncres.cc @@ -1116,7 +1116,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, @@ -736,7 +738,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; @@ -744,7 +746,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