From 18f707fac12931bb9d86cc5537f5052d789ac1eb Mon Sep 17 00:00:00 2001 From: Remi Gacogne Date: Thu, 2 Nov 2017 16:12:36 +0100 Subject: [PATCH] Make the XPF code point configurable in dnsdist and the rec It's a bit trickier for sdig, though. --- pdns/dnsdist-lua.cc | 4 ++-- pdns/dnsdist-tcp.cc | 4 ++-- pdns/dnsdist.cc | 9 ++++----- pdns/dnsdist.hh | 4 ++-- pdns/dnsdistdist/docs/reference/config.rst | 6 +++--- pdns/dnsparser.cc | 7 +++++-- pdns/pdns_recursor.cc | 7 +++++-- pdns/recursordist/docs/settings.rst | 10 ++++++++++ regression-tests.dnsdist/test_XPF.py | 3 ++- 9 files changed, 35 insertions(+), 19 deletions(-) diff --git a/pdns/dnsdist-lua.cc b/pdns/dnsdist-lua.cc index 866ae4050..0be2c3e84 100644 --- a/pdns/dnsdist-lua.cc +++ b/pdns/dnsdist-lua.cc @@ -307,10 +307,10 @@ void setupLuaConfig(bool client) if(vars.count("ipBindAddrNoPort")) { ret->ipBindAddrNoPort=boost::get(vars["ipBindAddrNoPort"]); - } + } if(vars.count("addXPF")) { - ret->addXPF=boost::get(vars["addXPF"]); + ret->xpfOptionCode=std::stoi(boost::get(vars["addXPF"])); } if(vars.count("maxCheckFailures")) { diff --git a/pdns/dnsdist-tcp.cc b/pdns/dnsdist-tcp.cc index 54586a437..13be397e9 100644 --- a/pdns/dnsdist-tcp.cc +++ b/pdns/dnsdist-tcp.cc @@ -440,8 +440,8 @@ void* tcpClientThread(int pipefd) break; } - if (dq.addXPF && ds->addXPF) { - addXPF(dq); + if (dq.addXPF && ds->xpfOptionCode != 0) { + addXPF(dq, ds->xpfOptionCode); } int dsock = -1; diff --git a/pdns/dnsdist.cc b/pdns/dnsdist.cc index 293f4616c..74924d3ec 100644 --- a/pdns/dnsdist.cc +++ b/pdns/dnsdist.cc @@ -1101,12 +1101,12 @@ static ssize_t udpClientSendRequestToBackend(DownstreamState* ss, const int sd, return result; } -bool addXPF(DNSQuestion& dq) +bool addXPF(DNSQuestion& dq, uint16_t optionCode) { std::string payload = generateXPFPayload(dq.tcp, *dq.remote, *dq.local); uint8_t root = '\0'; dnsrecordheader drh; - drh.d_type = htons(QType::XPF); + drh.d_type = htons(optionCode); drh.d_class = htons(QClass::IN); drh.d_ttl = 0; drh.d_clen = htons(payload.size()); @@ -1389,8 +1389,8 @@ static void processUDPQuery(ClientState& cs, LocalHolders& holders, const struct return; } - if (dq.addXPF && ss->addXPF) { - addXPF(dq); + if (dq.addXPF && ss->xpfOptionCode != 0) { + addXPF(dq, ss->xpfOptionCode); } ss->queries++; @@ -1542,7 +1542,6 @@ static void MultipleMessagesUDPClientThread(ClientState* cs, LocalHolders& holde } #endif /* defined(HAVE_RECVMMSG) && defined(HAVE_SENDMMSG) && defined(MSG_WAITFORONE) */ - // listens to incoming queries, sends out to downstream servers, noting the intended return path static void* udpClientThread(ClientState* cs) try diff --git a/pdns/dnsdist.hh b/pdns/dnsdist.hh index 06973257b..6352f205f 100644 --- a/pdns/dnsdist.hh +++ b/pdns/dnsdist.hh @@ -624,6 +624,7 @@ struct DownstreamState int tcpSendTimeout{30}; unsigned int sourceItf{0}; uint16_t retries{5}; + uint16_t xpfOptionCode{0}; uint8_t currentCheckFailures{0}; uint8_t maxCheckFailures{1}; StopWatch sw; @@ -632,7 +633,6 @@ struct DownstreamState bool mustResolve{false}; bool upStatus{false}; bool useECS{false}; - bool addXPF{false}; bool setCD{false}; std::atomic connected{false}; bool tcpFastOpen{false}; @@ -869,7 +869,7 @@ int handleDnsCryptQuery(DnsCryptContext* ctx, char* packet, uint16_t len, std::s bool encryptResponse(char* response, uint16_t* responseLen, size_t responseSize, bool tcp, std::shared_ptr dnsCryptQuery, dnsheader** dh, dnsheader* dhCopy); #endif -bool addXPF(DNSQuestion& dq); +bool addXPF(DNSQuestion& dq, uint16_t optionCode); #include "dnsdist-snmp.hh" diff --git a/pdns/dnsdistdist/docs/reference/config.rst b/pdns/dnsdistdist/docs/reference/config.rst index b83f05b36..401437563 100644 --- a/pdns/dnsdistdist/docs/reference/config.rst +++ b/pdns/dnsdistdist/docs/reference/config.rst @@ -245,8 +245,6 @@ Servers newServer({ address="IP:PORT", -- IP and PORT of the backend server (mandatory) - addXPF=BOOL, -- Add the client's IP address and port to the query, along with the original destination address and port, - -- using the experimental XPF record from `draft-bellis-dnsop-xpf`. Default to false qps=NUM, -- Limit the number of queries per second to NUM, when using the `firstAvailable` policy order=NUM, -- The order of this server, used by the `leastOustanding` and `firstAvailable` policies weight=NUM, -- The weight of this server, used by the `wrandom` and `whashed` policies @@ -265,11 +263,13 @@ Servers maxCheckFailures=NUM, -- Allow NUM check failures before declaring the backend down, default: false mustResolve=BOOL, -- Set to true when the health check MUST return a NOERROR RCODE and an answer useClientSubnet=BOOL, -- Add the client's IP address in the EDNS Client Subnet option when forwarding the query to this backend - source=STRING -- The source address or interface to use for queries to this backend, by default this is left to the kernel's address selection + source=STRING, -- The source address or interface to use for queries to this backend, by default this is left to the kernel's address selection -- The following formats are supported: -- "address", e.g. "192.0.2.2" -- "interface name", e.g. "eth0" -- "address@interface", e.g. "192.0.2.2@eth0" + addXPF=NUM -- Add the client's IP address and port to the query, along with the original destination address and port, + -- using the experimental XPF record from `draft-bellis-dnsop-xpf` and the specified option code. Default is disabled (0) }) :param str server_string: A simple IP:PORT string. diff --git a/pdns/dnsparser.cc b/pdns/dnsparser.cc index bfe15ed93..ba44b61e8 100644 --- a/pdns/dnsparser.cc +++ b/pdns/dnsparser.cc @@ -296,13 +296,16 @@ void MOADNSParser::init(bool query, const char *packet, unsigned int len) d_answers.push_back(make_pair(dr, pr.d_pos)); - if (dr.d_place == DNSResourceRecord::ADDITIONAL && seenTSIG && dr.d_type != QType::XPF) { + /* XXX: XPF records should be allowed after TSIG as soon as the actual XPF option code has been assigned: + if (dr.d_place == DNSResourceRecord::ADDITIONAL && seenTSIG && dr.d_type != QType::XPF) + */ + if (dr.d_place == DNSResourceRecord::ADDITIONAL && seenTSIG) { /* only XPF records are allowed after a TSIG */ throw MOADNSException("Packet ("+d_qname.toString()+"|#"+std::to_string(d_qtype)+") has an unexpected record ("+std::to_string(dr.d_type)+") after a TSIG one."); } if(dr.d_type == QType::TSIG && dr.d_class == QClass::ANY) { - if(dr.d_place != DNSResourceRecord::ADDITIONAL) { + if(seenTSIG || dr.d_place != DNSResourceRecord::ADDITIONAL) { throw MOADNSException("Packet ("+d_qname.toLogString()+"|#"+std::to_string(d_qtype)+") has a TSIG record in an invalid position."); } seenTSIG = true; diff --git a/pdns/pdns_recursor.cc b/pdns/pdns_recursor.cc index ff3ec5ca5..a6f9f99db 100644 --- a/pdns/pdns_recursor.cc +++ b/pdns/pdns_recursor.cc @@ -148,6 +148,7 @@ static unsigned int g_maxMThreads; static unsigned int g_numWorkerThreads; static int g_tcpTimeout; static uint16_t g_udpTruncationThreshold; +static uint16_t g_xpfOptionCode{0}; static std::atomic statsWanted; static std::atomic g_quiet; static bool g_logCommonErrors; @@ -1384,7 +1385,7 @@ static void getQNameAndSubnet(const std::string& question, DNSName* dnsname, uin bool& foundECS, EDNSSubnetOpts* ednssubnet, std::map* options, bool& foundXPF, ComboAddress* xpfSource, ComboAddress* xpfDest) { - const bool lookForXPF = xpfSource != nullptr; + const bool lookForXPF = xpfSource != nullptr && g_xpfOptionCode != 0; const bool lookForECS = ednssubnet != nullptr; const struct dnsheader* dh = reinterpret_cast(question.c_str()); size_t questionLen = question.length(); @@ -1439,7 +1440,7 @@ static void getQNameAndSubnet(const std::string& question, DNSName* dnsname, uin } } } - else if (lookForXPF && ntohs(drh->d_type) == QType::XPF && ntohs(drh->d_class) == QClass::IN && drh->d_ttl == 0) { + else if (lookForXPF && ntohs(drh->d_type) == g_xpfOptionCode && ntohs(drh->d_class) == QClass::IN && drh->d_ttl == 0) { if ((questionLen - pos) < ntohs(drh->d_clen)) { return; } @@ -3064,6 +3065,7 @@ static int serviceMain(int argc, char*argv[]) g_useIncomingECS = ::arg().mustDo("use-incoming-edns-subnet"); g_XPFAcl.toMasks(::arg()["xpf-allow-from"]); + g_xpfOptionCode = ::arg().asNum("xpf-option-code"); g_networkTimeoutMsec = ::arg().asNum("network-timeout"); @@ -3498,6 +3500,7 @@ int main(int argc, char **argv) ::arg().setSwitch("log-rpz-changes", "Log additions and removals to RPZ zones at Info level")="no"; ::arg().set("xpf-allow-from","XPF information is only processed from these subnets")=""; + ::arg().set("xpf-option-code","XPF option code to use")="0"; ::arg().setCmd("help","Provide a helpful message"); ::arg().setCmd("version","Print version string"); diff --git a/pdns/recursordist/docs/settings.rst b/pdns/recursordist/docs/settings.rst index 13a1e6b44..67eb4d311 100644 --- a/pdns/recursordist/docs/settings.rst +++ b/pdns/recursordist/docs/settings.rst @@ -1176,3 +1176,13 @@ and will adjust queries' source and destination accordingly. This is especially is placed behind a proxy like dnsdist. Note that the `allow-from`_ setting is still applied to the original source address, and thus access restriction should be done on the proxy. + +``xpf-option-code`` +------------- +.. versionadded:: 4.1.0 + +- Integer +- Default: 0 + +This is an experimental implementation of `draft-bellis-dnsop-xpf`. +The option code to use for XPF records, as long as an official code has not been assigned to it. 0 means disabled. diff --git a/regression-tests.dnsdist/test_XPF.py b/regression-tests.dnsdist/test_XPF.py index 261c5172b..1ee865262 100644 --- a/regression-tests.dnsdist/test_XPF.py +++ b/regression-tests.dnsdist/test_XPF.py @@ -10,8 +10,9 @@ class XPFTest(DNSDistTest): _xpfCode = 65422 _config_template = """ - newServer{address="127.0.0.1:%s", addXPF=true} + newServer{address="127.0.0.1:%d", addXPF=%d} """ + _config_params = ['_testServerPort', '_xpfCode'] def checkMessageHasXPF(self, msg, expectedValue): self.assertGreaterEqual(len(msg.additional), 1) -- 2.40.0