]> git.ipfire.org Git - thirdparty/pdns.git/commitdiff
Merge pull request #8718 from rgacogne/rec-fix-pb-source-port
authorOtto Moerbeek <otto.moerbeek@open-xchange.com>
Tue, 21 Jan 2020 10:09:55 +0000 (11:09 +0100)
committerGitHub <noreply@github.com>
Tue, 21 Jan 2020 10:09:55 +0000 (11:09 +0100)
Make ComboAddress::setPort() update the current object

21 files changed:
builder-support/specs/dnsdist.spec
builder-support/specs/pdns-recursor.spec
docs/backends/remote.rst
modules/remotebackend/remotebackend.cc
modules/remotebackend/remotebackend.hh
modules/remotebackend/test-remotebackend.cc
pdns/dnsdist-console.cc
pdns/dnsdist-lua-actions.cc
pdns/dnsdist-lua-bindings.cc
pdns/dnsdist-lua.hh
pdns/dnsdist.cc
pdns/dnsdistdist/docs/rules-actions.rst
pdns/pdns_recursor.cc
regression-tests.dnsdist/test_Advanced.py
regression-tests.dnsdist/test_Basics.py
regression-tests.dnsdist/test_DOH.py
regression-tests.dnsdist/test_DynBlocks.py
regression-tests.dnsdist/test_EDNSSelfGenerated.py
regression-tests.dnsdist/test_RecordsCount.py
regression-tests.dnsdist/test_Spoofing.py
regression-tests.dnsdist/test_TCPKeepAlive.py

index 145f2d3b306e55b09031ec52c71112af1257244a..5614e978cb7981e202f93dd1ecf6b29f16af7603 100644 (file)
@@ -56,10 +56,7 @@ Requires(pre): shadow
 %endif
 %if 0%{?rhel} >= 7
 Requires(pre): shadow-utils
-%if 0%{?rhel} == 7
-# No fstrm in EPEL 8 (yet) https://bugzilla.redhat.com/show_bug.cgi?id=1760298
 BuildRequires: fstrm-devel
-%endif
 %systemd_requires
 %endif
 
@@ -101,12 +98,10 @@ sed -i '/^ExecStart/ s/dnsdist/dnsdist -u dnsdist -g dnsdist/' dnsdist.service.i
   --without-protobuf \
   --without-net-snmp
 %endif
-%if 0%{?rhel} == 7
-  --enable-dnstap \
-%endif
 %if 0%{?rhel} >= 7
   --with-gnutls \
   --with-protobuf \
+  --enable-dnstap \
   --with-lua=%{lua_implementation} \
   --with-libcap \
   --with-libsodium \
index 7f354b8590846a9da6e22863c71f7894f05eb884..39df2502128c1a5695c5203a2b90c7f312a93660 100644 (file)
@@ -35,12 +35,8 @@ BuildRequires: libatomic
 %if 0%{?rhel} >= 7
 BuildRequires: protobuf-compiler
 BuildRequires: protobuf-devel
-
-%if 0%{?rhel} == 7
-# No fstrm in EPEL 8 yet
 BuildRequires: fstrm-devel
 %endif
-%endif
 
 BuildRequires: openssl-devel
 BuildRequires: net-snmp-devel
@@ -83,6 +79,7 @@ package if you need a dns cache for your network.
 make %{?_smp_mflags} LIBRARY_PATH=/usr/lib64/boost148
 %else
     --with-protobuf \
+    --enable-dnstap \
     --with-libcap \
     --with-lua=%{lua_implementation} \
     --enable-systemd --with-systemd=%{_unitdir}
index 49e74b4afc048a5bcb024812d9ca8e0e4d5966fc..6cb2653cc10069508698a12cf90e540440dbc582 100644 (file)
@@ -144,6 +144,11 @@ in this array will be logged in PowerDNS at loglevel ``info`` (6).
 Methods
 ^^^^^^^
 
+Methods required for different features
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+:Always required: ``initialize``, ``lookup``
+:Master operation: ``list``, ``getUpdatedMasters``, ``setNotified``
+
 ``initialize``
 ~~~~~~~~~~~~~~
 
@@ -958,12 +963,13 @@ Alternative response
 
 ``createSlaveDomain``
 ~~~~~~~~~~~~~~~~~~~~~
-
 Creates new domain. This method is called when NOTIFY is received and
 you are superslaving.
 
-Mandatory: No Parameters: ip, domain Optional parameters: nameserver,
-account Reply: true for success, false for failure
+ - Mandatory: No
+ - Parameters: ip, domain
+ - Optional parameters: nameserver, account
+ - Reply: true for success, false for failure
 
 Example JSON/RPC
 ''''''''''''''''
@@ -1516,7 +1522,7 @@ Used to find out any updates to master domains. This is used to trigger notifica
 
 -  Mandatory: no
 -  Parameters: none
--  Reply: array of DomainInfo
+-  Reply: array of DomainInfo or at least the ``id``, ``zone``, ``serial`` and ``notified_serial`` fields
 
 Example JSON/RPC
 ''''''''''''''''
index 6d7f34437f78b6410377a55ebb871af4b87a7975..2c20cd306a678bddea867ba3ea95a118b5542b21 100644 (file)
@@ -904,6 +904,13 @@ void RemoteBackend::getAllDomains(vector<DomainInfo> *domains, bool include_disa
   }
 }
 
