]> granicus.if.org Git - pdns/commitdiff
dnsdist: Add NotRule() and OrRule()
authorRemi Gacogne <remi.gacogne@powerdns.com>
Mon, 18 Jan 2016 10:19:40 +0000 (11:19 +0100)
committerRemi Gacogne <remi.gacogne@powerdns.com>
Mon, 18 Jan 2016 10:19:40 +0000 (11:19 +0100)
pdns/README-dnsdist.md
pdns/dnsdist-lua.cc
pdns/dnsrulactions.hh
regression-tests.dnsdist/test_Advanced.py
regression-tests.dnsdist/test_Basics.py

index 40718e13c832befbf4b59ad4d1a8b5f0f8945653..ecf696459b8775fa61964ce3650fd58aa5ccd492 100644 (file)
@@ -303,7 +303,10 @@ Rules have selectors and actions. Current selectors are:
  * 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.
+Special rules are:
+ * `AndRule{rule1, rule2}`, which only matches if all of its subrules match
+ * `OrRule{rule1, rule2}`, which matches if at least one of its subrules match
+ * `NotRule(rule)`, which matches if its subrule does not match
 
 Current actions are:
  * Drop (DropAction)
@@ -334,6 +337,8 @@ A DNS rule can be:
  * a MaxQPSIPRule
  * a MaxQPSRule
  * a NetmaskGroupRule
+ * a NotRule
+ * an OrRule
  * a QTypeRule
  * a RegexRule
  * a SuffixMatchNodeRule
@@ -815,6 +820,8 @@ instantiate a server with additional parameters
    * `MaxQPSIPRule(qps, v4Mask=32, v6Mask=64)`: matches traffic exceeding the qps limit per subnet
    * `MaxQPSRule(qps)`: matches traffic not exceeding this qps limit
    * `NetmaskGroupRule()`: matches traffic from the specified network range
