From: Remi Gacogne Date: Sun, 17 Jan 2016 15:15:18 +0000 (+0100) Subject: dnsdist: Add TCPRule. Make addAnyTCRule set TC=1 over UDP, not TCP. X-Git-Tag: dnsdist-1.0.0-alpha2~62^2 X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=490a29bbb2738b43733ca06a8a3663baf4b94ae9;p=pdns dnsdist: Add TCPRule. Make addAnyTCRule set TC=1 over UDP, not TCP. --- diff --git a/pdns/README-dnsdist.md b/pdns/README-dnsdist.md index 8a7316bda..40718e13c 100644 --- a/pdns/README-dnsdist.md +++ b/pdns/README-dnsdist.md @@ -301,6 +301,7 @@ Rules have selectors and actions. Current selectors are: * QType (QTypeRule) * RegexRule on query name * Packet requests DNSSEC processing + * Query received over UDP or TCP A special rule is `AndRule{rule1, rule2}`, which only matches if all of its subrules match. @@ -336,6 +337,7 @@ A DNS rule can be: * a QTypeRule * a RegexRule * a SuffixMatchNodeRule + * a TCPRule A convenience function `makeRule()` is supplied which will make a NetmaskGroupRule for you or a SuffixMatchNodeRule depending on how you call it. `makeRule("0.0.0.0/0")` will for example match all IPv4 traffic, `makeRule{"be","nl","lu"}` will @@ -354,7 +356,8 @@ the exact definition of `blockFilter()` is at the end of this document. ANY or whatever to TC --------------------- -The `blockFilter()` also gets passed read/writable copy of the DNS Header. +The `blockFilter()` also gets passed read/writable copy of the DNS Header, +via `dq.dh`. If you invoke setQR(1) on that, `dnsdist` knows you turned the packet into a response, and will send the answer directly to the original client. @@ -815,6 +818,7 @@ instantiate a server with additional parameters * `QTypeRule(qtype)`: matches queries with the specified qtype * `RegexRule(regex)`: matches the query name against the supplied regex * `SuffixMatchNodeRule()`: matches based on a group of domain suffixes for rapid testing of membership + * `TCPRule(tcp)`: matches question received over TCP if `tcp` is true, over UDP otherwise * Rule management related: * `showRules()`: show all defined rules (Pool, Block, QPS, addAnyTCRule) * `rmRule(n)`: remove rule n @@ -836,7 +840,7 @@ instantiate a server with additional parameters * `SpoofCNAMEAction()`: forge a response with the specified CNAME value * `TCAction()`: create answer to query with TC and RD bits set, to move to TCP/IP * Specialist rule generators - * `addAnyTCRule()`: generate TC=1 answers to ANY queries, moving them to TCP + * `addAnyTCRule()`: generate TC=1 answers to ANY queries received over UDP, moving them to TCP * `addDomainSpoof(domain, ip[, ip6])`: generate answers for A queries using the ip parameter (AAAA if ip is an IPv6). If ip6 is supplied, generate answers for AAAA queries too * `addDomainCNAMESpoof(domain, cname)`: generate CNAME answers for queries using the specified value * `addDisableValidationRule(domain)`: set the CD flags to 1 for all queries matching the specified domain diff --git a/pdns/dnsdist-lua.cc b/pdns/dnsdist-lua.cc index 38f48aa52..c4793dde3 100644 --- a/pdns/dnsdist-lua.cc +++ b/pdns/dnsdist-lua.cc @@ -312,10 +312,14 @@ vector> setupLua(bool client, const std::string& confi } ); g_lua.writeFunction("makeRule", makeRule); + g_lua.writeFunction("addAnyTCRule", []() { setLuaSideEffect(); auto rules=g_rulactions.getCopy(); - rules.push_back({ std::make_shared(0xff), std::make_shared()}); + std::vector >> v; + v.push_back({1, std::make_shared(0xff)}); + v.push_back({2, std::make_shared(false)}); + rules.push_back({ std::shared_ptr(new AndRule(v)), std::make_shared()}); g_rulactions.setState(rules); }); @@ -699,6 +703,9 @@ vector> setupLua(bool client, const std::string& confi return std::shared_ptr(new AndRule(a)); }); + g_lua.writeFunction("TCPRule", [](bool tcp) { + return std::shared_ptr(new TCPRule(tcp)); + }); g_lua.writeFunction("addAction", [](luadnsrule_t var, std::shared_ptr ea) { diff --git a/pdns/dnsrulactions.hh b/pdns/dnsrulactions.hh index b242b709d..4b6aa5f51 100644 --- a/pdns/dnsrulactions.hh +++ b/pdns/dnsrulactions.hh @@ -211,6 +211,24 @@ private: uint16_t d_qtype; }; +class TCPRule : public DNSRule +{ +public: + TCPRule(bool tcp): d_tcp(tcp) + { + } + bool matches(const DNSQuestion* dq) const override + { + return dq->tcp == d_tcp; + } + string toString() const override + { + return (d_tcp ? "TCP" : "UDP"); + } +private: + bool d_tcp; +}; + class DropAction : public DNSAction { public: diff --git a/regression-tests.dnsdist/dnsdisttests.py b/regression-tests.dnsdist/dnsdisttests.py index c92cb3ea4..e7833d242 100644 --- a/regression-tests.dnsdist/dnsdisttests.py +++ b/regression-tests.dnsdist/dnsdisttests.py @@ -157,6 +157,7 @@ class DNSDistTest(unittest.TestCase): if not answered: # unexpected query, or health check response = dns.message.make_response(request) + rrset = None if request.question[0].rdclass == dns.rdataclass.IN: if request.question[0].rdtype == dns.rdatatype.A: rrset = dns.rrset.from_text(request.question[0].name, @@ -211,6 +212,7 @@ class DNSDistTest(unittest.TestCase): if not answered: # unexpected query, or health check response = dns.message.make_response(request) + rrset = None if request.question[0].rdclass == dns.rdataclass.IN: if request.question[0].rdtype == dns.rdatatype.A: rrset = dns.rrset.from_text(request.question[0].name, diff --git a/regression-tests.dnsdist/test_Basics.py b/regression-tests.dnsdist/test_Basics.py index 286d7cad0..4414481bb 100644 --- a/regression-tests.dnsdist/test_Basics.py +++ b/regression-tests.dnsdist/test_Basics.py @@ -86,6 +86,7 @@ class TestBasics(DNSDistTest): dnsdist is configured to reply with TC to ANY queries, send an ANY query and check the result. + It should be truncated over UDP, not over TCP. """ name = 'any.tests.powerdns.com.' query = dns.message.make_query(name, 'ANY', 'IN') @@ -96,9 +97,22 @@ class TestBasics(DNSDistTest): receivedResponse.id = expectedResponse.id self.assertEquals(receivedResponse, expectedResponse) - (_, receivedResponse) = self.sendTCPQuery(query, response=None, useQueue=False) - receivedResponse.id = expectedResponse.id - self.assertEquals(receivedResponse, expectedResponse) + response = dns.message.make_response(query) + rrset = dns.rrset.from_text('any.tests.powerdns.com.', + 3600, + dns.rdataclass.IN, + dns.rdatatype.A, + '127.0.0.1') + + response.answer.append(rrset) + + (receivedQuery, receivedResponse) = self.sendTCPQuery(query, response) + self.assertTrue(receivedQuery) + self.assertTrue(receivedResponse) + receivedQuery.id = query.id + receivedResponse.id = response.id + self.assertEquals(query, receivedQuery) + self.assertEquals(receivedResponse, response) def testTruncateTC(self): """