+void RemoteBackend::alsoNotifies(const DNSName &domain, set<string> *ips)
+{
+  std::vector<std::string> meta;
+  getDomainMetadata(domain, "ALSO-NOTIFY", meta);
+  ips->insert(meta.begin(), meta.end());
+}
+
 void RemoteBackend::getUpdatedMasters(vector<DomainInfo>* domains)
 {
   Json query = Json::object{
index c453c52e99e002ae5cb3c981feedda911ad7cb7c..647da8cdd953744bf60cc6d9f883471aeff465a9 100644 (file)
@@ -189,6 +189,7 @@ class RemoteBackend : public DNSBackend
   bool searchComments(const string &pattern, int maxResults, vector<Comment>& result) override;
   void getAllDomains(vector<DomainInfo> *domains, bool include_disabled=false) override;
   void getUpdatedMasters(vector<DomainInfo>* domains) override;
+  void alsoNotifies(const DNSName &domain, set<string> *ips) override;
 
   static DNSBackend *maker();
 
index acabae29336a86ec513cda617d410f7ea0fb5bd8..13d803b560450d5378d6bd17a09752235a000a91 100644 (file)
@@ -89,6 +89,17 @@ BOOST_AUTO_TEST_CASE(test_method_setDomainMetadata) {
    BOOST_CHECK(be->setDomainMetadata(DNSName("unit.test."),"TEST", meta));
 }
 
+BOOST_AUTO_TEST_CASE(test_method_alsoNotifies) {
+   BOOST_CHECK(be->setDomainMetadata(DNSName("unit.test."),"ALSO-NOTIFY", {"192.0.2.1"}));
+   std::set<std::string> alsoNotifies;
+   BOOST_TEST_MESSAGE("Testing alsoNotifies method");
+   be->alsoNotifies(DNSName("unit.test."), &alsoNotifies);
+   BOOST_CHECK_EQUAL(alsoNotifies.size(), 1);
+   if (alsoNotifies.size() > 0)
+      BOOST_CHECK_EQUAL(alsoNotifies.count("192.0.2.1"), 1);
+   BOOST_CHECK(be->setDomainMetadata(DNSName("unit.test."),"ALSO-NOTIFY", std::vector<std::string>()));
+}
+
 BOOST_AUTO_TEST_CASE(test_method_getDomainMetadata) {
    std::vector<std::string> meta;
    BOOST_TEST_MESSAGE("Testing getDomainMetadata method");
index 76b99967f5b55cfcfe98a53fc115a1a1c2d8a445..6c0f78205dac755d1d325aa8fc42a4621b4620f3 100644 (file)
@@ -569,8 +569,8 @@ const std::vector<ConsoleKeyword> g_consoleKeywords{
   { "snmpAgent", true, "enableTraps [, masterSocket]", "enable `SNMP` support. `enableTraps` is a boolean indicating whether traps should be sent and `masterSocket` an optional string specifying how to connect to the master agent"},
   { "SNMPTrapAction", true, "[reason]", "send an SNMP trap, adding the optional `reason` string as the query description"},
   { "SNMPTrapResponseAction", true, "[reason]", "send an SNMP trap, adding the optional `reason` string as the response description"},
-  { "SpoofAction", true, "{ip, ...} ", "forge a response with the specified IPv4 (for an A query) or IPv6 (for an AAAA). If you specify multiple addresses, all that match the query type (A, AAAA or ANY) will get spoofed in" },
-  { "SpoofCNAMEAction", true, "cname", "Forge a response with the specified CNAME value" },
+  { "SpoofAction", true, "ip|list of ips [, options]", "forge a response with the specified IPv4 (for an A query) or IPv6 (for an AAAA). If you specify multiple addresses, all that match the query type (A, AAAA or ANY) will get spoofed in" },
+  { "SpoofCNAMEAction", true, "cname [, options]", "Forge a response with the specified CNAME value" },
   { "SuffixMatchNodeRule", true, "smn[, quiet]", "Matches based on a group of domain suffixes for rapid testing of membership. Pass true as second parameter to prevent listing of all domains matched" },
   { "TagAction", true, "name, value", "set the tag named 'name' to the given value" },
   { "TagResponseAction", true, "name, value", "set the tag named 'name' to the given value" },
index 0c3ae18a078ed4a9b890089c47d64f44efa3ea7c..cc714d23d36e1bc31dfe578eed317419f7c9d426 100644 (file)
@@ -306,6 +306,7 @@ public:
   {
     dq->dh->rcode = d_rcode;
     dq->dh->qr = true; // for good measure
+    setResponseHeadersFromConfig(*dq->dh, d_responseConfig);
     return Action::HeaderModify;
   }
   std::string toString() const override
@@ -313,6 +314,7 @@ public:
     return "set rcode "+std::to_string(d_rcode);
   }
 
+  ResponseConfig d_responseConfig;
 private:
   uint8_t d_rcode;
 };
@@ -326,6 +328,7 @@ public:
     dq->dh->rcode = (d_rcode & 0xF);
     dq->ednsRCode = ((d_rcode & 0xFFF0) >> 4);
     dq->dh->qr = true; // for good measure
+    setResponseHeadersFromConfig(*dq->dh, d_responseConfig);
     return Action::HeaderModify;
   }
   std::string toString() const override
@@ -333,6 +336,7 @@ public:
     return "set ercode "+ERCode::to_s(d_rcode);
   }
 
+  ResponseConfig d_responseConfig;
 private:
   uint8_t d_rcode;
 };
@@ -444,8 +448,7 @@ DNSAction::Action SpoofAction::operator()(DNSQuestion* dq, std::string* ruleresu
   char* dest = ((char*)dq->dh) + dq->len;
 
   dq->dh->qr = true; // for good measure
-  dq->dh->ra = dq->dh->rd; // for good measure
-  dq->dh->ad = false;
+  setResponseHeadersFromConfig(*dq->dh, d_responseConfig);
   dq->dh->ancount = 0;
   dq->dh->arcount = 0; // for now, forget about your EDNS, we're marching over it
 
@@ -1179,6 +1182,7 @@ public:
 
     dq->du->setHTTPResponse(d_code, d_body, d_contentType);
     dq->dh->qr = true; // for good measure
+    setResponseHeadersFromConfig(*dq->dh, d_responseConfig);
     return Action::HeaderModify;
   }
 
@@ -1186,6 +1190,8 @@ public:
   {
     return "return an HTTP status of " + std::to_string(d_code);
   }
+
+  ResponseConfig d_responseConfig;
 private:
   std::string d_body;
   std::string d_contentType;
@@ -1244,6 +1250,42 @@ static void addAction(GlobalStateHolder<vector<T> > *someRulActions, const luadn
     });
 }
 