+   * `NotRule()`: matches if the sub-rule does not match
+   * `OrRule()`: matches if at least one of the sub-rules matches
    * `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
index c4793dde314cc27dd9b8b2016000bc20b5f25b7a..d05baf19d5ae6b9cd37e8d898d44240356f10a51 100644 (file)
@@ -703,10 +703,18 @@ vector<std::function<void(void)>> setupLua(bool client, const std::string& confi
       return std::shared_ptr<DNSRule>(new AndRule(a));
     });
 
+  g_lua.writeFunction("OrRule", [](vector<pair<int, std::shared_ptr<DNSRule> > >a) {
+      return std::shared_ptr<DNSRule>(new OrRule(a));
+    });
+
   g_lua.writeFunction("TCPRule", [](bool tcp) {
       return std::shared_ptr<DNSRule>(new TCPRule(tcp));
     });
 
+  g_lua.writeFunction("NotRule", [](std::shared_ptr<DNSRule>rule) {
+      return std::shared_ptr<DNSRule>(new NotRule(rule));
+    });
+
   g_lua.writeFunction("addAction", [](luadnsrule_t var, std::shared_ptr<DNSAction> ea) 
                      {
                         setLuaSideEffect();
index 4b6aa5f51520940cea816684a2bd8e5b1dd56263..5af45777ff316463a0b18d5a06afcb9d0c0d2609 100644 (file)
@@ -152,6 +152,41 @@ private:
 };
 
 
+class OrRule : public DNSRule
+{
+public:
+  OrRule(const vector<pair<int, shared_ptr<DNSRule> > >& rules)
+  {
+    for(const auto& r : rules)
+      d_rules.push_back(r.second);
+  }
+
+  bool matches(const DNSQuestion* dq) const override
+  {
+    auto iter = d_rules.begin();
+    for(; iter != d_rules.end(); ++iter)
+      if((*iter)->matches(dq))
+        return true;
+    return false;
+  }
+
+  string toString() const override
+  {
+    string ret;
+    for(const auto& rule : d_rules) {
+      if(!ret.empty())
+        ret+= " || ";
+      ret += "("+ rule->toString()+")";
+    }
+    return ret;
+  }
+private:
+
+  vector<std::shared_ptr<DNSRule> > d_rules;
+
+};
+
+
 class RegexRule : public DNSRule
 {
 public:
@@ -229,6 +264,26 @@ private:
   bool d_tcp;
 };
 
+
+class NotRule : public DNSRule
+{
+public:
+  NotRule(shared_ptr<DNSRule>& rule): d_rule(rule)
+  {
+  }
+  bool matches(const DNSQuestion* dq) const override
+  {
+    return !d_rule->matches(dq);
+  }
+  string toString() const override
+  {
+    return "!"+ d_rule->toString();
+  }
+private:
+  shared_ptr<DNSRule> d_rule;
+};
+
+
 class DropAction : public DNSAction
 {
 public:
index f35a2b07173cfd92e4c36bdc5a34974315f5aa3b..e93ea7936f222aba73a6a728843a92e871ea5a00 100644 (file)
@@ -683,3 +683,184 @@ class TestAdvancedLuaSpoof(DNSDistTest):
         self.assertTrue(receivedResponse)
         receivedResponse.id = expectedResponse.id
         self.assertEquals(expectedResponse, receivedResponse)
+
+class TestAdvancedTruncateAnyAndTCP(DNSDistTest):
+
+    _config_template = """
+    truncateTC(false)
+    addAction(AndRule({QTypeRule("ANY"), TCPRule(true)}), TCAction())
+    newServer{address="127.0.0.1:%s"}
+    """
+    def testTruncateAnyOverTCP(self):
+        """
+        Advanced: Truncate ANY over TCP
+
+        Send an ANY query to "anytruncatetcp.tests.powerdns.com.",
+        should be truncated over TCP, not over UDP (yes, it makes no sense,
+        deal with it).
+        """
+        name = 'anytruncatetcp.tests.powerdns.com.'
+        query = dns.message.make_query(name, 'ANY', 'IN')
+
+        response = dns.message.make_response(query)
+        rrset = dns.rrset.from_text(name,
+                                    3600,
+                                    dns.rdataclass.IN,
+                                    dns.rdatatype.A,
+                                    '127.0.0.1')
+
+        response.answer.append(rrset)
+
+        (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
+        self.assertTrue(receivedQuery)
+        self.assertTrue(receivedResponse)
+        receivedQuery.id = query.id
+        receivedResponse.id = response.id
+        self.assertEquals(query, receivedQuery)
+        self.assertEquals(receivedResponse, response)
+
+        expectedResponse = dns.message.make_response(query)
+        expectedResponse.flags |= dns.flags.TC
+
+        (_, receivedResponse) = self.sendTCPQuery(query, response=None, useQueue=False)
+        receivedResponse.id = expectedResponse.id
+        self.assertEquals(receivedResponse, expectedResponse)
+
+class TestAdvancedAndNot(DNSDistTest):
+
+    _config_template = """
+    addAction(AndRule({NotRule(QTypeRule("A")), TCPRule(false)}), RCodeAction(4))
+    newServer{address="127.0.0.1:%s"}
+    """
+    def testAOverUDPReturnsNotImplementedCanary(self):
+        """
+        Advanced: !A && UDP canary
+
+        dnsdist is configured to reply 'not implemented' for query
+        over UDP AND !qtype A.
+        We send an A query over UDP and TCP, and check that the
+        response is OK.
+        """
+        name = 'andnot.tests.powerdns.com.'
+        query = dns.message.make_query(name, 'A', 'IN')
+        response = dns.message.make_response(query)
+        rrset = dns.rrset.from_text(name,
+                                    3600,
+                                    dns.rdataclass.IN,
+                                    dns.rdatatype.A,
+                                    '127.0.0.1')
+        response.answer.append(rrset)
+
+        (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
+        self.assertTrue(receivedQuery)
+        self.assertTrue(receivedResponse)
+        receivedQuery.id = query.id
+        receivedResponse.id = response.id
+        self.assertEquals(query, receivedQuery)
+        self.assertEquals(receivedResponse, response)
+
+        (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 testAOverUDPReturnsNotImplemented(self):
+        """
+        Advanced: !A && UDP
+
+        dnsdist is configured to reply 'not implemented' for query
+        over UDP AND !qtype A.
+        We send a TXT query over UDP and TCP, and check that the
+        response is OK for TCP and 'not implemented' for UDP.
+        """
+        name = 'andnot.tests.powerdns.com.'
+        query = dns.message.make_query(name, 'TXT', 'IN')
+
+        expectedResponse = dns.message.make_response(query)
+        expectedResponse.set_rcode(dns.rcode.NOTIMP)
+
+        (_, receivedResponse) = self.sendUDPQuery(query, response=None, useQueue=False)
+        receivedResponse.id = expectedResponse.id
+        self.assertEquals(receivedResponse, expectedResponse)
+
+        response = dns.message.make_response(query)
+        rrset = dns.rrset.from_text(name,
+                                    3600,
+                                    dns.rdataclass.IN,
+                                    dns.rdatatype.TXT,
+                                    'nothing to see here')
+        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)
+
+class TestAdvancedOr(DNSDistTest):
+
+    _config_template = """
+    addAction(OrRule({QTypeRule("A"), TCPRule(false)}), RCodeAction(4))
+    newServer{address="127.0.0.1:%s"}
+    """
+    def testAAAAOverUDPReturnsNotImplemented(self):
+        """
+        Advanced: A || UDP: AAAA
+
+        dnsdist is configured to reply 'not implemented' for query
+        over UDP OR qtype A.
+        We send an AAAA query over UDP and TCP, and check that the
+        response is 'not implemented' for UDP and OK for TCP.
+        """
+        name = 'aorudp.tests.powerdns.com.'
+        query = dns.message.make_query(name, 'AAAA', 'IN')
+        response = dns.message.make_response(query)
+        rrset = dns.rrset.from_text(name,
+                                    3600,
+                                    dns.rdataclass.IN,
+                                    dns.rdatatype.AAAA,
+                                    '::1')
+        response.answer.append(rrset)
+
+        expectedResponse = dns.message.make_response(query)
+        expectedResponse.set_rcode(dns.rcode.NOTIMP)
+
+        (_, receivedResponse) = self.sendUDPQuery(query, response=None, useQueue=False)
+        receivedResponse.id = expectedResponse.id
+        self.assertEquals(receivedResponse, expectedResponse)
+
+        (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 testAOverUDPReturnsNotImplemented(self):
+        """
+        Advanced: A || UDP: A
+
+        dnsdist is configured to reply 'not implemented' for query
+        over UDP OR qtype A.
+        We send an A query over UDP and TCP, and check that the
+        response is 'not implemented' for both.
+        """
+        name = 'aorudp.tests.powerdns.com.'
+        query = dns.message.make_query(name, 'A', 'IN')
+
+        expectedResponse = dns.message.make_response(query)
+        expectedResponse.set_rcode(dns.rcode.NOTIMP)
+
+        (_, receivedResponse) = self.sendUDPQuery(query, response=None, useQueue=False)
+        receivedResponse.id = expectedResponse.id
+        self.assertEquals(receivedResponse, expectedResponse)
+
+        (_, receivedResponse) = self.sendTCPQuery(query, response=None, useQueue=False)
+        receivedResponse.id = expectedResponse.id
+        self.assertEquals(receivedResponse, expectedResponse)
index 4414481bb1cf3c9e1cd317944ea371482e82ac15..68baf8c28ede9a965e997c60542ea59c97b0c537 100644 (file)
@@ -57,7 +57,7 @@ class TestBasics(DNSDistTest):
         name = 'simplea.tests.powerdns.com.'
         query = dns.message.make_query(name, 'A', 'IN', use_edns=False)
         response = dns.message.make_response(query)
-        rrset = dns.rrset.from_text('simplea.tests.powerdns.com.',
+        rrset = dns.rrset.from_text(name,
                                     3600,
                                     dns.rdataclass.IN,
                                     dns.rdatatype.A,
@@ -98,7 +98,7 @@ class TestBasics(DNSDistTest):
         self.assertEquals(receivedResponse, expectedResponse)
 
         response = dns.message.make_response(query)
-        rrset = dns.rrset.from_text('any.tests.powerdns.com.',
+        rrset = dns.rrset.from_text(name,
                                     3600,
                                     dns.rdataclass.IN,
                                     dns.rdatatype.A,
@@ -173,7 +173,7 @@ class TestBasics(DNSDistTest):
         Basics: NOTIMPL for specific name and qtype
 
         dnsdist is configured to reply 'not implemented' for query
-        matching "nameAndQtype.tests.powerdns.com." AND qtype TXT/
+        matching "nameAndQtype.tests.powerdns.com." AND qtype TXT.
         We send a TXT query for "nameAndQtype.powerdns.com."
         and check that the response is 'not implemented'.
         """
@@ -195,7 +195,7 @@ class TestBasics(DNSDistTest):
         Basics: NOTIMPL qtype canary
 
         dnsdist is configured to reply 'not implemented' for query
-        matching "nameAndQtype.tests.powerdns.com." AND qtype TXT/
+        matching "nameAndQtype.tests.powerdns.com." AND qtype TXT.
         We send a A query for "nameAndQtype.tests.powerdns.com."
         and check that the response is OK.
         """
@@ -230,7 +230,7 @@ class TestBasics(DNSDistTest):
         Basics: NOTIMPL qname canary
 
         dnsdist is configured to reply 'not implemented' for query
-        matching "nameAndQtype.tests.powerdns.com." AND qtype TXT/
+        matching "nameAndQtype.tests.powerdns.com." AND qtype TXT.
         We send a TXT query for "OtherNameAndQtype.tests.powerdns.com."
         and check that the response is OK.
         """