+typedef std::unordered_map<std::string, boost::variant<bool> > responseParams_t;
+
+static void parseResponseConfig(boost::optional<responseParams_t> vars, ResponseConfig& config)
+{
+  if (vars) {
+    if (vars->count("aa")) {
+      config.setAA = boost::get<bool>((*vars)["aa"]);
+    }
+    if (vars->count("ad")) {
+      config.setAD = boost::get<bool>((*vars)["ad"]);
+    }
+    if (vars->count("ra")) {
+      config.setRA = boost::get<bool>((*vars)["ra"]);
+    }
+  }
+}
+
+void setResponseHeadersFromConfig(dnsheader& dh, const ResponseConfig& config)
+{
+  if (config.setAA) {
+    dh.aa = *config.setAA;
+  }
+  if (config.setAD) {
+    dh.ad = *config.setAD;
+  }
+  else {
+    dh.ad = false;
+  }
+  if (config.setRA) {
+    dh.ra = *config.setRA;
+  }
+  else {
+    dh.ra = dh.rd; // for good measure
+  }
+}
+
 void setupLuaActions()
 {
   g_lua.writeFunction("newRuleAction", [](luadnsrule_t dnsrule, std::shared_ptr<DNSAction> action, boost::optional<luaruleparams_t> params) {
@@ -1336,7 +1378,7 @@ void setupLuaActions()
       return std::shared_ptr<DNSAction>(new QPSPoolAction(limit, a));
     });
 
-  g_lua.writeFunction("SpoofAction", [](boost::variant<std::string,vector<pair<int, std::string>>> inp, boost::optional<std::string> b ) {
+  g_lua.writeFunction("SpoofAction", [](boost::variant<std::string,vector<pair<int, std::string>>> inp, boost::optional<std::string> b, boost::optional<responseParams_t> vars ) {
       vector<ComboAddress> addrs;
       if(auto s = boost::get<std::string>(&inp))
         addrs.push_back(ComboAddress(*s));
@@ -1345,13 +1387,23 @@ void setupLuaActions()
         for(const auto& a: v)
           addrs.push_back(ComboAddress(a.second));
       }
-      if(b)
+      if(b) {
         addrs.push_back(ComboAddress(*b));
-      return std::shared_ptr<DNSAction>(new SpoofAction(addrs));
+      }
+
+      auto ret = std::shared_ptr<DNSAction>(new SpoofAction(addrs));
+      auto sa = std::dynamic_pointer_cast<SpoofAction>(ret);
+      parseResponseConfig(vars, sa->d_responseConfig);
+      return ret;
     });
 
-  g_lua.writeFunction("SpoofCNAMEAction", [](const std::string& a) {
-      return std::shared_ptr<DNSAction>(new SpoofAction(a));
+  g_lua.writeFunction("SpoofCNAMEAction", [](const std::string& a, boost::optional<responseParams_t> vars) {
+      auto ret = std::shared_ptr<DNSAction>(new SpoofAction(a));
+      ResponseConfig responseConfig;
+      parseResponseConfig(vars, responseConfig);
+      auto sa = std::dynamic_pointer_cast<SpoofAction>(ret);
+      sa->d_responseConfig = responseConfig;
+      return ret;
     });
 
   g_lua.writeFunction("DropAction", []() {
@@ -1386,12 +1438,18 @@ void setupLuaActions()
       return std::shared_ptr<DNSResponseAction>(new LogResponseAction(fname ? *fname : "", append ? *append : false, buffered ? *buffered : false, verboseOnly ? *verboseOnly : true, includeTimestamp ? *includeTimestamp : false));
     });
 
-  g_lua.writeFunction("RCodeAction", [](uint8_t rcode) {
-      return std::shared_ptr<DNSAction>(new RCodeAction(rcode));
+  g_lua.writeFunction("RCodeAction", [](uint8_t rcode, boost::optional<responseParams_t> vars) {
+      auto ret = std::shared_ptr<DNSAction>(new RCodeAction(rcode));
+      auto rca = std::dynamic_pointer_cast<RCodeAction>(ret);
+      parseResponseConfig(vars, rca->d_responseConfig);
+      return ret;
     });
 
-  g_lua.writeFunction("ERCodeAction", [](uint8_t rcode) {
-      return std::shared_ptr<DNSAction>(new ERCodeAction(rcode));
+  g_lua.writeFunction("ERCodeAction", [](uint8_t rcode, boost::optional<responseParams_t> vars) {
+      auto ret = std::shared_ptr<DNSAction>(new ERCodeAction(rcode));
+      auto erca = std::dynamic_pointer_cast<ERCodeAction>(ret);
+      parseResponseConfig(vars, erca->d_responseConfig);
+      return ret;
     });
 
   g_lua.writeFunction("SkipCacheAction", []() {
@@ -1543,8 +1601,11 @@ void setupLuaActions()
     });
 
 #ifdef HAVE_DNS_OVER_HTTPS
-  g_lua.writeFunction("HTTPStatusAction", [](uint16_t status, std::string body, boost::optional<std::string> contentType) {
-      return std::shared_ptr<DNSAction>(new HTTPStatusAction(status, body, contentType ? *contentType : ""));
+  g_lua.writeFunction("HTTPStatusAction", [](uint16_t status, std::string body, boost::optional<std::string> contentType, boost::optional<responseParams_t> vars) {
+      auto ret = std::shared_ptr<DNSAction>(new HTTPStatusAction(status, body, contentType ? *contentType : ""));
+      auto hsa = std::dynamic_pointer_cast<HTTPStatusAction>(ret);
+      parseResponseConfig(vars, hsa->d_responseConfig);
+      return ret;
     });
 #endif /* HAVE_DNS_OVER_HTTPS */
 
index b02b4232f996d4a8dadf7947b3590f5ec5a9ad3d..2cfb6f61e800cd58800e220f69271dbb6cdca220 100644 (file)
@@ -129,6 +129,30 @@ void setupLuaBindings(bool client)
       return (bool)dh.rd;
     });
 
+  g_lua.registerFunction<void(dnsheader::*)(bool)>("setRA", [](dnsheader& dh, bool v) {
+      dh.ra=v;
+    });
+
+  g_lua.registerFunction<bool(dnsheader::*)()>("getRA", [](dnsheader& dh) {
+      return (bool)dh.ra;
+    });
+
+  g_lua.registerFunction<void(dnsheader::*)(bool)>("setAD", [](dnsheader& dh, bool v) {
+      dh.ad=v;
+    });
+
+  g_lua.registerFunction<bool(dnsheader::*)()>("getAD", [](dnsheader& dh) {
+      return (bool)dh.ad;
+    });
+
+  g_lua.registerFunction<void(dnsheader::*)(bool)>("setAA", [](dnsheader& dh, bool v) {
+      dh.aa=v;
+     });
+
+  g_lua.registerFunction<bool(dnsheader::*)()>("getAA", [](dnsheader& dh) {
+      return (bool)dh.aa;
+    });
+
   g_lua.registerFunction<void(dnsheader::*)(bool)>("setCD", [](dnsheader& dh, bool v) {
       dh.cd=v;
     });
index 827d6e1cfb99c78df7ce96b168e1406e1ca56dcb..aa33859c8001f47c956ab50137893c1f70af6f90 100644 (file)
  */
 #pragma once
 
+struct ResponseConfig
+{
+  boost::optional<bool> setAA{boost::none};
+  boost::optional<bool> setAD{boost::none};
+  boost::optional<bool> setRA{boost::none};
+};
+void setResponseHeadersFromConfig(dnsheader& dh, const ResponseConfig& config);
+
 class LuaAction : public DNSAction
 {
 public:
@@ -72,6 +80,9 @@ public:
     }
     return ret;
   }
+
+
+  ResponseConfig d_responseConfig;
 private:
   std::vector<ComboAddress> d_addrs;
   DNSName d_cname;
index 5dfd81549b9818c168fd0f209deb2fa5ab22f4d6..96af451cf7f52905ac9b89ef937312c923e6f37f 100644 (file)
@@ -1098,6 +1098,9 @@ bool processRulesResult(const DNSAction::Action& action, DNSQuestion& dq, std::s
   case DNSAction::Action::Truncate:
     dq.dh->tc = true;
     dq.dh->qr = true;
+    dq.dh->ra = dq.dh->rd;
+    dq.dh->aa = false;
+    dq.dh->ad = false;
     return true;
     break;
   case DNSAction::Action::HeaderModify:
@@ -1185,6 +1188,9 @@ static bool applyRulesToQuery(LocalHolders& holders, DNSQuestion& dq, const stru
           vinfolog("Query from %s truncated because of dynamic block", dq.remote->toStringWithPort());
           dq.dh->tc = true;
           dq.dh->qr = true;
+          dq.dh->ra = dq.dh->rd;
+          dq.dh->aa = false;
+          dq.dh->ad = false;
           return true;
         }
         else {
@@ -1240,6 +1246,9 @@ static bool applyRulesToQuery(LocalHolders& holders, DNSQuestion& dq, const stru
           vinfolog("Query from %s for %s truncated because of dynamic block", dq.remote->toStringWithPort(), dq.qname->toLogString());
           dq.dh->tc = true;
           dq.dh->qr = true;
+          dq.dh->ra = dq.dh->rd;
+          dq.dh->aa = false;
+          dq.dh->ad = false;
           return true;
         }
         else {
index c1d1d10a0aaab85d60c9710886c0941e27619b25..1d698b69d5fc59466b646ebcc9fd8d1c0abc5658 100644 (file)
@@ -960,24 +960,44 @@ The following actions exist.
   :param int v6: The IPv6 netmask length
 
 
-.. function:: ERCodeAction(rcode)
+.. function:: ERCodeAction(rcode [, options])
 
   .. versionadded:: 1.4.0
 
+  .. versionchanged:: 1.5.0
+    Added the optional parameter ``options``.
+
   Reply immediately by turning the query into a response with the specified EDNS extended ``rcode``.
   ``rcode`` can be specified as an integer or as one of the built-in :ref:`DNSRCode`.
 
   :param int rcode: The extended RCODE to respond with.
+  :param table options: A table with key: value pairs with options.
+
+  Options:
+
+  * ``aa``: bool - Set the AA bit to this value (true means the bit is set, false means it's cleared). Default is to clear it.
+  * ``ad``: bool - Set the AD bit to this value (true means the bit is set, false means it's cleared). Default is to clear it.
+  * ``ra``: bool - Set the RA bit to this value (true means the bit is set, false means it's cleared). Default is to copy the value of the RD bit from the incoming query.
 
-.. function:: HTTPStatusAction(status, body, contentType="")
+.. function:: HTTPStatusAction(status, body, contentType="" [, options])
 
   .. versionadded:: 1.4.0
 
+  .. versionchanged:: 1.5.0
+    Added the optional parameter ``options``.
+
   Return an HTTP response with a status code of ''status''. For HTTP redirects, ''body'' should be the redirect URL.
 
   :param int status: The HTTP status code to return.
   :param string body: The body of the HTTP response, or a URL if the status code is a redirect (3xx).
   :param string contentType: The HTTP Content-Type header to return for a 200 response, ignored otherwise. Default is ''application/dns-message''.
+  :param table options: A table with key: value pairs with options.
+
+  Options:
+
+  * ``aa``: bool - Set the AA bit to this value (true means the bit is set, false means it's cleared). Default is to clear it.
+  * ``ad``: bool - Set the AD bit to this value (true means the bit is set, false means it's cleared). Default is to clear it.
+  * ``ra``: bool - Set the RA bit to this value (true means the bit is set, false means it's cleared). Default is to copy the value of the RD bit from the incoming query.
 
 .. function:: KeyValueStoreLookupAction(kvs, lookupKey, destinationTag)
 
@@ -1092,12 +1112,22 @@ The following actions exist.
   :param int maxqps: The QPS limit for that pool
   :param string poolname: The name of the pool
 
-.. function:: RCodeAction(rcode)
+.. function:: RCodeAction(rcode [, options])
+
+  .. versionchanged:: 1.5.0
+    Added the optional parameter ``options``.
 
   Reply immediately by turning the query into a response with the specified ``rcode``.
   ``rcode`` can be specified as an integer or as one of the built-in :ref:`DNSRCode`.
 
   :param int rcode: The RCODE to respond with.
+  :param table options: A table with key: value pairs with options.
+
+  Options:
+
+  * ``aa``: bool - Set the AA bit to this value (true means the bit is set, false means it's cleared). Default is to clear it.
+  * ``ad``: bool - Set the AD bit to this value (true means the bit is set, false means it's cleared). Default is to clear it.
+  * ``ra``: bool - Set the RA bit to this value (true means the bit is set, false means it's cleared). Default is to copy the value of the RD bit from the incoming query.
 
 .. function:: RemoteLogAction(remoteLogger[, alterFunction [, options]])
 
@@ -1175,20 +1205,40 @@ The following actions exist.
 
   :param string message: The message to include
 
-.. function:: SpoofAction(ip[, ip[...]])
-              SpoofAction(ips)
+.. function:: SpoofAction(ip [, options])
+              SpoofAction(ips [, options])
+
+  .. versionchanged:: 1.5.0
+    Added the optional parameter ``options``.
 
   Forge a response with the specified IPv4 (for an A query) or IPv6 (for an AAAA) addresses.
   If you specify multiple addresses, all that match the query type (A, AAAA or ANY) will get spoofed in.
 
   :param string ip: An IPv4 and/or IPv6 address to spoof
   :param {string} ips: A table of IPv4 and/or IPv6 addresses to spoof
+  :param table options: A table with key: value pairs with options.
+
+  Options:
 
-.. function:: SpoofCNAMEAction(cname)
+  * ``aa``: bool - Set the AA bit to this value (true means the bit is set, false means it's cleared). Default is to clear it.
+  * ``ad``: bool - Set the AD bit to this value (true means the bit is set, false means it's cleared). Default is to clear it.
+  * ``ra``: bool - Set the RA bit to this value (true means the bit is set, false means it's cleared). Default is to copy the value of the RD bit from the incoming query.
+
+.. function:: SpoofCNAMEAction(cname [, options])
+
+  .. versionchanged:: 1.5.0
+    Added the optional parameter ``options``.
 
   Forge a response with the specified CNAME value.
 
   :param string cname: The name to respond with
+  :param table options: A table with key: value pairs with options.
+
+  Options:
+
+  * ``aa``: bool - Set the AA bit to this value (true means the bit is set, false means it's cleared). Default is to clear it.
+  * ``ad``: bool - Set the AD bit to this value (true means the bit is set, false means it's cleared). Default is to clear it.
+  * ``ra``: bool - Set the RA bit to this value (true means the bit is set, false means it's cleared). Default is to copy the value of the RD bit from the incoming query.
 
 .. function:: TagAction(name, value)
 
index 76b325497ae76754d92b31b57f36935cea9a93d0..ebfb3440a55872e28618b31e40ea3ab430e05ab9 100644 (file)
@@ -3819,6 +3819,27 @@ static void setupNODGlobal()
 }
 #endif /* NOD_ENABLED */
 
+static void checkSocketDir(void)
+{
+  struct stat st;
+  string dir(::arg()["socket-dir"]);
+  string msg;
+
+  if (stat(dir.c_str(), &st) == -1) {
+    msg = "it does not exist or cannot access";
+  }
+  else if (!S_ISDIR(st.st_mode)) {
+    msg = "it is not a directory";
+  }
+  else if (access(dir.c_str(),  R_OK | W_OK | X_OK) != 0) {
+    msg = "cannot read, write or search";
+  } else {
+    return;
+  }
+  g_log << Logger::Error << "Problem with socket directory " << dir << ": " << msg << "; see https://docs.powerdns.com/recursor/upgrade.html#x-to-4-3-0-or-master" << endl;
+  _exit(1);
+}
+
 static int serviceMain(int argc, char*argv[])
 {
   g_log.setName(s_programname);
@@ -4221,6 +4242,8 @@ static int serviceMain(int argc, char*argv[])
       g_log<<Logger::Info<<"Chrooted to '"<<::arg()["chroot"]<<"'"<<endl;
   }
 
+  checkSocketDir();
+
   s_pidfname=::arg()["socket-dir"]+"/"+s_programname+".pid";
   if(!s_pidfname.empty())
     unlink(s_pidfname.c_str()); // remove possible old pid file
index bb309b76387b98ce4489863eb5ee6cd35f4ee685..6be7d4f3c0c55488df0740ab62c51ff94a5abc6e 100644 (file)
@@ -426,6 +426,8 @@ class TestAdvancedTruncateAnyAndTCP(DNSDistTest):
         """
         name = 'anytruncatetcp.advanced.tests.powerdns.com.'
         query = dns.message.make_query(name, 'ANY', 'IN')
+        # dnsdist sets RA = RD for TC responses
+        query.flags &= ~dns.flags.RD
 
         response = dns.message.make_response(query)
         rrset = dns.rrset.from_text(name,
@@ -494,6 +496,8 @@ class TestAdvancedAndNot(DNSDistTest):
         """
         name = 'andnot.advanced.tests.powerdns.com.'
         query = dns.message.make_query(name, 'TXT', 'IN')
+        # dnsdist sets RA = RD for TC responses
+        query.flags &= ~dns.flags.RD
 
         expectedResponse = dns.message.make_response(query)
         expectedResponse.set_rcode(dns.rcode.NOTIMP)
@@ -533,6 +537,7 @@ class TestAdvancedOr(DNSDistTest):
         """
         name = 'aorudp.advanced.tests.powerdns.com.'
         query = dns.message.make_query(name, 'AAAA', 'IN')
+        query.flags &= ~dns.flags.RD
         response = dns.message.make_response(query)
         rrset = dns.rrset.from_text(name,
                                     3600,
@@ -565,6 +570,7 @@ class TestAdvancedOr(DNSDistTest):
         """
         name = 'aorudp.advanced.tests.powerdns.com.'
         query = dns.message.make_query(name, 'A', 'IN')
+        query.flags &= ~dns.flags.RD
 
         expectedResponse = dns.message.make_response(query)
         expectedResponse.set_rcode(dns.rcode.NOTIMP)
@@ -899,6 +905,7 @@ class TestAdvancedQPSNone(DNSDistTest):
         """
         name = 'qpsnone.advanced.tests.powerdns.com.'
         query = dns.message.make_query(name, 'A', 'IN')
+        query.flags &= ~dns.flags.RD
         expectedResponse = dns.message.make_response(query)
         expectedResponse.set_rcode(dns.rcode.REFUSED)
 
@@ -925,6 +932,7 @@ class TestAdvancedNMGRule(DNSDistTest):
         """
         name = 'nmgrule.advanced.tests.powerdns.com.'
         query = dns.message.make_query(name, 'A', 'IN')
+        query.flags &= ~dns.flags.RD
         expectedResponse = dns.message.make_response(query)
         expectedResponse.set_rcode(dns.rcode.REFUSED)
 
@@ -951,6 +959,7 @@ class TestDSTPortRule(DNSDistTest):
 
         name = 'dstportrule.advanced.tests.powerdns.com.'
         query = dns.message.make_query(name, 'A', 'IN')
+        query.flags &= ~dns.flags.RD
         expectedResponse = dns.message.make_response(query)
         expectedResponse.set_rcode(dns.rcode.REFUSED)
 
@@ -993,6 +1002,7 @@ class TestAdvancedLabelsCountRule(DNSDistTest):
         # more than 6 labels, the query should be refused
         name = 'not.ok.labelscount.advanced.tests.powerdns.com.'
         query = dns.message.make_query(name, 'A', 'IN')
+        query.flags &= ~dns.flags.RD
         expectedResponse = dns.message.make_response(query)
         expectedResponse.set_rcode(dns.rcode.REFUSED)
 
@@ -1004,6 +1014,7 @@ class TestAdvancedLabelsCountRule(DNSDistTest):
         # less than 5 labels, the query should be refused
         name = 'labelscountadvanced.tests.powerdns.com.'
         query = dns.message.make_query(name, 'A', 'IN')
+        query.flags &= ~dns.flags.RD
         expectedResponse = dns.message.make_response(query)
         expectedResponse.set_rcode(dns.rcode.REFUSED)
 
@@ -1045,6 +1056,7 @@ class TestAdvancedWireLengthRule(DNSDistTest):
         # too short, the query should be refused
         name = 'short.qnamewirelength.advanced.tests.powerdns.com.'
         query = dns.message.make_query(name, 'A', 'IN')
+        query.flags &= ~dns.flags.RD
         expectedResponse = dns.message.make_response(query)
         expectedResponse.set_rcode(dns.rcode.REFUSED)
 
@@ -1056,6 +1068,7 @@ class TestAdvancedWireLengthRule(DNSDistTest):
         # too long, the query should be refused
         name = 'toolongtobevalid.qnamewirelength.advanced.tests.powerdns.com.'
         query = dns.message.make_query(name, 'A', 'IN')
+        query.flags &= ~dns.flags.RD
         expectedResponse = dns.message.make_response(query)
         expectedResponse.set_rcode(dns.rcode.REFUSED)
 
@@ -1098,6 +1111,7 @@ class TestAdvancedIncludeDir(DNSDistTest):
         # this one should be refused
         name = 'notincludedir.advanced.tests.powerdns.com.'
         query = dns.message.make_query(name, 'A', 'IN')
+        query.flags &= ~dns.flags.RD
         expectedResponse = dns.message.make_response(query)
         expectedResponse.set_rcode(dns.rcode.REFUSED)
 
@@ -1239,6 +1253,8 @@ class TestAdvancedLuaTruncated(DNSDistTest):
         """
         name = 'tc.advanced.tests.powerdns.com.'
         query = dns.message.make_query(name, 'A', 'IN')
+        # dnsdist sets RA = RD for TC responses
+        query.flags &= ~dns.flags.RD
         response = dns.message.make_response(query)
         rrset = dns.rrset.from_text(name,
                                     3600,
@@ -1363,6 +1379,7 @@ class TestAdvancedRD(DNSDistTest):
         query = dns.message.make_query(name, 'A', 'IN')
         expectedResponse = dns.message.make_response(query)
         expectedResponse.set_rcode(dns.rcode.REFUSED)
+        expectedResponse.flags |= dns.flags.RA
 
         for method in ("sendUDPQuery", "sendTCPQuery"):
             sender = getattr(self, method)
@@ -1657,14 +1674,15 @@ class TestAdvancedEDNSVersionRule(DNSDistTest):
     addAction(EDNSVersionRule(0), ERCodeAction(DNSRCode.BADVERS))
     """
 
-    def testDropped(self):
+    def testBadVers(self):
         """
-        Advanced: A question with ECS version larger than 0 is dropped
+        Advanced: A question with ECS version larger than 0 yields BADVERS
         """
 
         name = 'ednsversionrule.advanced.tests.powerdns.com.'
 
         query = dns.message.make_query(name, 'A', 'IN', use_edns=1)
+        query.flags &= ~dns.flags.RD
         expectedResponse = dns.message.make_response(query)
         expectedResponse.set_rcode(dns.rcode.BADVERS)
 
index 24cbc549f0b01fb77f9a7ccca0c86be82be83a91..c2dc85f426bb51063c4ab6715a25306b6842f1d9 100644 (file)
@@ -90,6 +90,8 @@ class TestBasics(DNSDistTest):
         """
         name = 'any.tests.powerdns.com.'
         query = dns.message.make_query(name, 'ANY', 'IN')
+        # dnsdist sets RA = RD for TC responses
+        query.flags &= ~dns.flags.RD
         expectedResponse = dns.message.make_response(query)
         expectedResponse.flags |= dns.flags.TC
 
@@ -201,6 +203,7 @@ class TestBasics(DNSDistTest):
         """
         name = 'evil4242.regex.tests.powerdns.com.'
         query = dns.message.make_query(name, 'A', 'IN')
+        query.flags &= ~dns.flags.RD
         expectedResponse = dns.message.make_response(query)
         expectedResponse.set_rcode(dns.rcode.REFUSED)
 
@@ -243,6 +246,7 @@ class TestBasics(DNSDistTest):
         """
         name = 'nameAndQtype.tests.powerdns.com.'
         query = dns.message.make_query(name, 'TXT', 'IN')
+        query.flags &= ~dns.flags.RD
         expectedResponse = dns.message.make_response(query)
         expectedResponse.set_rcode(dns.rcode.NOTIMP)
 
@@ -396,6 +400,7 @@ class TestBasics(DNSDistTest):
         """
         name = 'dnsname.addaction.powerdns.com.'
         query = dns.message.make_query(name, 'A', 'IN')
+        query.flags &= ~dns.flags.RD
         expectedResponse = dns.message.make_response(query)
         expectedResponse.set_rcode(dns.rcode.REFUSED)
 
@@ -410,6 +415,7 @@ class TestBasics(DNSDistTest):
         """
         for name in ['dnsname-table{}.addaction.powerdns.com.'.format(i) for i in range(1,2)]:
             query = dns.message.make_query(name, 'A', 'IN')
+            query.flags &= ~dns.flags.RD
             expectedResponse = dns.message.make_response(query)
             expectedResponse.set_rcode(dns.rcode.REFUSED)
 
index 563d3b50292980af02a8173af12da59e9e350940..bb27fde2861e192435a9b3eecaf2de0f24278c0b 100644 (file)
@@ -321,6 +321,7 @@ class TestDOH(DNSDistDOHTest):
         name = 'refused.doh.tests.powerdns.com.'
         query = dns.message.make_query(name, 'A', 'IN')
         query.id = 0
+        query.flags &= ~dns.flags.RD
         expectedResponse = dns.message.make_response(query)
         expectedResponse.set_rcode(dns.rcode.REFUSED)
 
index 542eb218b97e144f78415fb139897bd972449540..fad4a22426e7e233f8248d67410c13cc23c6e656 100644 (file)
@@ -732,6 +732,8 @@ class TestDynBlockQPSActionTruncated(DNSDistTest):
         """
         name = 'qrateactiontruncated.dynblocks.tests.powerdns.com.'
         query = dns.message.make_query(name, 'A', 'IN')
+        # dnsdist sets RA = RD for TC responses
+        query.flags &= ~dns.flags.RD
         response = dns.message.make_response(query)
         rrset = dns.rrset.from_text(name,
                                     60,
index fdaeec32a0d368db5718b387d9bff6752bf2d559..42567f3cabf2935d28fb4baa5ab55f62c35af261 100644 (file)
@@ -33,6 +33,7 @@ class TestEDNSSelfGenerated(DNSDistTest):
         """
         name = 'no-edns.rcode.edns-self.tests.powerdns.com.'
         query = dns.message.make_query(name, 'A', 'IN')
+        query.flags &= ~dns.flags.RD
         expectedResponse = dns.message.make_response(query)
         expectedResponse.set_rcode(dns.rcode.REFUSED)
 
@@ -43,6 +44,8 @@ class TestEDNSSelfGenerated(DNSDistTest):
 
         name = 'no-edns.tc.edns-self.tests.powerdns.com.'
         query = dns.message.make_query(name, 'A', 'IN')
+        # dnsdist sets RA = RD for TC responses
+        query.flags &= ~dns.flags.RD
         expectedResponse = dns.message.make_response(query)
         expectedResponse.flags |= dns.flags.TC
 
@@ -83,6 +86,7 @@ class TestEDNSSelfGenerated(DNSDistTest):
         """
         name = 'edns-no-do.rcode.edns-self.tests.powerdns.com.'
         query = dns.message.make_query(name, 'A', 'IN', use_edns=True, payload=4096, want_dnssec=False)
+        query.flags &= ~dns.flags.RD
         expectedResponse = dns.message.make_response(query, our_payload=1042)
         expectedResponse.set_rcode(dns.rcode.REFUSED)
 
@@ -95,6 +99,8 @@ class TestEDNSSelfGenerated(DNSDistTest):
 
         name = 'edns-no-do.tc.edns-self.tests.powerdns.com.'
         query = dns.message.make_query(name, 'A', 'IN', use_edns=True, payload=4096, want_dnssec=False)
+        # dnsdist sets RA = RD for TC responses
+        query.flags &= ~dns.flags.RD
         expectedResponse = dns.message.make_response(query, our_payload=1042)
         expectedResponse.flags |= dns.flags.TC
 
@@ -141,6 +147,7 @@ class TestEDNSSelfGenerated(DNSDistTest):
         """
         name = 'edns-do.rcode.edns-self.tests.powerdns.com.'
         query = dns.message.make_query(name, 'A', 'IN', use_edns=True, payload=4096, want_dnssec=True)
+        query.flags &= ~dns.flags.RD
         expectedResponse = dns.message.make_response(query, our_payload=1042)
         expectedResponse.set_rcode(dns.rcode.REFUSED)
 
@@ -153,6 +160,8 @@ class TestEDNSSelfGenerated(DNSDistTest):
 
         name = 'edns-do.tc.edns-self.tests.powerdns.com.'
         query = dns.message.make_query(name, 'A', 'IN', use_edns=True, payload=4096, want_dnssec=True)
+        # dnsdist sets RA = RD for TC responses
+        query.flags &= ~dns.flags.RD
         expectedResponse = dns.message.make_response(query, our_payload=1042)
         expectedResponse.flags |= dns.flags.TC
 
@@ -200,6 +209,7 @@ class TestEDNSSelfGenerated(DNSDistTest):
         name = 'edns-options.rcode.edns-self.tests.powerdns.com.'
         ecso = clientsubnetoption.ClientSubnetOption('127.0.0.1', 24)
         query = dns.message.make_query(name, 'A', 'IN', use_edns=True, options=[ecso], payload=512, want_dnssec=True)
+        query.flags &= ~dns.flags.RD
         expectedResponse = dns.message.make_response(query, our_payload=1042)
         expectedResponse.set_rcode(dns.rcode.REFUSED)
 
@@ -212,6 +222,8 @@ class TestEDNSSelfGenerated(DNSDistTest):
 
         name = 'edns-options.tc.edns-self.tests.powerdns.com.'
         query = dns.message.make_query(name, 'A', 'IN', use_edns=True, options=[ecso], payload=512, want_dnssec=True)
+        # dnsdist sets RA = RD for TC responses
+        query.flags &= ~dns.flags.RD
         expectedResponse = dns.message.make_response(query, our_payload=1042)
         expectedResponse.flags |= dns.flags.TC
 
@@ -284,6 +296,7 @@ class TestEDNSSelfGeneratedDisabled(DNSDistTest):
         """
         name = 'edns-no-do.rcode.edns-self-disabled.tests.powerdns.com.'
         query = dns.message.make_query(name, 'A', 'IN', use_edns=True, payload=4096, want_dnssec=False)
+        query.flags &= ~dns.flags.RD
         expectedResponse = dns.message.make_response(query)
         expectedResponse.set_rcode(dns.rcode.REFUSED)
 
@@ -294,6 +307,8 @@ class TestEDNSSelfGeneratedDisabled(DNSDistTest):
 
         name = 'edns-no-do.tc.edns-self-disabled.tests.powerdns.com.'
         query = dns.message.make_query(name, 'A', 'IN', use_edns=True, payload=4096, want_dnssec=False)
+        # dnsdist sets RA = RD for TC responses
+        query.flags &= ~dns.flags.RD
         expectedResponse = dns.message.make_response(query)
         expectedResponse.flags |= dns.flags.TC
 
index 1abe575fd0991168e83c3ccbcc18d26311b05d9f..07741dba39d5a9237ae6174991d2fdd80cf4cf1c 100644 (file)
@@ -20,6 +20,7 @@ class TestRecordsCountOnlyOneAR(DNSDistTest):
         """
         name = 'refuseemptyar.recordscount.tests.powerdns.com.'
         query = dns.message.make_query(name, 'A', 'IN')
+        query.flags &= ~dns.flags.RD
         expectedResponse = dns.message.make_response(query)
         expectedResponse.set_rcode(dns.rcode.REFUSED)
 
@@ -62,6 +63,7 @@ class TestRecordsCountOnlyOneAR(DNSDistTest):
         """
         name = 'refusetwoar.recordscount.tests.powerdns.com.'
         query = dns.message.make_query(name, 'A', 'IN', use_edns=True)
+        query.flags &= ~dns.flags.RD
         query.additional.append(dns.rrset.from_text(name,
                                                     3600,
                                                     dns.rdataclass.IN,
@@ -92,6 +94,7 @@ class TestRecordsCountMoreThanOneLessThanFour(DNSDistTest):
         """
         name = 'refusenoan.recordscount.tests.powerdns.com.'
         query = dns.message.make_query(name, 'A', 'IN')
+        query.flags &= ~dns.flags.RD
         expectedResponse = dns.message.make_response(query)
         expectedResponse.set_rcode(dns.rcode.REFUSED)
 
@@ -136,6 +139,7 @@ class TestRecordsCountMoreThanOneLessThanFour(DNSDistTest):
         """
         name = 'refusefouran.recordscount.tests.powerdns.com.'
         query = dns.message.make_query(name, 'A', 'IN', use_edns=True)
+        query.flags &= ~dns.flags.RD
         rrset = dns.rrset.from_text_list(name,
                                          3600,
                                          dns.rdataclass.IN,
@@ -175,6 +179,7 @@ class TestRecordsCountNothingInNS(DNSDistTest):
                                     dns.rdatatype.NS,
                                     'ns.tests.powerdns.com.')
         query.authority.append(rrset)
+        query.flags &= ~dns.flags.RD
         expectedResponse = dns.message.make_response(query)
         expectedResponse.set_rcode(dns.rcode.REFUSED)
         expectedResponse.authority.append(rrset)
@@ -226,6 +231,7 @@ class TestRecordsCountNoOPTInAR(DNSDistTest):
         """
         name = 'refuseoptinar.recordscount.tests.powerdns.com.'
         query = dns.message.make_query(name, 'A', 'IN', use_edns=True)
+        query.flags &= ~dns.flags.RD
         expectedResponse = dns.message.make_response(query)
         expectedResponse.set_rcode(dns.rcode.REFUSED)
 
index 4cedcd32bf59294dfdc00fea6839cc146d168946..2ae03bf8668a178a0b846bdd8c9d5ab3b6102567 100644 (file)
@@ -6,6 +6,10 @@ class TestSpoofingSpoof(DNSDistTest):
 
     _config_template = """
     addAction(makeRule("spoofaction.spoofing.tests.powerdns.com."), SpoofAction("192.0.2.1", "2001:DB8::1"))
+    addAction(makeRule("spoofaction-aa.spoofing.tests.powerdns.com."), SpoofAction("192.0.2.1", "2001:DB8::1", {aa=true}))
+    addAction(makeRule("spoofaction-ad.spoofing.tests.powerdns.com."), SpoofAction("192.0.2.1", "2001:DB8::1", {ad=true}))
+    addAction(makeRule("spoofaction-ra.spoofing.tests.powerdns.com."), SpoofAction("192.0.2.1", "2001:DB8::1", {ra=true}))
+    addAction(makeRule("spoofaction-nora.spoofing.tests.powerdns.com."), SpoofAction("192.0.2.1", "2001:DB8::1", {ra=false}))
     addAction(makeRule("cnamespoofaction.spoofing.tests.powerdns.com."), SpoofCNAMEAction("cnameaction.spoofing.tests.powerdns.com."))
     addAction("multispoof.spoofing.tests.powerdns.com", SpoofAction({"192.0.2.1", "192.0.2.2", "2001:DB8::1", "2001:DB8::2"}))
     newServer{address="127.0.0.1:%s"}
@@ -169,6 +173,96 @@ class TestSpoofingSpoof(DNSDistTest):
             self.assertTrue(receivedResponse)
             self.assertEquals(expectedResponse, receivedResponse)
 
+    def testSpoofActionSetAA(self):
+        """
+        Spoofing: Spoof via Action, setting AA=1
+        """
+        name = 'spoofaction-aa.spoofing.tests.powerdns.com.'
+        query = dns.message.make_query(name, 'AAAA', 'IN')
+        # dnsdist set RA = RD for spoofed responses
+        query.flags &= ~dns.flags.RD
+        expectedResponse = dns.message.make_response(query)
+        expectedResponse.flags |= dns.flags.AA
+        rrset = dns.rrset.from_text(name,
+                                    60,
+                                    dns.rdataclass.IN,
+                                    dns.rdatatype.AAAA,
+                                    '2001:DB8::1')
+        expectedResponse.answer.append(rrset)
+
+        for method in ("sendUDPQuery", "sendTCPQuery"):
+            sender = getattr(self, method)
+            (_, receivedResponse) = sender(query, response=None, useQueue=False)
+            self.assertTrue(receivedResponse)
+            self.assertEquals(expectedResponse, receivedResponse)
+
+    def testSpoofActionSetAD(self):
+        """
+        Spoofing: Spoof via Action, setting AD=1
+        """
+        name = 'spoofaction-ad.spoofing.tests.powerdns.com.'
+        query = dns.message.make_query(name, 'AAAA', 'IN')
+        # dnsdist set RA = RD for spoofed responses
+        query.flags &= ~dns.flags.RD
+        expectedResponse = dns.message.make_response(query)
+        expectedResponse.flags |= dns.flags.AD
+        rrset = dns.rrset.from_text(name,
+                                    60,
+                                    dns.rdataclass.IN,
+                                    dns.rdatatype.AAAA,
+                                    '2001:DB8::1')
+        expectedResponse.answer.append(rrset)
+
+        for method in ("sendUDPQuery", "sendTCPQuery"):
+            sender = getattr(self, method)
+            (_, receivedResponse) = sender(query, response=None, useQueue=False)
+            self.assertTrue(receivedResponse)
+            self.assertEquals(expectedResponse, receivedResponse)
+
+    def testSpoofActionSetRA(self):
+        """
+        Spoofing: Spoof via Action, setting RA=1
+        """
+        name = 'spoofaction-ra.spoofing.tests.powerdns.com.'
+        query = dns.message.make_query(name, 'AAAA', 'IN')
+        # dnsdist set RA = RD for spoofed responses
+        query.flags &= ~dns.flags.RD
+        expectedResponse = dns.message.make_response(query)
+        expectedResponse.flags |= dns.flags.RA
+        rrset = dns.rrset.from_text(name,
+                                    60,
+                                    dns.rdataclass.IN,
+                                    dns.rdatatype.AAAA,
+                                    '2001:DB8::1')
+        expectedResponse.answer.append(rrset)
+
+        for method in ("sendUDPQuery", "sendTCPQuery"):
+            sender = getattr(self, method)
+            (_, receivedResponse) = sender(query, response=None, useQueue=False)
+            self.assertTrue(receivedResponse)
+            self.assertEquals(expectedResponse, receivedResponse)
+
+    def testSpoofActionSetNoRA(self):
+        """
+        Spoofing: Spoof via Action, setting RA=0
+        """
+        name = 'spoofaction-nora.spoofing.tests.powerdns.com.'
+        query = dns.message.make_query(name, 'AAAA', 'IN')
+        expectedResponse = dns.message.make_response(query)
+        expectedResponse.flags &= ~dns.flags.RA
+        rrset = dns.rrset.from_text(name,
+                                    60,
+                                    dns.rdataclass.IN,
+                                    dns.rdatatype.AAAA,
+                                    '2001:DB8::1')
+        expectedResponse.answer.append(rrset)
+
+        for method in ("sendUDPQuery", "sendTCPQuery"):
+            sender = getattr(self, method)
+            (_, receivedResponse) = sender(query, response=None, useQueue=False)
+            self.assertTrue(receivedResponse)
+            self.assertEquals(expectedResponse, receivedResponse)
+
 class TestSpoofingLuaSpoof(DNSDistTest):
 
     _config_template = """
index f975c9f403a03d30c6d1481ba207c8d857b86567..c0c8ebb675882aadf4dc1636092ab76f484344fc 100644 (file)
@@ -45,6 +45,7 @@ class TestTCPKeepAlive(DNSDistTest):
         """
         name = 'refused.tcpka.tests.powerdns.com.'
         query = dns.message.make_query(name, 'A', 'IN')
+        query.flags &= ~dns.flags.RD
         expectedResponse = dns.message.make_response(query)
         expectedResponse.set_rcode(dns.rcode.REFUSED)