]> git.ipfire.org Git - thirdparty/pdns.git/commitdiff
Merge pull request #13814 from wwijkander/wwijkander-patch-remote-doc
authorPeter van Dijk <peter.van.dijk@powerdns.com>
Thu, 7 Mar 2024 12:50:45 +0000 (13:50 +0100)
committerGitHub <noreply@github.com>
Thu, 7 Mar 2024 12:50:45 +0000 (13:50 +0100)
Update remote.rst to reflect that getAllDomains might be mandatory

49 files changed:
.clang-tidy.full
Docker-README.md
dockerdata/startup.py
docs/performance.rst
docs/secpoll.zone
docs/settings.rst
modules/remotebackend/test-remotebackend.cc
modules/remotebackend/testrunner.sh
modules/remotebackend/unittest.rb
modules/remotebackend/unittest_pipe.rb
modules/remotebackend/unittest_zeromq.rb
modules/remotebackend/unixconnector.cc
pdns/Makefile.am
pdns/dnsdistdist/Makefile.am
pdns/dnsdistdist/dnsdist-lua-hooks.hh
pdns/dnsdistdist/docs/upgrade_guide.rst
pdns/dnsdistdist/views.hh [new symlink]
pdns/dnsname.cc
pdns/dnsname.hh
pdns/fstrm_logger.cc
pdns/fstrm_logger.hh
pdns/recursordist/Makefile.am
pdns/recursordist/docs/changelog/4.8.rst
pdns/recursordist/docs/changelog/4.9.rst
pdns/recursordist/docs/changelog/5.0.rst
pdns/recursordist/docs/security-advisories/powerdns-advisory-2024-01.rst
pdns/recursordist/docs/upgrade.rst
pdns/recursordist/filterpo.cc
pdns/recursordist/filterpo.hh
pdns/recursordist/lua-recursor4.cc
pdns/recursordist/pdns_recursor.cc
pdns/recursordist/rec-lua-conf.cc
pdns/recursordist/rec-main.cc
pdns/recursordist/rec-zonetocache.cc
pdns/recursordist/rpzloader.cc
pdns/recursordist/rpzloader.hh
pdns/recursordist/syncres.cc
pdns/recursordist/syncres.hh
pdns/recursordist/test-filterpo_cc.cc
pdns/recursordist/test-syncres_cc3.cc
pdns/recursordist/views.hh [new symlink]
pdns/tcpiohandler.cc
pdns/validate.cc
pdns/validate.hh
pdns/views.hh [new file with mode: 0644]
pdns/xsk.cc
pdns/xsk.hh
regression-tests.dnsdist/test_DOH.py
regression-tests.recursor-dnssec/test_ServerNames.py

index 2b208a6996e64a3e5ec7fefd300b581f1f1eb433..85a3367533a0721e8099851f806221070c3c8483 100644 (file)
@@ -1,5 +1,5 @@
 ---
-Checks:          'clang-diagnostic-*,clang-analyzer-*,cppcoreguidelines-*,bugprone-*,concurrency-*,modernize-*,performance-*,portability-*,readability-*,-modernize-use-trailing-return-type,-readability-magic-numbers,-cppcoreguidelines-avoid-magic-numbers,-cppcoreguidelines-pro-type-union-access,-cppcoreguidelines-avoid-non-const-global-variables,-cppcoreguidelines-pro-type-vararg,-cppcoreguidelines-avoid-do-while'
+Checks:          'clang-diagnostic-*,clang-analyzer-*,cppcoreguidelines-*,bugprone-*,concurrency-*,modernize-*,performance-*,portability-*,readability-*,-modernize-use-trailing-return-type,-readability-magic-numbers,-cppcoreguidelines-avoid-magic-numbers,-cppcoreguidelines-pro-type-union-access,-cppcoreguidelines-avoid-non-const-global-variables,-cppcoreguidelines-pro-type-vararg,-cppcoreguidelines-avoid-do-while,-cppcoreguidelines-avoid-const-or-ref-data-members'
 WarningsAsErrors: ''
 HeaderFilterRegex: ''
 AnalyzeTemporaryDtors: false
index 3e01d514caad47628027fdc03a64b83ff8ddd3c3..7bd94ad4292f2b9abd9af791ddd61a20b8bb00bc 100644 (file)
@@ -19,15 +19,17 @@ Other data involved in the Docker build process can be found at https://github.c
 
 The images are ready to run with limited functionality.
 At container startup, the startup.py wrapper (from the dockerdata directory linked above) checks for `PDNS_RECURSOR_API_KEY` / `PDNS_AUTH_API_KEY` / `DNSDIST_API_KEY` environment variables for the product you are running.
-If such a variable is found, `/etc/powerdns-api.conf` or `/etc/dnsdist-api.conf` is written, enabling the webserver in all products, and the dnsdist console.
+If such a variable is found, `/etc/powerdns/recursor.d/_api.conf` / `/etc/powerdns/pdns.d/_api.conf` / `/etc/dnsdist/conf.d/_api.conf` is written, enabling the webserver in all products, and the dnsdist console.
 For the dnsdist console, make sure that your API key is in a format suitable for the console (use `makeKey()`).
 
 The default configs shipped in the image (see dockerdata above) parse all files in `/etc/powerdns/pdns.d` / `/etc/powerdns/recursor.d` / `/etc/dnsdist/conf.d`.
-The image also ships a symlink to the API config file inside those `.d` dirs.
 For Auth and Recursor, extra configuration can be passed on the command line, or via a volume mount into `/etc/powerdns` or the `.d` dir.
 For dnsdist, only the volume mount is applicable.
 
-If you want to volume mount a config, but also take the keys from the environment, please take care to include the same `X-api.conf` symlink in your `.d` directory.
+If you want to volume mount a config, but also take the keys from the environment, please take care to include the same `_api.conf` file in your `.d` directory.
+
+If you want to read the configuration for debugging purposes, you can run the containers with the `DEBUG_CONFIG` environment variable set to `'yes'`.
+This will print the full config on startup. Please keep in mind that this also includes credentials, therefore this setting should never be used in production environments.
 
 # Auth and databases
 
@@ -76,4 +78,4 @@ args:
 ```
 
 In the above example `/path/to/supervisord.conf` is the path where a configmap containing your supervisord configuration is mounted.
-Further details about `supervisord` and how to configure it can be found here: http://supervisord.org/configuration.html
\ No newline at end of file
+Further details about `supervisord` and how to configure it can be found here: http://supervisord.org/configuration.html
index e84055151a60aafc861a552a06ef5c26a3c600f8..114bc8d90275f96b6167bb88444384954f9f5bd1 100755 (executable)
@@ -45,13 +45,16 @@ setConsoleACL('0.0.0.0/0')
     templateroot = '/etc/dnsdist/templates.d'
     templatedestination = '/etc/dnsdist/conf.d'
 
+debug = os.getenv("DEBUG_CONFIG", 'no').lower() == 'yes'
+
 apikey = os.getenv(apienvvar)
 if apikey is not None:
     webserver_conf = jinja2.Template(apiconftemplate).render(apikey=apikey)
     conffile = os.path.join(templatedestination, '_api.conf')
     with open(conffile, 'w') as f:
         f.write(webserver_conf)
-    print("Created {} with content:\n{}\n".format(conffile, webserver_conf))
+    if debug:
+        print("Created {} with content:\n{}\n".format(conffile, webserver_conf))
 
 templates = os.getenv('TEMPLATE_FILES')
 if templates is not None:
@@ -63,6 +66,7 @@ if templates is not None:
         target = os.path.join(templatedestination, templateFile + '.conf')
         with open(target, 'w') as f:
             f.write(rendered)
-        print("Created {} with content:\n{}\n".format(target, rendered))
+        if debug:
+            print("Created {} with content:\n{}\n".format(target, rendered))
 
 os.execv(program, [program]+args+sys.argv[1:])
index d2730788339741d3a622cdf0a363289eb98059d0..82417d635873e40b86b90e31ab779c31f632d928 100644 (file)
@@ -236,7 +236,7 @@ Number of currently open TCP connections
 
 overload-drops
 ^^^^^^^^^^^^^^
-Number of questions dropped because backends overloaded
+Number of questions dropped because backends overloaded (backends are overloaded if they have more outstanding queries than the value of :ref:`setting-overload-queue-length`)
 
 .. _stat-packetcache-hit:
 
index 50ae02b78c1aea0b6bdd95a1c5bbad92039829a6..f189d819c592ebf560d595459058645c48e85aba 100644 (file)
@@ -1,4 +1,4 @@
-@       86400   IN  SOA pdns-public-ns1.powerdns.com. peter\.van\.dijk.powerdns.com. 2024021601 10800 3600 604800 10800
+@       86400   IN  SOA pdns-public-ns1.powerdns.com. peter\.van\.dijk.powerdns.com. 2024030700 10800 3600 604800 10800
 @       3600    IN  NS  pdns-public-ns1.powerdns.com.
 @       3600    IN  NS  pdns-public-ns2.powerdns.com.
 
@@ -358,6 +358,7 @@ recursor-4.8.3.security-status                          60 IN TXT "3 Upgrade now
 recursor-4.8.4.security-status                          60 IN TXT "3 Upgrade now, see https://doc.powerdns.com/recursor/security-advisories/powerdns-advisory-2024-01.html"
 recursor-4.8.5.security-status                          60 IN TXT "3 Upgrade now, see https://doc.powerdns.com/recursor/security-advisories/powerdns-advisory-2024-01.html"
 recursor-4.8.6.security-status                          60 IN TXT "1 OK"
+recursor-4.8.7.security-status                          60 IN TXT "1 OK"
 recursor-4.9.0-alpha1.security-status                   60 IN TXT "3 Unsupported pre-release (known vulnerabilities)"
 recursor-4.9.0-beta1.security-status                    60 IN TXT "3 Unsupported pre-release (known vulnerabilities)"
 recursor-4.9.0-rc1.security-status                      60 IN TXT "3 Unsupported pre-release (known vulnerabilities)"
@@ -365,6 +366,7 @@ recursor-4.9.0.security-status                          60 IN TXT "3 Upgrade now
 recursor-4.9.1.security-status                          60 IN TXT "3 Upgrade now, see https://doc.powerdns.com/recursor/security-advisories/powerdns-advisory-2024-01.html"
 recursor-4.9.2.security-status                          60 IN TXT "3 Upgrade now, see https://doc.powerdns.com/recursor/security-advisories/powerdns-advisory-2024-01.html"
 recursor-4.9.3.security-status                          60 IN TXT "1 OK"
+recursor-4.9.4.security-status                          60 IN TXT "1 OK"
 recursor-5.0.0-alpha1.security-status                   60 IN TXT "3 Unsupported pre-release (known vulnerabilities)"
 recursor-5.0.0-alpha2.security-status                   60 IN TXT "3 Unsupported pre-release (known vulnerabilities)"
 recursor-5.0.0-beta1.security-status                    60 IN TXT "3 Unsupported pre-release (known vulnerabilities)"
@@ -373,6 +375,7 @@ recursor-5.0.0-rc2.security-status                      60 IN TXT "3 Unsupported
 recursor-5.0.0.security-status                          60 IN TXT "3 Unsupported pre-release (known vulnerabilities)"
 recursor-5.0.1.security-status                          60 IN TXT "3 Upgrade now, see https://doc.powerdns.com/recursor/security-advisories/powerdns-advisory-2024-01.html"
 recursor-5.0.2.security-status                          60 IN TXT "1 OK"
+recursor-5.0.3.security-status                          60 IN TXT "1 OK"
 
 ; Recursor Debian
 recursor-3.6.2-2.debian.security-status                 60 IN TXT "3 Upgrade now, see https://doc.powerdns.com/3/security/powerdns-advisory-2015-01/ and https://doc.powerdns.com/3/security/powerdns-advisory-2016-02/"
index 17d61bebdf2a8d43d0a3820943691c9ea3945ca5..9d68d0c82af6cf01a33a434e008ee67977926394 100644 (file)
@@ -1369,7 +1369,8 @@ Secondary name servers.
 -  Default: 0 (disabled)
 
 If this many packets are waiting for database attention, answer any new
-questions strictly from the packet cache.
+questions strictly from the packet cache. Packets not in the cache will
+be dropped, and :ref:`_stat-overload-drops` will be incremented.
 
 .. _setting-prevent-self-notification:
 
index ebbb636cc8af5c3da509ceeafd9b7e1548246b59..4cebc2c16dd7bde733caa4ab1041b7779794640e 100644 (file)
@@ -194,7 +194,7 @@ BOOST_AUTO_TEST_CASE(test_method_getBeforeAndAfterNamesAbsolute)
   DNSName after;
   BOOST_TEST_MESSAGE("Testing getBeforeAndAfterNamesAbsolute method");
 
-  backendUnderTest->getBeforeAndAfterNamesAbsolute(-1, DNSName("middle.unit.test."), unhashed, before, after);
+  backendUnderTest->getBeforeAndAfterNamesAbsolute(1, DNSName("middle.unit.test."), unhashed, before, after);
   BOOST_CHECK_EQUAL(unhashed.toString(), "middle.");
   BOOST_CHECK_EQUAL(before.toString(), "begin.");
   BOOST_CHECK_EQUAL(after.toString(), "stop.");
index 86b85b220849d4e30f272ef9ad92070ca549cb5d..a33fc470cc21e236c44a2c4651f65880500ec6a8 100755 (executable)
@@ -225,27 +225,27 @@ case "$mode" in
   ;;
   remotebackend_unix.test)
     start_unix
-    run_test
+    run_test ; rv=$?
     stop_unix
   ;;
   remotebackend_http.test)
     start_web "http"
-    run_test
+    run_test ; rv=$?
     stop_web "http"
   ;;
   remotebackend_post.test)
     start_web "post"
-    run_test
+    run_test ; rv=$?
     stop_web "post"
   ;;
   remotebackend_json.test)
     start_web "json"
-    run_test
+    run_test ; rv=$?
     stop_web "json"
   ;;
   remotebackend_zeromq.test)
     start_zeromq
-    run_test
+    run_test ; rv=$?
     stop_zeromq
   ;;
   *)
@@ -254,4 +254,4 @@ case "$mode" in
   ;;
 esac
 
-exit $?
+exit $rv
index 4984c5da7943564857d32dd7ea8304af34c4d5be..b38e72e1d480fe5e830737f50a04a3313f5d2ba6 100644 (file)
@@ -286,7 +286,7 @@ class Handler
      [do_getdomaininfo({'name'=>'unit.test.'})]
    end
 
-   def do_getupdatedmasters()
+   def do_getupdatedmasters(args)
      [do_getdomaininfo({'name'=>'master.test.'})]
    end
 end
index 4c6a1557208148f7b6204e54629faba40fad9729..70ab58695f475fbef29b39038560cecc2616424e 100755 (executable)
@@ -24,10 +24,8 @@ begin
 
       if h.respond_to?(method.to_sym) == false
          res = false
-      elsif args.size > 0
-         res, log = h.send(method,args)
       else
-         res, log = h.send(method)
+         res, log = h.send(method,args)
       end
       puts ({:result => res, :log => log}).to_json
       f.puts "#{Time.now.to_f} [pipe]: #{({:result => res, :log => log}).to_json}"
index 7f1b82af7bee0cef29430d70d2ab5d5eb07e9e2c..bf5b0f4470cbb4f29240f2c54675af292d24c4b0 100755 (executable)
@@ -38,10 +38,8 @@ begin
 
       if h.respond_to?(method.to_sym) == false
          res = false
-      elsif args.size > 0
-         res, log = h.send(method,args)
       else
-         res, log = h.send(method)
+         res, log = h.send(method,args)
       end
       socket.send_string ({:result => res, :log => log}).to_json + "\n" , 0
       f.puts "#{Time.now.to_f} [zmq]: #{({:result => res, :log => log}).to_json}"
index 82447c617264edc6d7c2e85d50a2f34b85fa2733..8b4d5064eccfc0154a4e5d43f896bcbee736e804 100644 (file)
@@ -49,11 +49,6 @@ UnixsocketConnector::UnixsocketConnector(std::map<std::string, std::string> opti
 UnixsocketConnector::~UnixsocketConnector()
 {
   if (this->connected) {
-    try {
-      g_log << Logger::Info << "closing socket connection" << endl;
-    }
-    catch (...) {
-    }
     close(fd);
   }
 }
index 263bf3cd01867378de6ee64ece90125da43f8ff0..bee1d58ef06d9ef2bf07f4dd03bf6c1ab84a515a 100644 (file)
@@ -287,6 +287,7 @@ pdns_server_SOURCES = \
        utility.hh \
        uuid-utils.hh uuid-utils.cc \
        version.cc version.hh \
+       views.hh \
        webserver.cc webserver.hh \
        ws-api.cc ws-api.hh \
        ws-auth.cc ws-auth.hh \
index 68ab167dcfbd9ba951713ec59e982191c8eb3c43..faaeefd379666d6bbb0a9e2e709761eb31e20f54 100644 (file)
@@ -256,6 +256,7 @@ dnsdist_SOURCES = \
        tcpiohandler.cc tcpiohandler.hh \
        threadname.hh threadname.cc \
        uuid-utils.hh uuid-utils.cc \
+       views.hh \
        xpf.cc xpf.hh \
        xsk.cc xsk.hh
 
index 03f1ac4ec41b653553b505f1809b15857634c68d..11a9084883ee8a74585acc611bb10bdb9ea3fae8 100644 (file)
@@ -29,7 +29,7 @@ namespace dnsdist::lua::hooks
 {
 using MaintenanceCallback = std::function<void()>;
 void runMaintenanceHooks(const LuaContext& context);
-void addMaintenanceCallback(MaintenanceCallback callback);
+void addMaintenanceCallback(const LuaContext& context, MaintenanceCallback callback);
 void clearMaintenanceHooks();
 void setupLuaHooks(LuaContext& luaCtx);
 }
index b48887bae61bcc23d66ddd1e3916be9202d704e9..c4fb3ba763d6b98f04415550e4713c6da0f844b8 100644 (file)
@@ -9,7 +9,7 @@ but is now deprecated, disabled by default (see ``--with-h2o`` to enable it back
 (see https://github.com/h2o/h2o/issues/3230). See the ``library`` parameter on the :func:`addDOHLocal` directive for more information on how to select
 the library used when dnsdist is built with support for both ``h2o`` and ``nghttp2``. The default is now ``nghttp2`` whenever possible.
 Note that ``nghttp2`` only supports HTTP/2, and not HTTP/1, while ``h2o`` supported both. This is not an issue for actual DNS over HTTPS clients that
-support HTTP/2, but might be one in setups running dnsdist behind a reverse-proxy that does not support HTTP/1. See :doc:`guides/dns-over-https` for some work-around.
+support HTTP/2, but might be one in setups running dnsdist behind a reverse-proxy that does not support HTTP/2. See :doc:`guides/dns-over-https` for some work-around.
 
 SNMP support is no longer enabled by default during ``configure``, requiring ``--with-net-snmp`` to be built.
 
diff --git a/pdns/dnsdistdist/views.hh b/pdns/dnsdistdist/views.hh
new file mode 120000 (symlink)
index 0000000..2213b7d
--- /dev/null
@@ -0,0 +1 @@
+../views.hh
\ No newline at end of file
index c5728cc2cea245054dc21c5eb0ffdd4c38c9f59d..bbac4ffcb6a4a46fcbaa7c72d024abc7b12b2f29 100644 (file)
@@ -124,7 +124,7 @@ static void checkLabelLength(uint8_t length)
 }
 
 // this parses a DNS name until a compression pointer is found
-size_t DNSName::parsePacketUncompressed(const UnsignedCharView& view, size_t pos, bool uncompress)
+size_t DNSName::parsePacketUncompressed(const pdns::views::UnsignedCharView& view, size_t pos, bool uncompress)
 {
   const size_t initialPos = pos;
   size_t totalLength = 0;
@@ -189,7 +189,7 @@ void DNSName::packetParser(const char* qpos, size_t len, size_t offset, bool unc
   }
   unsigned char labellen{0};
 
-  UnsignedCharView view(qpos, len);
+  pdns::views::UnsignedCharView view(qpos, len);
   auto pos = parsePacketUncompressed(view, offset, uncompress);
 
   labellen = view.at(pos);
index e7a9f4b4eafd1149386b5a7019a649c16235cca1..0d9112b2ad2183649e5180be48d69edc8a0d6417 100644 (file)
@@ -57,6 +57,7 @@ inline unsigned char dns_tolower(unsigned char c)
 }
 
 #include "burtle.hh"
+#include "views.hh"
 
 // #include "dns.hh"
 // #include "logger.hh"
@@ -216,28 +217,8 @@ public:
 private:
   string_t d_storage;
 
-  class UnsignedCharView
-  {
-  public:
-    UnsignedCharView(const char* data_, size_t size_): view(data_, size_)
-    {
-    }
-    const unsigned char& at(std::string_view::size_type pos) const
-    {
-      return reinterpret_cast<const unsigned char&>(view.at(pos));
-    }
-
-    size_t size() const
-    {
-      return view.size();
-    }
-
-  private:
-    std::string_view view;
-  };
-
   void packetParser(const char* qpos, size_t len, size_t offset, bool uncompress, uint16_t* qtype, uint16_t* qclass, unsigned int* consumed, int depth, uint16_t minOffset);
-  size_t parsePacketUncompressed(const UnsignedCharView& view, size_t position, bool uncompress);
+  size_t parsePacketUncompressed(const pdns::views::UnsignedCharView& view, size_t position, bool uncompress);
   static void appendEscapedLabel(std::string& appendTo, const char* orig, size_t len);
   static std::string unescapeLabel(const std::string& orig);
   static void throwSafeRangeError(const std::string& msg, const char* buf, size_t length);
index 0de4632036ff60ad5a8f3c81b9d857db5350f085..7c839e7b19d61e71e24298d89d79555e7b566940 100644 (file)
 #include "dolog.hh"
 #endif
 
-#define DNSTAP_CONTENT_TYPE            "protobuf:dnstap.Dnstap"
-
 #ifdef HAVE_FSTRM
 
-FrameStreamLogger::FrameStreamLogger(const int family, const std::string& address, bool connect,
-    const std::unordered_map<string,unsigned>& options): d_family(family), d_address(address)
-{
-  fstrm_res res;
+static const std::string DNSTAP_CONTENT_TYPE = "protobuf:dnstap.Dnstap";
 
+FrameStreamLogger::FrameStreamLogger(const int family, std::string address, bool connect, const std::unordered_map<string, unsigned>& options) :
+  d_family(family), d_address(std::move(address))
+{
   try {
     d_fwopt = fstrm_writer_options_init();
-    if (!d_fwopt) {
+    if (d_fwopt == nullptr) {
       throw std::runtime_error("FrameStreamLogger: fstrm_writer_options_init failed.");
     }
 
-    res = fstrm_writer_options_add_content_type(d_fwopt, DNSTAP_CONTENT_TYPE, sizeof(DNSTAP_CONTENT_TYPE) - 1);
+    auto res = fstrm_writer_options_add_content_type(d_fwopt, DNSTAP_CONTENT_TYPE.c_str(), DNSTAP_CONTENT_TYPE.size());
     if (res != fstrm_res_success) {
       throw std::runtime_error("FrameStreamLogger: fstrm_writer_options_add_content_type failed: " + std::to_string(res));
     }
 
     if (d_family == AF_UNIX) {
-      struct sockaddr_un local;
-      if (makeUNsockaddr(d_address, &local)) {
+      struct sockaddr_un local
+      {
+      };
+      if (makeUNsockaddr(d_address, &local) != 0) {
         throw std::runtime_error("FrameStreamLogger: Unable to use '" + d_address + "', it is not a valid UNIX socket path.");
       }
 
       d_uwopt = fstrm_unix_writer_options_init();
-      if (!d_uwopt) {
+      if (d_uwopt == nullptr) {
         throw std::runtime_error("FrameStreamLogger: fstrm_unix_writer_options_init failed.");
       }
 
@@ -46,37 +46,40 @@ FrameStreamLogger::FrameStreamLogger(const int family, const std::string& addres
       fstrm_unix_writer_options_set_socket_path(d_uwopt, d_address.c_str());
 
       d_writer = fstrm_unix_writer_init(d_uwopt, d_fwopt);
-      if (!d_writer) {
+      if (d_writer == nullptr) {
         throw std::runtime_error("FrameStreamLogger: fstrm_unix_writer_init() failed.");
       }
-  #ifdef HAVE_FSTRM_TCP_WRITER_INIT
-    } else if (family == AF_INET) {
+#ifdef HAVE_FSTRM_TCP_WRITER_INIT
+    }
+    else if (family == AF_INET || family == AF_INET6) {
       d_twopt = fstrm_tcp_writer_options_init();
-      if (!d_twopt) {
+      if (d_twopt == nullptr) {
         throw std::runtime_error("FrameStreamLogger: fstrm_tcp_writer_options_init failed.");
       }
 
       try {
-        ComboAddress ca(d_address);
+        ComboAddress inetAddress(d_address);
 
         // void return, no error checking.
-        fstrm_tcp_writer_options_set_socket_address(d_twopt, ca.toString().c_str());
-        fstrm_tcp_writer_options_set_socket_port(d_twopt, std::to_string(ca.getPort()).c_str());
-      } catch (PDNSException &e) {
+        fstrm_tcp_writer_options_set_socket_address(d_twopt, inetAddress.toString().c_str());
+        fstrm_tcp_writer_options_set_socket_port(d_twopt, std::to_string(inetAddress.getPort()).c_str());
+      }
+      catch (PDNSException& e) {
         throw std::runtime_error("FrameStreamLogger: Unable to use '" + d_address + "': " + e.reason);
       }
 
       d_writer = fstrm_tcp_writer_init(d_twopt, d_fwopt);
-      if (!d_writer) {
+      if (d_writer == nullptr) {
         throw std::runtime_error("FrameStreamLogger: fstrm_tcp_writer_init() failed.");
       }
-  #endif
-    } else {
+#endif
+    }
+    else {
       throw std::runtime_error("FrameStreamLogger: family " + std::to_string(family) + " not supported");
     }
 
     d_iothropt = fstrm_iothr_options_init();
-    if (!d_iothropt) {
+    if (d_iothropt == nullptr) {
       throw std::runtime_error("FrameStreamLogger: fstrm_iothr_options_init() failed.");
     }
 
@@ -85,39 +88,40 @@ FrameStreamLogger::FrameStreamLogger(const int family, const std::string& addres
       throw std::runtime_error("FrameStreamLogger: fstrm_iothr_options_set_queue_model failed: " + std::to_string(res));
     }
 
-    const struct {
+    struct setters
+    {
       const std::string name;
-      fstrm_res (*function)(struct fstrm_iothr_options *, const unsigned int);
-    } list[] = {
-      { "bufferHint", fstrm_iothr_options_set_buffer_hint },
-      { "flushTimeout", fstrm_iothr_options_set_flush_timeout },
-      { "inputQueueSize", fstrm_iothr_options_set_input_queue_size },
-      { "outputQueueSize", fstrm_iothr_options_set_output_queue_size },
-      { "queueNotifyThreshold", fstrm_iothr_options_set_queue_notify_threshold },
-      { "setReopenInterval", fstrm_iothr_options_set_reopen_interval }
+      fstrm_res (*function)(struct fstrm_iothr_options*, const unsigned int);
     };
-
-    for (const auto& i : list) {
-      if (options.find(i.name) != options.end() && options.at(i.name)) {
-        fstrm_res r = i.function(d_iothropt, options.at(i.name));
-        if (r != fstrm_res_success) {
-          throw std::runtime_error("FrameStreamLogger: setting " + string(i.name) + " failed: " + std::to_string(r));
+    const std::array<struct setters, 6> list = {{{"bufferHint", fstrm_iothr_options_set_buffer_hint},
+                                                 {"flushTimeout", fstrm_iothr_options_set_flush_timeout},
+                                                 {"inputQueueSize", fstrm_iothr_options_set_input_queue_size},
+                                                 {"outputQueueSize", fstrm_iothr_options_set_output_queue_size},
+                                                 {"queueNotifyThreshold", fstrm_iothr_options_set_queue_notify_threshold},
+                                                 {"setReopenInterval", fstrm_iothr_options_set_reopen_interval}}};
+
+    for (const auto& entry : list) {
+      if (auto option = options.find(entry.name); option != options.end() && option->second != 0) {
+        auto result = entry.function(d_iothropt, option->second);
+        if (result != fstrm_res_success) {
+          throw std::runtime_error("FrameStreamLogger: setting " + string(entry.name) + " failed: " + std::to_string(result));
         }
       }
     }
 
     if (connect) {
       d_iothr = fstrm_iothr_init(d_iothropt, &d_writer);
-      if (!d_iothr) {
+      if (d_iothr == nullptr) {
         throw std::runtime_error("FrameStreamLogger: fstrm_iothr_init() failed.");
       }
 
       d_ioqueue = fstrm_iothr_get_input_queue(d_iothr);
-      if (!d_ioqueue) {
+      if (d_ioqueue == nullptr) {
         throw std::runtime_error("FrameStreamLogger: fstrm_iothr_get_input_queue() failed.");
       }
     }
-  } catch (std::runtime_error &e) {
+  }
+  catch (std::runtime_error& e) {
     this->cleanup();
     throw;
   }
@@ -160,34 +164,34 @@ FrameStreamLogger::~FrameStreamLogger()
 
 RemoteLoggerInterface::Result FrameStreamLogger::queueData(const std::string& data)
 {
-  if (!d_ioqueue || !d_iothr) {
+  if ((d_ioqueue == nullptr) || d_iothr == nullptr) {
     ++d_permanentFailures;
     return Result::OtherError;
   }
-  uint8_t *frame = (uint8_t*)malloc(data.length());
-  if (!frame) {
+  uint8_t* frame = (uint8_t*)malloc(data.length()); // NOLINT: it's the API
+  if (frame == nullptr) {
     ++d_queueFullDrops; // XXX separate count?
     return Result::TooLarge;
   }
   memcpy(frame, data.c_str(), data.length());
 
-  fstrm_res res;
-  res = fstrm_iothr_submit(d_iothr, d_ioqueue, frame, data.length(), fstrm_free_wrapper, nullptr);
+  auto res = fstrm_iothr_submit(d_iothr, d_ioqueue, frame, data.length(), fstrm_free_wrapper, nullptr);
 
   if (res == fstrm_res_success) {
     // Frame successfully queued.
     ++d_framesSent;
+    // do not call free here
     return Result::Queued;
-  } else if (res == fstrm_res_again) {
-    free(frame);
+  }
+  if (res == fstrm_res_again) {
+    free(frame); // NOLINT: it's the API
     ++d_queueFullDrops;
     return Result::PipeFull;
- } else {
-    // Permanent failure.
-    free(frame);
-    ++d_permanentFailures;
-    return Result::OtherError;
   }
+  // Permanent failure.
+  free(frame); // NOLINT: it's the API
+  ++d_permanentFailures;
+  return Result::OtherError;
 }
 
 #endif /* HAVE_FSTRM */
index 3eafb3d997f8a9a6617cc694cb3535d310308339..711c65a17189388daa39006b9b1f55a946840c42 100644 (file)
 #include <fstrm/tcp_writer.h>
 #endif
 
-class FrameStreamLogger : public RemoteLoggerInterface, boost::noncopyable
+class FrameStreamLogger : public RemoteLoggerInterface
 {
 public:
-  FrameStreamLogger(int family, const std::string& address, bool connect, const std::unordered_map<string,unsigned>& options = std::unordered_map<string,unsigned>());
-  ~FrameStreamLogger();
+  FrameStreamLogger(int family, std::string address, bool connect, const std::unordered_map<string, unsigned>& options = std::unordered_map<string, unsigned>());
+  FrameStreamLogger(const FrameStreamLogger&) = delete;
+  FrameStreamLogger(FrameStreamLogger&&) = delete;
+  FrameStreamLogger& operator=(const FrameStreamLogger&) = delete;
+  FrameStreamLogger& operator=(FrameStreamLogger&&) = delete;
+  ~FrameStreamLogger() override;
   [[nodiscard]] RemoteLoggerInterface::Result queueData(const std::string& data) override;
 
   [[nodiscard]] std::string address() const override
@@ -49,7 +53,7 @@ public:
   {
     return "dnstap";
   }
-  
+
   [[nodiscard]] std::string toString() override
   {
     return "FrameStreamLogger to " + d_address + " (" + std::to_string(d_framesSent) + " frames sent, " + std::to_string(d_queueFullDrops) + " dropped, " + std::to_string(d_permanentFailures) + " permanent failures)";
@@ -60,23 +64,21 @@ public:
     return Stats{.d_queued = d_framesSent,
                  .d_pipeFull = d_queueFullDrops,
                  .d_tooLarge = 0,
-                 .d_otherError = d_permanentFailures
-    };
+                 .d_otherError = d_permanentFailures};
   }
 
 private:
-
   const int d_family;
   const std::string d_address;
-  struct fstrm_iothr_queue *d_ioqueue{nullptr};
-  struct fstrm_writer_options *d_fwopt{nullptr};
-  struct fstrm_unix_writer_options *d_uwopt{nullptr};
+  struct fstrm_iothr_queued_ioqueue{nullptr};
+  struct fstrm_writer_optionsd_fwopt{nullptr};
+  struct fstrm_unix_writer_optionsd_uwopt{nullptr};
 #ifdef HAVE_FSTRM_TCP_WRITER_INIT
-  struct fstrm_tcp_writer_options *d_twopt{nullptr};
+  struct fstrm_tcp_writer_optionsd_twopt{nullptr};
 #endif
-  struct fstrm_writer *d_writer{nullptr};
-  struct fstrm_iothr_options *d_iothropt{nullptr};
-  struct fstrm_iothr *d_iothr{nullptr};
+  struct fstrm_writerd_writer{nullptr};
+  struct fstrm_iothr_optionsd_iothropt{nullptr};
+  struct fstrm_iothrd_iothr{nullptr};
   std::atomic<uint64_t> d_framesSent{0};
   std::atomic<uint64_t> d_queueFullDrops{0};
   std::atomic<uint64_t> d_permanentFailures{0};
@@ -85,5 +87,11 @@ private:
 };
 
 #else
-class FrameStreamLogger : public RemoteLoggerInterface, boost::noncopyable {};
+class FrameStreamLogger : public RemoteLoggerInterface
+{
+  FrameStreamLogger(const FrameStreamLogger&) = delete;
+  FrameStreamLogger(FrameStreamLogger&&) = delete;
+  FrameStreamLogger& operator=(const FrameStreamLogger&) = delete;
+  FrameStreamLogger& operator=(FrameStreamLogger&&) = delete;
+};
 #endif /* HAVE_FSTRM */
index 2819adac31c780275586b445e09754b7f7648a8d..9bb70569495e2411ad1b7274a6c5e12fda28abbe 100644 (file)
@@ -218,6 +218,7 @@ pdns_recursor_SOURCES = \
        uuid-utils.hh uuid-utils.cc \
        validate.cc validate.hh validate-recursor.cc validate-recursor.hh \
        version.cc version.hh \
+       views.hh \
        webserver.cc webserver.hh \
        ws-api.cc ws-api.hh \
        ws-recursor.cc ws-recursor.hh \
index dfc66fd6342b6406c11b4bbd4282979eba78160f..a09aab1680c86dc6cd840450a0a9f6af47403bdc 100644 (file)
@@ -1,5 +1,35 @@
 Changelogs for 4.8.X
 ====================
+.. changelog::
+  :version: 4.8.7
+  :released: 7th of March 2024
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 13797
+    :tickets: 13353
+
+    If serving stale, wipe CNAME records from cache when we get a NODATA negative response for them.
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 13799
+
+    Fix the zoneToCache regression introduced by SA 2024-01.
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 13854
+    :tickets: 13847
+
+    Fix gathering of denial of existence proof for wildcard-expanded names.
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 13796
+    :tickets: 13387
+
+    Update new b-root-server.net addresses in built-in hints.
 
 .. changelog::
   :version: 4.8.6
index 40d819f987f5e135d886d9ec35c14127f5ac1f4f..ac93382f9015ec73d9660eee3a6ffc36ca6446b0 100644 (file)
@@ -1,6 +1,37 @@
 Changelogs for 4.9.X
 ====================
 
+.. changelog::
+  :version: 4.9.4
+  :released: 7th of March 2024
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 13853
+
+    Fix gathering of denial of existence proof for wildcard-expanded names.
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 13795
+    :tickets: 13788
+
+    Fix the zoneToCache regression introduced by SA 2024-01.
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 13793
+    :tickets: 13387, 12897
+
+    Update new b-root-server.net addresses in built-in hints.
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 13792
+    :tickets: 13543
+
+    A single NSEC3 record covering everything is a special case.
+
 .. changelog::
   :version: 4.9.3
   :released: 13th of February 2024
index 000d37e9a5e6e35d50434b694f1890b94968056e..7b495c8a658d15b0aa324a54538161585a3c6bf2 100644 (file)
@@ -3,6 +3,38 @@ Changelogs for 5.0.X
 
 Before upgrading, it is advised to read the :doc:`../upgrade`.
 
+.. changelog::
+  :version: 5.0.3
+  :released: 7th of March 2024
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 13845
+    :tickets: 13824
+
+    Log if a DNSSEC related limit was hit if log_bogus is set.
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 13846
+    :tickets: 13830
+
+    Reduce RPZ memory usage by not keeping the initially loaded RPZs in memory.
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 13852
+    :tickets: 13847
+
+    Fix gathering of denial of existence proof for wildcard-expanded names.
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 13791
+    :tickets: 13788
+
+    Fix the zoneToCache regression introduced by SA 2024-01.
+
 .. changelog::
   :version: 5.0.2
   :released: 13th of February 2024
index 07a53e25e22796fdd51ab555683b8a4981715317..ea105495d8811be08431d57ab9ffdc917f8471be 100644 (file)
@@ -30,4 +30,5 @@ The remedies are one of:
 We would like to thank Elias Heftrig, Haya Schulmann, Niklas Vogel, and Michael Waidner from the
 German National Research Center for Applied Cybersecurity ATHENE for bringing this issue to the
 attention of the DNS community and especially Niklas Vogel for his assistance in validating the
-patches.
+patches. We would also like to thank Petr Špaček from ISC for discovering and responsibly disclosing
+CVE-2023-50868.
index da559a483fe9bdd365e1b2d28b97722d09152f96..8b5164e1d931070ae42d55a4a93b0f47ef8c7697 100644 (file)
@@ -4,7 +4,14 @@ Upgrade Guide
 Before upgrading, it is advised to read the :doc:`changelog/index`.
 When upgrading several versions, please read **all** notes applying to the upgrade.
 
-5.0.1 to 5.0.2 and master, 4.9.2 to 4.9.3 and 4.8.5 to 4.8.6
+5.0.2 to 5.0.3 and master, 4.9.3 to 4.9.4 and 4.8.6 to 4.8.7
+------------------------------------------------------------
+
+Known Issue Solved
+^^^^^^^^^^^^^^^^^^
+The DNSSEC validation issue with the :func:`zoneToCache` function has been resolved and workarounds can be removed.
+
+5.0.1 to 5.0.2, 4.9.2 to 4.9.3 and 4.8.5 to 4.8.6
 ------------------------------------------------------------
 
 Known Issues
index f73b95d54d3437e756d4df439531b99b92bfcb48..bfcbbc71e6a88a89a3cb51ffe570f5a56db5ae45 100644 (file)
@@ -50,8 +50,8 @@ bool DNSFilterEngine::Zone::findExactQNamePolicy(const DNSName& qname, DNSFilter
 bool DNSFilterEngine::Zone::findExactNSPolicy(const DNSName& qname, DNSFilterEngine::Policy& pol) const
 {
   if (findExactNamedPolicy(d_propolName, qname, pol)) {
-    pol.d_trigger = qname;
-    pol.d_trigger.appendRawLabel(rpzNSDnameName);
+    // hitdata set by findExactNamedPolicy
+    pol.d_hitdata->d_trigger.appendRawLabel(rpzNSDnameName);
     return true;
   }
   return false;
@@ -61,9 +61,8 @@ bool DNSFilterEngine::Zone::findNSIPPolicy(const ComboAddress& addr, DNSFilterEn
 {
   if (const auto* fnd = d_propolNSAddr.lookup(addr)) {
     pol = fnd->second;
-    pol.d_trigger = Zone::maskToRPZ(fnd->first);
-    pol.d_trigger.appendRawLabel(rpzNSIPName);
-    pol.d_hit = addr.toString();
+    pol.setHitData(Zone::maskToRPZ(fnd->first), addr.toString());
+    pol.d_hitdata->d_trigger.appendRawLabel(rpzNSIPName);
     return true;
   }
   return false;
@@ -73,9 +72,8 @@ bool DNSFilterEngine::Zone::findResponsePolicy(const ComboAddress& addr, DNSFilt
 {
   if (const auto* fnd = d_postpolAddr.lookup(addr)) {
     pol = fnd->second;
-    pol.d_trigger = Zone::maskToRPZ(fnd->first);
-    pol.d_trigger.appendRawLabel(rpzIPName);
-    pol.d_hit = addr.toString();
+    pol.setHitData(Zone::maskToRPZ(fnd->first), addr.toString());
+    pol.d_hitdata->d_trigger.appendRawLabel(rpzIPName);
     return true;
   }
   return false;
@@ -85,9 +83,8 @@ bool DNSFilterEngine::Zone::findClientPolicy(const ComboAddress& addr, DNSFilter
 {
   if (const auto* fnd = d_qpolAddr.lookup(addr)) {
     pol = fnd->second;
-    pol.d_trigger = Zone::maskToRPZ(fnd->first);
-    pol.d_trigger.appendRawLabel(rpzClientIPName);
-    pol.d_hit = addr.toString();
+    pol.setHitData(Zone::maskToRPZ(fnd->first), addr.toString());
+    pol.d_hitdata->d_trigger.appendRawLabel(rpzClientIPName);
     return true;
   }
   return false;
@@ -119,8 +116,7 @@ bool DNSFilterEngine::Zone::findNamedPolicy(const std::unordered_map<DNSName, DN
     iter = polmap.find(g_wildcarddnsname + sub);
     if (iter != polmap.end()) {
       pol = iter->second;
-      pol.d_trigger = iter->first;
-      pol.d_hit = qname.toStringNoDot();
+      pol.setHitData(iter->first, qname.toStringNoDot());
       return true;
     }
   }
@@ -136,8 +132,7 @@ bool DNSFilterEngine::Zone::findExactNamedPolicy(const std::unordered_map<DNSNam
   const auto iter = polmap.find(qname);
   if (iter != polmap.end()) {
     pol = iter->second;
-    pol.d_trigger = qname;
-    pol.d_hit = qname.toStringNoDot();
+    pol.setHitData(qname, qname.toStringNoDot());
     return true;
   }
 
@@ -196,7 +191,7 @@ bool DNSFilterEngine::getProcessingPolicy(const DNSName& qname, const std::unord
       if (zone->findExactNSPolicy(wildcard, pol)) {
         // cerr<<"Had a hit on the nameserver ("<<qname<<") used to process the query"<<endl;
         // Hit is not the wildcard passed to findExactQNamePolicy but the actual qname!
-        pol.d_hit = qname.toStringNoDot();
+        pol.d_hitdata->d_hit = qname.toStringNoDot();
         return true;
       }
     }
@@ -304,7 +299,7 @@ bool DNSFilterEngine::getQueryPolicy(const DNSName& qname, const std::unordered_
       if (zone->findExactQNamePolicy(wildcard, pol)) {
         // cerr<<"Had a hit on the name of the query"<<endl;
         // Hit is not the wildcard passed to findExactQNamePolicy but the actual qname!
-        pol.d_hit = qname.toStringNoDot();
+        pol.d_hitdata->d_hit = qname.toStringNoDot();
         return true;
       }
     }
@@ -371,6 +366,17 @@ void DNSFilterEngine::assureZones(size_t zone)
   }
 }
 
+static void addCustom(DNSFilterEngine::Policy& existingPol, const DNSFilterEngine::Policy& pol)
+{
+  if (!existingPol.d_custom) {
+    existingPol.d_custom = make_unique<DNSFilterEngine::Policy::CustomData>();
+  }
+  if (pol.d_custom) {
+    existingPol.d_custom->reserve(existingPol.d_custom->size() + pol.d_custom->size());
+    std::move(pol.d_custom->begin(), pol.d_custom->end(), std::back_inserter(*existingPol.d_custom));
+  }
+}
+
 void DNSFilterEngine::Zone::addNameTrigger(std::unordered_map<DNSName, Policy>& map, const DNSName& n, Policy&& pol, bool ignoreDuplicate, PolicyType ptype)
 {
   auto iter = map.find(n);
@@ -392,9 +398,7 @@ void DNSFilterEngine::Zone::addNameTrigger(std::unordered_map<DNSName, Policy>&
       throw std::runtime_error("Adding a " + getTypeToString(ptype) + "-based filter policy of kind " + getKindToString(pol.d_kind) + " but a policy of kind " + getKindToString(existingPol.d_kind) + " already exists for for the following name: " + n.toLogString());
     }
 
-    existingPol.d_custom.reserve(existingPol.d_custom.size() + pol.d_custom.size());
-
-    std::move(pol.d_custom.begin(), pol.d_custom.end(), std::back_inserter(existingPol.d_custom));
+    addCustom(existingPol, pol);
   }
   else {
     auto& qpol = map.insert({n, std::move(pol)}).first->second;
@@ -424,9 +428,7 @@ void DNSFilterEngine::Zone::addNetmaskTrigger(NetmaskTree<Policy>& nmt, const Ne
       throw std::runtime_error("Adding a " + getTypeToString(ptype) + "-based filter policy of kind " + getKindToString(pol.d_kind) + " but a policy of kind " + getKindToString(existingPol.d_kind) + " already exists for the following netmask: " + netmask.toString());
     }
 
-    existingPol.d_custom.reserve(existingPol.d_custom.size() + pol.d_custom.size());
-
-    std::move(pol.d_custom.begin(), pol.d_custom.end(), std::back_inserter(existingPol.d_custom));
+    addCustom(existingPol, pol);
   }
   else {
     pol.d_zoneData = d_zoneData;
@@ -451,18 +453,20 @@ bool DNSFilterEngine::Zone::rmNameTrigger(std::unordered_map<DNSName, Policy>& m
   /* for custom types, we might have more than one type,
      and then we need to remove only the right ones. */
   bool result = false;
-  for (const auto& toRemove : pol.d_custom) {
-    for (auto it = existing.d_custom.begin(); it != existing.d_custom.end(); ++it) {
-      if (**it == *toRemove) {
-        existing.d_custom.erase(it);
-        result = true;
-        break;
+  if (pol.d_custom && existing.d_custom) {
+    for (const auto& toRemove : *pol.d_custom) {
+      for (auto it = existing.d_custom->begin(); it != existing.d_custom->end(); ++it) {
+        if (**it == *toRemove) {
+          existing.d_custom->erase(it);
+          result = true;
+          break;
+        }
       }
     }
   }
 
   // No records left for this trigger?
-  if (existing.d_custom.empty()) {
+  if (existing.customRecordsSize() == 0) {
     map.erase(found);
     return true;
   }
@@ -487,18 +491,20 @@ bool DNSFilterEngine::Zone::rmNetmaskTrigger(NetmaskTree<Policy>& nmt, const Net
      and then we need to remove only the right ones. */
 
   bool result = false;
-  for (const auto& toRemove : pol.d_custom) {
-    for (auto it = existing.d_custom.begin(); it != existing.d_custom.end(); ++it) {
-      if (**it == *toRemove) {
-        existing.d_custom.erase(it);
-        result = true;
-        break;
+  if (pol.d_custom && existing.d_custom) {
+    for (const auto& toRemove : *pol.d_custom) {
+      for (auto it = existing.d_custom->begin(); it != existing.d_custom->end(); ++it) {
+        if (**it == *toRemove) {
+          existing.d_custom->erase(it);
+          result = true;
+          break;
+        }
       }
     }
   }
 
   // No records left for this trigger?
-  if (existing.d_custom.empty()) {
+  if (existing.customRecordsSize() == 0) {
     nmt.erase(netmask);
     return true;
   }
@@ -558,13 +564,13 @@ bool DNSFilterEngine::Zone::rmNSIPTrigger(const Netmask& netmask, const Policy&
 
 std::string DNSFilterEngine::Policy::getLogString() const
 {
-  return ": RPZ Hit; PolicyName=" + getName() + "; Trigger=" + d_trigger.toLogString() + "; Hit=" + d_hit + "; Type=" + getTypeToString(d_type) + "; Kind=" + getKindToString(d_kind);
+  return ": RPZ Hit; PolicyName=" + getName() + "; Trigger=" + getTrigger().toLogString() + "; Hit=" + getHit() + "; Type=" + getTypeToString(d_type) + "; Kind=" + getKindToString(d_kind);
 }
 
 void DNSFilterEngine::Policy::info(Logr::Priority prio, const std::shared_ptr<Logr::Logger>& log) const
 {
-  log->info(prio, "RPZ Hit", "policyName", Logging::Loggable(getName()), "trigger", Logging::Loggable(d_trigger),
-            "hit", Logging::Loggable(d_hit), "type", Logging::Loggable(getTypeToString(d_type)),
+  log->info(prio, "RPZ Hit", "policyName", Logging::Loggable(getName()), "trigger", Logging::Loggable(getTrigger()),
+            "hit", Logging::Loggable(getHit()), "type", Logging::Loggable(getTypeToString(d_type)),
             "kind", Logging::Loggable(getKindToString(d_kind)));
 }
 
@@ -599,8 +605,11 @@ std::vector<DNSRecord> DNSFilterEngine::Policy::getCustomRecords(const DNSName&
   }
 
   std::vector<DNSRecord> result;
+  if (customRecordsSize() == 0) {
+    return result;
+  }
 
-  for (const auto& custom : d_custom) {
+  for (const auto& custom : *d_custom) {
     if (qtype != QType::ANY && qtype != custom->getType() && custom->getType() != QType::CNAME) {
       continue;
     }
index 7d20b7a387127d80a9895f96b5f44a4c6df51e0d..d69b799e5d50bd10958b4b93c4744c96ff73ba27 100644 (file)
@@ -113,10 +113,48 @@ public:
     }
 
     Policy(PolicyKind kind, PolicyType type, int32_t ttl = 0, std::shared_ptr<PolicyZoneData> data = nullptr, const std::vector<std::shared_ptr<const DNSRecordContent>>& custom = {}) :
-      d_custom(custom), d_zoneData(std::move(data)), d_ttl(ttl), d_kind(kind), d_type(type)
+      d_zoneData(std::move(data)), d_custom(nullptr), d_ttl(ttl), d_kind(kind), d_type(type)
+    {
+      if (!custom.empty()) {
+        setCustom(custom);
+      }
+    }
+
+    ~Policy() = default;
+
+    Policy(const Policy& rhs) :
+      d_zoneData(rhs.d_zoneData),
+      d_custom(rhs.d_custom ? make_unique<CustomData>(*rhs.d_custom) : nullptr),
+      d_hitdata(rhs.d_hitdata ? make_unique<HitData>(*rhs.d_hitdata) : nullptr),
+      d_ttl(rhs.d_ttl),
+      d_kind(rhs.d_kind),
+      d_type(rhs.d_type)
     {
     }
 
+    Policy& operator=(const Policy& rhs)
+    {
+      if (this != &rhs) {
+        if (rhs.d_custom) {
+          d_custom = make_unique<CustomData>(*rhs.d_custom);
+        }
+        d_zoneData = rhs.d_zoneData;
+        if (rhs.d_hitdata) {
+          d_hitdata = make_unique<HitData>(*rhs.d_hitdata);
+        }
+        else {
+          d_hitdata = nullptr;
+        }
+        d_ttl = rhs.d_ttl;
+        d_kind = rhs.d_kind;
+        d_type = rhs.d_type;
+      }
+      return *this;
+    }
+
+    Policy(Policy&&) = default;
+    Policy& operator=(Policy&&) = default;
+
     bool operator==(const Policy& rhs) const
     {
       return d_kind == rhs.d_kind && d_type == rhs.d_type && d_ttl == rhs.d_ttl && d_custom == rhs.d_custom;
@@ -199,10 +237,17 @@ public:
     [[nodiscard]] std::vector<DNSRecord> getCustomRecords(const DNSName& qname, uint16_t qtype) const;
     [[nodiscard]] std::vector<DNSRecord> getRecords(const DNSName& qname) const;
 
-    std::vector<std::shared_ptr<const DNSRecordContent>> d_custom;
     std::shared_ptr<PolicyZoneData> d_zoneData{nullptr};
-    DNSName d_trigger;
-    string d_hit;
+
+    using CustomData = std::vector<std::shared_ptr<const DNSRecordContent>>;
+    std::unique_ptr<CustomData> d_custom;
+
+    struct HitData
+    {
+      DNSName d_trigger;
+      string d_hit;
+    };
+    std::unique_ptr<HitData> d_hitdata;
     /* Yup, we are currently using the same TTL for every record for a given name */
     int32_t d_ttl;
     PolicyKind d_kind;
@@ -217,6 +262,35 @@ public:
       }
     }
 
+    void setCustom(const CustomData& custom)
+    {
+      d_custom = make_unique<CustomData>(custom);
+    }
+
+    [[nodiscard]] size_t customRecordsSize() const
+    {
+      if (d_custom) {
+        return d_custom->size();
+      }
+      return 0;
+    }
+
+    void setHitData(const DNSName& name, const string& hit)
+    {
+      HitData hitdata{name, hit};
+      d_hitdata = make_unique<HitData>(hitdata);
+    }
+
+    [[nodiscard]] DNSName getTrigger() const
+    {
+      return d_hitdata ? d_hitdata->d_trigger : DNSName();
+    }
+
+    [[nodiscard]] std::string getHit() const
+    {
+      return d_hitdata ? d_hitdata->d_hit : "";
+    }
+
   private:
     [[nodiscard]] DNSRecord getRecordFromCustom(const DNSName& qname, const std::shared_ptr<const DNSRecordContent>& custom) const;
   };
index 0a381b2b890b406168cba35f5ecbe36d3f0f6567..1177cf05cbd140f5ff776a5dce37d9395d499546 100644 (file)
@@ -218,8 +218,20 @@ void RecursorLua4::postPrepareContext()
   d_lw->registerMember("policyKind", &DNSFilterEngine::Policy::d_kind);
   d_lw->registerMember("policyType", &DNSFilterEngine::Policy::d_type);
   d_lw->registerMember("policyTTL", &DNSFilterEngine::Policy::d_ttl);
-  d_lw->registerMember("policyTrigger", &DNSFilterEngine::Policy::d_trigger);
-  d_lw->registerMember("policyHit", &DNSFilterEngine::Policy::d_hit);
+  d_lw->registerMember<DNSFilterEngine::Policy, DNSName>("policyTrigger",
+    [](const DNSFilterEngine::Policy& pol) {
+      return pol.getTrigger();
+    },
+    [](DNSFilterEngine::Policy& pol, const DNSName& dnsname) {
+      pol.setHitData(dnsname, pol.getHit());
+    });
+  d_lw->registerMember<DNSFilterEngine::Policy, std::string>("policyHit",
+    [](const DNSFilterEngine::Policy& pol) {
+      return pol.getHit();
+    },
+    [](DNSFilterEngine::Policy& pol, const std::string& hit) {
+      pol.setHitData(pol.getTrigger(), hit);
+    });
   d_lw->registerMember<DNSFilterEngine::Policy, std::string>("policyCustom",
     [](const DNSFilterEngine::Policy& pol) -> std::string {
       std::string result;
@@ -227,19 +239,21 @@ void RecursorLua4::postPrepareContext()
         return result;
       }
 
-      for (const auto& dr : pol.d_custom) {
-        if (!result.empty()) {
-          result += "\n";
+      if (pol.d_custom) {
+        for (const auto& dnsRecord : *pol.d_custom) {
+          if (!result.empty()) {
+            result += "\n";
+          }
+          result += dnsRecord->getZoneRepresentation();
         }
-        result += dr->getZoneRepresentation();
       }
 
       return result;
     },
     [](DNSFilterEngine::Policy& pol, const std::string& content) {
       // Only CNAMES for now, when we ever add a d_custom_type, there will be pain
-      pol.d_custom.clear();
-      pol.d_custom.push_back(DNSRecordContent::make(QType::CNAME, QClass::IN, content));
+      pol.d_custom = make_unique<DNSFilterEngine::Policy::CustomData>();
+      pol.d_custom->push_back(DNSRecordContent::make(QType::CNAME, QClass::IN, content));
     }
   );
   d_lw->registerFunction("getDH", &DNSQuestion::getDH);
index 5071a38787c5ee4b1351d2f0430dd2d5354292e2..419e341369d0ce9e1f561dc85a86bf2e31f49b42 100644 (file)
@@ -569,10 +569,10 @@ static PolicyResult handlePolicyHit(const DNSFilterEngine::Policy& appliedPolicy
           break;
         }
         catch (const pdns::validation::TooManySEC3IterationsException& e) {
-          if (g_logCommonErrors) {
+          if (g_logCommonErrors || (g_dnssecLogBogus && resolver.getDNSSECLimitHit())) {
             SLOG(g_log << Logger::Notice << "Sending SERVFAIL to " << comboWriter->getRemote() << " during resolve of the custom filter policy '" << appliedPolicy.getName() << "' while resolving '" << comboWriter->d_mdp.d_qname << "' because: " << e.what() << endl,
                  resolver.d_slog->error(Logr::Notice, e.what(), "Sending SERVFAIL during resolve of the custom filter policy",
-                                        "policyName", Logging::Loggable(appliedPolicy.getName()), "exception", Logging::Loggable("TooManySEC3IterationsException")));
+                                        "policyName", Logging::Loggable(appliedPolicy.getName()), "exception", Logging::Loggable("TooManySEC3IterationsException"), "dnsseclimithit", Logging::Loggable(resolver.getDNSSECLimitHit())));
           }
           res = RCode::ServFail;
           break;
@@ -1282,7 +1282,7 @@ void startDoResolve(void* arg) // NOLINT(readability-function-cognitive-complexi
       catch (const pdns::validation::TooManySEC3IterationsException& e) {
         if (g_logCommonErrors) {
           SLOG(g_log << Logger::Notice << "Sending SERVFAIL to " << comboWriter->getRemote() << " during resolve of '" << comboWriter->d_mdp.d_qname << "' because: " << e.what() << endl,
-               resolver.d_slog->error(Logr::Notice, e.what(), "Sending SERVFAIL during resolve"));
+               resolver.d_slog->error(Logr::Notice, e.what(), "Sending SERVFAIL during resolve", "dnsseclimithit", Logging::Loggable(true)));
         }
         res = RCode::ServFail;
       }
@@ -1403,6 +1403,9 @@ void startDoResolve(void* arg) // NOLINT(readability-function-cognitive-complexi
           if (resolver.doLog() || vStateIsBogus(state)) {
             // Only create logging object if needed below, beware if you change the logging logic!
             log = resolver.d_slog->withValues("vstate", Logging::Loggable(state));
+            if (resolver.getDNSSECLimitHit()) {
+              log = log->withValues("dnsseclimithit", Logging::Loggable(true));
+            }
             auto xdnssec = g_xdnssec.getLocal();
             if (xdnssec->check(comboWriter->d_mdp.d_qname)) {
               log = log->withValues("in-x-dnssec-names", Logging::Loggable(1));
@@ -1466,9 +1469,9 @@ void startDoResolve(void* arg) // NOLINT(readability-function-cognitive-complexi
           goto sendit; // NOLINT(cppcoreguidelines-avoid-goto)
         }
         catch (const pdns::validation::TooManySEC3IterationsException& e) {
-          if (g_logCommonErrors) {
+          if (g_logCommonErrors || (g_dnssecLogBogus && resolver.getDNSSECLimitHit())) {
             SLOG(g_log << Logger::Notice << "Sending SERVFAIL to " << comboWriter->getRemote() << " during validation of '" << comboWriter->d_mdp.d_qname << "|" << QType(comboWriter->d_mdp.d_qtype) << "' because: " << e.what() << endl,
-                 resolver.d_slog->error(Logr::Notice, e.what(), "Sending SERVFAIL during validation", "exception", Logging::Loggable("TooManySEC3IterationsException")));
+                 resolver.d_slog->error(Logr::Notice, e.what(), "Sending SERVFAIL during validation", "exception", Logging::Loggable("TooManySEC3IterationsException"), "dnsseclimithit", Logging::Loggable(resolver.getDNSSECLimitHit())));
           }
           goto sendit; // NOLINT(cppcoreguidelines-avoid-goto)
         }
@@ -1713,8 +1716,8 @@ void startDoResolve(void* arg) // NOLINT(readability-function-cognitive-complexi
       if (!appliedPolicy.getName().empty()) {
         pbMessage.setAppliedPolicy(appliedPolicy.getName());
         pbMessage.setAppliedPolicyType(appliedPolicy.d_type);
-        pbMessage.setAppliedPolicyTrigger(appliedPolicy.d_trigger);
-        pbMessage.setAppliedPolicyHit(appliedPolicy.d_hit);
+        pbMessage.setAppliedPolicyTrigger(appliedPolicy.getTrigger());
+        pbMessage.setAppliedPolicyHit(appliedPolicy.getHit());
         pbMessage.setAppliedPolicyKind(appliedPolicy.d_kind);
       }
       pbMessage.setInBytes(packet.size());
index e862da35f90443e4892c60f86c0562f594eb34f2..bd080360f5a317011f2843088a49811fe764c82c 100644 (file)
@@ -109,8 +109,11 @@ static void parseRPZParameters(rpzOptions_t& have, std::shared_ptr<DNSFilterEngi
     defpol->d_kind = (DNSFilterEngine::PolicyKind)boost::get<uint32_t>(have["defpol"]);
     defpol->setName(polName);
     if (defpol->d_kind == DNSFilterEngine::PolicyKind::Custom) {
-      defpol->d_custom.push_back(DNSRecordContent::make(QType::CNAME, QClass::IN,
-                                                        boost::get<string>(have["defcontent"])));
+      if (!defpol->d_custom) {
+        defpol->d_custom = make_unique<DNSFilterEngine::Policy::CustomData>();
+      }
+      defpol->d_custom->push_back(DNSRecordContent::make(QType::CNAME, QClass::IN,
+                                                         boost::get<string>(have["defcontent"])));
 
       if (have.count("defttl") != 0) {
         defpol->d_ttl = static_cast<int32_t>(boost::get<uint32_t>(have["defttl"]));
index 54d117bd7315869e5a976fdbe5714bf68c2792b6..e652ddb0cc7ff970cffd51adf6712ec711fe39cf 100644 (file)
@@ -2752,8 +2752,7 @@ static void recursorThread()
       }
     }
 
-    unsigned int ringsize = ::arg().asNum("stats-ringbuffer-entries") / RecThreadInfo::numUDPWorkers();
-    if (ringsize != 0) {
+    if (unsigned int ringsize = ::arg().asNum("stats-ringbuffer-entries") / RecThreadInfo::numUDPWorkers(); ringsize != 0) {
       t_remotes = std::make_unique<addrringbuf_t>();
       if (RecThreadInfo::weDistributeQueries()) {
         t_remotes->set_capacity(::arg().asNum("stats-ringbuffer-entries") / RecThreadInfo::numDistributors());
@@ -2780,14 +2779,16 @@ static void recursorThread()
     g_multiTasker = std::make_unique<MT_t>(::arg().asNum("stack-size"), ::arg().asNum("stack-cache-size"));
     threadInfo.setMT(g_multiTasker.get());
 
-    /* start protobuf export threads if needed */
-    auto luaconfsLocal = g_luaconfs.getLocal();
-    checkProtobufExport(luaconfsLocal);
-    checkOutgoingProtobufExport(luaconfsLocal);
+    {
+      /* start protobuf export threads if needed, don;'t keep a ref to lua config around */
+      auto luaconfsLocal = g_luaconfs.getLocal();
+      checkProtobufExport(luaconfsLocal);
+      checkOutgoingProtobufExport(luaconfsLocal);
 #ifdef HAVE_FSTRM
-    checkFrameStreamExport(luaconfsLocal, luaconfsLocal->frameStreamExportConfig, t_frameStreamServersInfo);
-    checkFrameStreamExport(luaconfsLocal, luaconfsLocal->nodFrameStreamExportConfig, t_nodFrameStreamServersInfo);
+      checkFrameStreamExport(luaconfsLocal, luaconfsLocal->frameStreamExportConfig, t_frameStreamServersInfo);
+      checkFrameStreamExport(luaconfsLocal, luaconfsLocal->nodFrameStreamExportConfig, t_nodFrameStreamServersInfo);
 #endif
+    }
 
     t_fdm = unique_ptr<FDMultiplexer>(getMultiplexer(log));
 
index 415933ca4bc701e767a5660a142749fdf2eb7b34..46488f43ecb07b13f31448005350467913ae08ef 100644 (file)
@@ -403,6 +403,9 @@ void ZoneData::ZoneToCache(const RecZoneToCache::Config& config)
   d_now = time(nullptr);
   for (const auto& [key, v] : d_all) {
     const auto& [qname, qtype] = key;
+    if (qname.isWildcard()) {
+      continue;
+    }
     switch (qtype) {
     case QType::NSEC:
     case QType::NSEC3:
index 33c9ae27a5ec7bb4debc97c90f47afb83bf1cb43..fcf04fd18322ef0935ddbff630f84820ad556db6 100644 (file)
@@ -43,65 +43,73 @@ Netmask makeNetmaskFromRPZ(const DNSName& name)
    * $NETMASK.zz (::/$NETMASK)
    * Terrible right?
    */
-  if (parts.size() < 2 || parts.size() > 9)
+  if (parts.size() < 2 || parts.size() > 9) {
     throw PDNSException("Invalid IP address in RPZ: " + name.toLogString());
+  }
 
   bool isV6 = (stoi(parts[0]) > 32);
   bool hadZZ = false;
 
   for (auto& part : parts) {
     // Check if we have an IPv4 octet
-    for (auto c : part)
-      if (!isdigit(c))
+    for (auto labelLetter : part) {
+      if (isdigit(labelLetter) == 0) {
         isV6 = true;
-
+      }
+    }
     if (pdns_iequals(part, "zz")) {
-      if (hadZZ)
+      if (hadZZ) {
         throw PDNSException("more than one 'zz' label found in RPZ name" + name.toLogString());
+      }
       part = "";
       isV6 = true;
       hadZZ = true;
     }
   }
 
-  if (isV6 && parts.size() < 9 && !hadZZ)
+  if (isV6 && parts.size() < 9 && !hadZZ) {
     throw PDNSException("No 'zz' label found in an IPv6 RPZ name shorter than 9 elements: " + name.toLogString());
+  }
 
-  if (parts.size() == 5 && !isV6)
-    return Netmask(parts[4] + "." + parts[3] + "." + parts[2] + "." + parts[1] + "/" + parts[0]);
-
-  string v6;
+  if (parts.size() == 5 && !isV6) {
+    return parts[4] + "." + parts[3] + "." + parts[2] + "." + parts[1] + "/" + parts[0];
+  }
+  string v6Address;
 
-  if (parts[parts.size() - 1] == "") {
-    v6 += ":";
+  if (parts[parts.size() - 1].empty()) {
+    v6Address += ":";
   }
   for (uint8_t i = parts.size() - 1; i > 0; i--) {
-    v6 += parts[i];
-    if (i > 1 || (i == 1 && parts[i] == "")) {
-      v6 += ":";
+    v6Address += parts[i];
+    if (i > 1 || (i == 1 && parts[i].empty())) {
+      v6Address += ":";
     }
   }
-  v6 += "/" + parts[0];
+  v6Address += "/" + parts[0];
 
-  return Netmask(v6);
+  return v6Address;
 }
 
-static void RPZRecordToPolicy(const DNSRecord& dr, std::shared_ptr<DNSFilterEngine::Zone> zone, bool addOrRemove, const boost::optional<DNSFilterEngine::Policy>& defpol, bool defpolOverrideLocal, uint32_t maxTTL, Logr::log_t log)
+static void RPZRecordToPolicy(const DNSRecord& dnsRecord, const std::shared_ptr<DNSFilterEngine::Zone>& zone, bool addOrRemove, const boost::optional<DNSFilterEngine::Policy>& defpol, bool defpolOverrideLocal, uint32_t maxTTL, Logr::log_t log)
 {
-  static const DNSName drop("rpz-drop."), truncate("rpz-tcp-only."), noaction("rpz-passthru.");
-  static const DNSName rpzClientIP("rpz-client-ip"), rpzIP("rpz-ip"),
-    rpzNSDname("rpz-nsdname"), rpzNSIP("rpz-nsip.");
+  static const DNSName drop("rpz-drop.");
+  static const DNSName truncate("rpz-tcp-only.");
+  static const DNSName noaction("rpz-passthru.");
+  static const DNSName rpzClientIP("rpz-client-ip");
+  static const DNSName rpzIP("rpz-ip");
+  static const DNSName rpzNSDname("rpz-nsdname");
+  static const DNSName rpzNSIP("rpz-nsip.");
   static const std::string rpzPrefix("rpz-");
 
   DNSFilterEngine::Policy pol;
   bool defpolApplied = false;
 
-  if (dr.d_class != QClass::IN) {
+  if (dnsRecord.d_class != QClass::IN) {
     return;
   }
 
-  if (dr.d_type == QType::CNAME) {
-    auto crc = getRR<CNAMERecordContent>(dr);
+  if (dnsRecord.d_type == QType::CNAME) {
+    auto crc = getRR<CNAMERecordContent>(dnsRecord);
     if (!crc) {
       return;
     }
@@ -140,13 +148,16 @@ static void RPZRecordToPolicy(const DNSRecord& dr, std::shared_ptr<DNSFilterEngi
     else if (!crcTarget.empty() && !crcTarget.isRoot() && crcTarget.getRawLabel(crcTarget.countLabels() - 1).compare(0, rpzPrefix.length(), rpzPrefix) == 0) {
       /* this is very likely an higher format number or a configuration error,
          let's just ignore it. */
-      SLOG(g_log << Logger::Info << "Discarding unsupported RPZ entry " << crcTarget << " for " << dr.d_name << endl,
-           log->info(Logr::Info, "Discarding unsupported RPZ entry", "target", Logging::Loggable(crcTarget), "name", Logging::Loggable(dr.d_name)));
+      SLOG(g_log << Logger::Info << "Discarding unsupported RPZ entry " << crcTarget << " for " << dnsRecord.d_name << endl,
+           log->info(Logr::Info, "Discarding unsupported RPZ entry", "target", Logging::Loggable(crcTarget), "name", Logging::Loggable(dnsRecord.d_name)));
       return;
     }
     else {
       pol.d_kind = DNSFilterEngine::PolicyKind::Custom;
-      pol.d_custom.emplace_back(dr.getContent());
+      if (!pol.d_custom) {
+        pol.d_custom = make_unique<DNSFilterEngine::Policy::CustomData>();
+      }
+      pol.d_custom->emplace_back(dnsRecord.getContent());
       // cerr<<"Wants custom "<<crcTarget<<" for "<<dr.d_name<<": ";
     }
   }
@@ -157,13 +168,16 @@ static void RPZRecordToPolicy(const DNSRecord& dr, std::shared_ptr<DNSFilterEngi
     }
     else {
       pol.d_kind = DNSFilterEngine::PolicyKind::Custom;
-      pol.d_custom.emplace_back(dr.getContent());
+      if (!pol.d_custom) {
+        pol.d_custom = make_unique<DNSFilterEngine::Policy::CustomData>();
+      }
+      pol.d_custom->emplace_back(dnsRecord.getContent());
       // cerr<<"Wants custom "<<dr.d_content->getZoneRepresentation()<<" for "<<dr.d_name<<": ";
     }
   }
 
   if (!defpolApplied || defpol->d_ttl < 0) {
-    pol.d_ttl = static_cast<int32_t>(std::min(maxTTL, dr.d_ttl));
+    pol.d_ttl = static_cast<int32_t>(std::min(maxTTL, dnsRecord.d_ttl));
   }
   else {
     pol.d_ttl = static_cast<int32_t>(std::min(maxTTL, static_cast<uint32_t>(pol.d_ttl)));
@@ -171,104 +185,113 @@ static void RPZRecordToPolicy(const DNSRecord& dr, std::shared_ptr<DNSFilterEngi
 
   // now to DO something with that
 
-  if (dr.d_name.isPartOf(rpzNSDname)) {
-    DNSName filt = dr.d_name.makeRelative(rpzNSDname);
-    if (addOrRemove)
+  if (dnsRecord.d_name.isPartOf(rpzNSDname)) {
+    DNSName filt = dnsRecord.d_name.makeRelative(rpzNSDname);
+    if (addOrRemove) {
       zone->addNSTrigger(filt, std::move(pol), defpolApplied);
-    else
-      zone->rmNSTrigger(filt, std::move(pol));
-  }
-  else if (dr.d_name.isPartOf(rpzClientIP)) {
-    DNSName filt = dr.d_name.makeRelative(rpzClientIP);
-    auto nm = makeNetmaskFromRPZ(filt);
-    if (addOrRemove)
-      zone->addClientTrigger(nm, std::move(pol), defpolApplied);
-    else
-      zone->rmClientTrigger(nm, std::move(pol));
-  }
-  else if (dr.d_name.isPartOf(rpzIP)) {
+    }
+    else {
+      zone->rmNSTrigger(filt, pol);
+    }
+  }
+  else if (dnsRecord.d_name.isPartOf(rpzClientIP)) {
+    DNSName filt = dnsRecord.d_name.makeRelative(rpzClientIP);
+    auto netmask = makeNetmaskFromRPZ(filt);
+    if (addOrRemove) {
+      zone->addClientTrigger(netmask, std::move(pol), defpolApplied);
+    }
+    else {
+      zone->rmClientTrigger(netmask, pol);
+    }
+  }
+  else if (dnsRecord.d_name.isPartOf(rpzIP)) {
     // cerr<<"Should apply answer content IP policy: "<<dr.d_name<<endl;
-    DNSName filt = dr.d_name.makeRelative(rpzIP);
-    auto nm = makeNetmaskFromRPZ(filt);
-    if (addOrRemove)
-      zone->addResponseTrigger(nm, std::move(pol), defpolApplied);
-    else
-      zone->rmResponseTrigger(nm, std::move(pol));
-  }
-  else if (dr.d_name.isPartOf(rpzNSIP)) {
-    DNSName filt = dr.d_name.makeRelative(rpzNSIP);
-    auto nm = makeNetmaskFromRPZ(filt);
-    if (addOrRemove)
-      zone->addNSIPTrigger(nm, std::move(pol), defpolApplied);
-    else
-      zone->rmNSIPTrigger(nm, std::move(pol));
+    DNSName filt = dnsRecord.d_name.makeRelative(rpzIP);
+    auto netmask = makeNetmaskFromRPZ(filt);
+    if (addOrRemove) {
+      zone->addResponseTrigger(netmask, std::move(pol), defpolApplied);
+    }
+    else {
+      zone->rmResponseTrigger(netmask, pol);
+    }
+  }
+  else if (dnsRecord.d_name.isPartOf(rpzNSIP)) {
+    DNSName filt = dnsRecord.d_name.makeRelative(rpzNSIP);
+    auto netmask = makeNetmaskFromRPZ(filt);
+    if (addOrRemove) {
+      zone->addNSIPTrigger(netmask, std::move(pol), defpolApplied);
+    }
+    else {
+      zone->rmNSIPTrigger(netmask, pol);
+    }
   }
   else {
     if (addOrRemove) {
       /* if we did override the existing policy with the default policy,
          we might turn two A or AAAA into a CNAME, which would trigger
          an exception. Let's just ignore it. */
-      zone->addQNameTrigger(dr.d_name, std::move(pol), defpolApplied);
+      zone->addQNameTrigger(dnsRecord.d_name, std::move(pol), defpolApplied);
     }
     else {
-      zone->rmQNameTrigger(dr.d_name, std::move(pol));
+      zone->rmQNameTrigger(dnsRecord.d_name, pol);
     }
   }
 }
 
-static shared_ptr<const SOARecordContent> loadRPZFromServer(Logr::log_t plogger, const ComboAddress& primary, const DNSName& zoneName, std::shared_ptr<DNSFilterEngine::Zone> zone, const boost::optional<DNSFilterEngine::Policy>& defpol, bool defpolOverrideLocal, uint32_t maxTTL, const TSIGTriplet& tt, size_t maxReceivedBytes, const ComboAddress& localAddress, uint16_t axfrTimeout)
+static shared_ptr<const SOARecordContent> loadRPZFromServer(Logr::log_t plogger, const ComboAddress& primary, const DNSName& zoneName, const std::shared_ptr<DNSFilterEngine::Zone>& zone, const boost::optional<DNSFilterEngine::Policy>& defpol, bool defpolOverrideLocal, uint32_t maxTTL, const TSIGTriplet& tsigTriplet, size_t maxReceivedBytes, const ComboAddress& localAddress, uint16_t axfrTimeout)
 {
 
   auto logger = plogger->withValues("primary", Logging::Loggable(primary));
   SLOG(g_log << Logger::Warning << "Loading RPZ zone '" << zoneName << "' from " << primary.toStringWithPort() << endl,
        logger->info(Logr::Info, "Loading RPZ from nameserver"));
-  if (!tt.name.empty()) {
-    SLOG(g_log << Logger::Warning << "With TSIG key '" << tt.name << "' of algorithm '" << tt.algo << "'" << endl,
-         logger->info(Logr::Info, "Using TSIG key for authentication", "tsig_key_name", Logging::Loggable(tt.name), "tsig_key_algorithm", Logging::Loggable(tt.algo)));
+  if (!tsigTriplet.name.empty()) {
+    SLOG(g_log << Logger::Warning << "With TSIG key '" << tsigTriplet.name << "' of algorithm '" << tsigTriplet.algo << "'" << endl,
+         logger->info(Logr::Info, "Using TSIG key for authentication", "tsig_key_name", Logging::Loggable(tsigTriplet.name), "tsig_key_algorithm", Logging::Loggable(tsigTriplet.algo)));
   }
 
   ComboAddress local(localAddress);
-  if (local == ComboAddress())
+  if (local == ComboAddress()) {
     local = pdns::getQueryLocalAddress(primary.sin4.sin_family, 0);
+  }
 
-  AXFRRetriever axfr(primary, zoneName, tt, &local, maxReceivedBytes, axfrTimeout);
+  AXFRRetriever axfr(primary, zoneName, tsigTriplet, &local, maxReceivedBytes, axfrTimeout);
   unsigned int nrecords = 0;
   Resolver::res_t nop;
   vector<DNSRecord> chunk;
   time_t last = 0;
   time_t axfrStart = time(nullptr);
   time_t axfrNow = time(nullptr);
-  shared_ptr<const SOARecordContent> sr;
+  shared_ptr<const SOARecordContent> soaRecordContent;
   // coverity[store_truncates_time_t]
-  while (axfr.getChunk(nop, &chunk, (axfrStart + axfrTimeout - axfrNow))) {
-    for (auto& dr : chunk) {
-      if (dr.d_type == QType::NS || dr.d_type == QType::TSIG) {
+  while (axfr.getChunk(nop, &chunk, (axfrStart + axfrTimeout - axfrNow)) != 0) {
+    for (auto& dnsRecord : chunk) {
+      if (dnsRecord.d_type == QType::NS || dnsRecord.d_type == QType::TSIG) {
         continue;
       }
 
-      dr.d_name.makeUsRelative(zoneName);
-      if (dr.d_type == QType::SOA) {
-        sr = getRR<SOARecordContent>(dr);
-        zone->setSOA(dr);
+      dnsRecord.d_name.makeUsRelative(zoneName);
+      if (dnsRecord.d_type == QType::SOA) {
+        soaRecordContent = getRR<SOARecordContent>(dnsRecord);
+        zone->setSOA(dnsRecord);
         continue;
       }
 
-      RPZRecordToPolicy(dr, zone, true, defpol, defpolOverrideLocal, maxTTL, logger);
+      RPZRecordToPolicy(dnsRecord, zone, true, defpol, defpolOverrideLocal, maxTTL, logger);
       nrecords++;
     }
     axfrNow = time(nullptr);
     if (axfrNow < axfrStart || axfrNow - axfrStart > axfrTimeout) {
       throw PDNSException("Total AXFR time exceeded!");
     }
-    if (last != time(0)) {
+    if (last != time(nullptr)) {
       SLOG(g_log << Logger::Info << "Loaded & indexed " << nrecords << " policy records so far for RPZ zone '" << zoneName << "'" << endl,
            logger->info(Logr::Info, "RPZ load in progress", "nrecords", Logging::Loggable(nrecords)));
-      last = time(0);
+      last = time(nullptr);
     }
   }
-  SLOG(g_log << Logger::Info << "Done: " << nrecords << " policy records active, SOA: " << sr->getZoneRepresentation() << endl,
-       logger->info(Logr::Info, "RPZ load completed", "nrecords", Logging::Loggable(nrecords), "soa", Logging::Loggable(sr->getZoneRepresentation())));
-  return sr;
+  SLOG(g_log << Logger::Info << "Done: " << nrecords << " policy records active, SOA: " << soaRecordContent->getZoneRepresentation() << endl,
+       logger->info(Logr::Info, "RPZ load completed", "nrecords", Logging::Loggable(nrecords), "soa", Logging::Loggable(soaRecordContent->getZoneRepresentation())));
+  return soaRecordContent;
 }
 
 static LockGuarded<std::unordered_map<std::string, shared_ptr<rpzStats>>> s_rpzStats;
@@ -276,20 +299,21 @@ static LockGuarded<std::unordered_map<std::string, shared_ptr<rpzStats>>> s_rpzS
 shared_ptr<rpzStats> getRPZZoneStats(const std::string& zone)
 {
   auto stats = s_rpzStats.lock();
-  auto it = stats->find(zone);
-  if (it == stats->end()) {
+  auto statsIt = stats->find(zone);
+  if (statsIt == stats->end()) {
     auto stat = std::make_shared<rpzStats>();
     (*stats)[zone] = stat;
     return stat;
   }
-  return it->second;
+  return statsIt->second;
 }
 
 static void incRPZFailedTransfers(const std::string& zone)
 {
   auto stats = getRPZZoneStats(zone);
-  if (stats != nullptr)
+  if (stats != nullptr) {
     stats->d_failedTransfers++;
+  }
 }
 
 static void setRPZZoneNewState(const std::string& zone, uint32_t serial, uint64_t numberOfRecords, bool fromFile, bool wasAXFR)
@@ -310,9 +334,9 @@ static void setRPZZoneNewState(const std::string& zone, uint32_t serial, uint64_
 }
 
 // this function is silent - you do the logging
-std::shared_ptr<const SOARecordContent> loadRPZFromFile(const std::string& fname, std::shared_ptr<DNSFilterEngine::Zone> zone, const boost::optional<DNSFilterEngine::Policy>& defpol, bool defpolOverrideLocal, uint32_t maxTTL)
+std::shared_ptr<const SOARecordContent> loadRPZFromFile(const std::string& fname, const std::shared_ptr<DNSFilterEngine::Zone>& zone, const boost::optional<DNSFilterEngine::Policy>& defpol, bool defpolOverrideLocal, uint32_t maxTTL)
 {
-  shared_ptr<const SOARecordContent> sr = nullptr;
+  shared_ptr<const SOARecordContent> soaRecordContent = nullptr;
   ZoneParserTNG zpt(fname);
   zpt.setMaxGenerateSteps(::arg().asNum("max-generate-steps"));
   zpt.setMaxIncludes(::arg().asNum("max-include-depth"));
@@ -322,21 +346,22 @@ std::shared_ptr<const SOARecordContent> loadRPZFromFile(const std::string& fname
   auto log = g_slog->withName("rpz")->withValues("file", Logging::Loggable(fname), "zone", Logging::Loggable(zone->getName()));
   while (zpt.get(drr)) {
     try {
-      if (drr.qtype.getCode() == QType::CNAME && drr.content.empty())
+      if (drr.qtype.getCode() == QType::CNAME && drr.content.empty()) {
         drr.content = ".";
-      DNSRecord dr(drr);
-      if (dr.d_type == QType::SOA) {
-        sr = getRR<SOARecordContent>(dr);
-        domain = dr.d_name;
+      }
+      DNSRecord dnsRecord(drr);
+      if (dnsRecord.d_type == QType::SOA) {
+        soaRecordContent = getRR<SOARecordContent>(dnsRecord);
+        domain = dnsRecord.d_name;
         zone->setDomain(domain);
-        soaRecord = std::move(dr);
+        soaRecord = std::move(dnsRecord);
       }
-      else if (dr.d_type == QType::NS) {
+      else if (dnsRecord.d_type == QType::NS) {
         continue;
       }
       else {
-        dr.d_name = dr.d_name.makeRelative(domain);
-        RPZRecordToPolicy(dr, zone, true, defpol, defpolOverrideLocal, maxTTL, log);
+        dnsRecord.d_name = dnsRecord.d_name.makeRelative(domain);
+        RPZRecordToPolicy(dnsRecord, zone, true, defpol, defpolOverrideLocal, maxTTL, log);
       }
     }
     catch (const PDNSException& pe) {
@@ -344,36 +369,36 @@ std::shared_ptr<const SOARecordContent> loadRPZFromFile(const std::string& fname
     }
   }
 
-  if (sr != nullptr) {
-    zone->setRefresh(sr->d_st.refresh);
+  if (soaRecordContent != nullptr) {
+    zone->setRefresh(soaRecordContent->d_st.refresh);
     zone->setSOA(std::move(soaRecord));
-    setRPZZoneNewState(zone->getName(), sr->d_st.serial, zone->size(), true, false);
+    setRPZZoneNewState(zone->getName(), soaRecordContent->d_st.serial, zone->size(), true, false);
   }
-  return sr;
+  return soaRecordContent;
 }
 
 static bool dumpZoneToDisk(Logr::log_t logger, const DNSName& zoneName, const std::shared_ptr<DNSFilterEngine::Zone>& newZone, const std::string& dumpZoneFileName)
 {
   logger->info(Logr::Debug, "Dumping zone to disk", "destination_file", Logging::Loggable(dumpZoneFileName));
   std::string temp = dumpZoneFileName + "XXXXXX";
-  int fd = mkstemp(&temp.at(0));
-  if (fd < 0) {
+  int fileDesc = mkstemp(&temp.at(0));
+  if (fileDesc < 0) {
     SLOG(g_log << Logger::Warning << "Unable to open a file to dump the content of the RPZ zone " << zoneName << endl,
          logger->error(Logr::Error, errno, "Unable to create temporary file"));
     return false;
   }
 
-  auto fp = std::unique_ptr<FILE, int (*)(FILE*)>(fdopen(fd, "w+"), fclose);
-  if (!fp) {
+  auto filePtr = std::unique_ptr<FILE, int (*)(FILE*)>(fdopen(fileDesc, "w+"), fclose);
+  if (!filePtr) {
     int err = errno;
-    close(fd);
+    close(fileDesc);
     SLOG(g_log << Logger::Warning << "Unable to open a file pointer to dump the content of the RPZ zone " << zoneName << endl,
          logger->error(Logr::Error, err, "Unable to open file pointer"));
     return false;
   }
 
   try {
-    newZone->dump(fp.get());
+    newZone->dump(filePtr.get());
   }
   catch (const std::exception& e) {
     SLOG(g_log << Logger::Warning << "Error while dumping the content of the RPZ zone " << zoneName << ": " << e.what() << endl,
@@ -381,19 +406,19 @@ static bool dumpZoneToDisk(Logr::log_t logger, const DNSName& zoneName, const st
     return false;
   }
 
-  if (fflush(fp.get()) != 0) {
+  if (fflush(filePtr.get()) != 0) {
     SLOG(g_log << Logger::Warning << "Error while flushing the content of the RPZ zone " << zoneName << " to the dump file: " << stringerror() << endl,
          logger->error(Logr::Warning, errno, "Error while flushing the content of the RPZ"));
     return false;
   }
 
-  if (fsync(fileno(fp.get())) != 0) {
+  if (fsync(fileno(filePtr.get())) != 0) {
     SLOG(g_log << Logger::Warning << "Error while syncing the content of the RPZ zone " << zoneName << " to the dump file: " << stringerror() << endl,
          logger->error(Logr::Error, errno, "Error while syncing the content of the RPZ"));
     return false;
   }
 
-  if (fclose(fp.release()) != 0) {
+  if (fclose(filePtr.release()) != 0) {
     SLOG(g_log << Logger::Warning << "Error while writing the content of the RPZ zone " << zoneName << " to the dump file: " << stringerror() << endl,
          logger->error(Logr::Error, errno, "Error while writing the content of the RPZ"));
     return false;
index 2933d9a1d0faa525f38407e549e2d80f2b652f76..57fddce8af4c8c0f126ccbfdd7fb257c26425897 100644 (file)
@@ -43,7 +43,7 @@ struct RPZTrackerParams
   std::string dumpZoneFileName;
 };
 
-std::shared_ptr<const SOARecordContent> loadRPZFromFile(const std::string& fname, std::shared_ptr<DNSFilterEngine::Zone> zone, const boost::optional<DNSFilterEngine::Policy>& defpol, bool defpolOverrideLocal, uint32_t maxTTL);
+std::shared_ptr<const SOARecordContent> loadRPZFromFile(const std::string& fname, const std::shared_ptr<DNSFilterEngine::Zone>& zone, const boost::optional<DNSFilterEngine::Policy>& defpol, bool defpolOverrideLocal, uint32_t maxTTL);
 void RPZIXFRTracker(RPZTrackerParams params, uint64_t configGeneration);
 bool notifyRPZTracker(const DNSName& name);
 
index 48c4acc2b3cdef18d98ed176ff8dfb3a62c34206..359d676072ac4b98e5e71ff7412079fa18fd4fc7 100644 (file)
@@ -4353,11 +4353,37 @@ RCode::rcodes_ SyncRes::updateCacheFromRecords(unsigned int depth, const string&
   sanitizeRecords(prefix, lwr, qname, qtype, auth, wasForwarded, rdQuery);
 
   std::vector<std::shared_ptr<DNSRecord>> authorityRecs;
-  const unsigned int labelCount = qname.countLabels();
   bool isCNAMEAnswer = false;
   bool isDNAMEAnswer = false;
   DNSName seenAuth;
 
+  // names that might be expanded from a wildcard, and thus require denial of existence proof
+  // this is the queried name and any part of the CNAME chain from the queried name
+  // the key is the name itself, the value is initially false and is set to true once we have
+  // confirmed it was actually expanded from a wildcard
+  std::map<DNSName, bool> wildcardCandidates{{qname, false}};
+
+  if (rdQuery) {
+    std::unordered_map<DNSName, DNSName> cnames;
+    for (const auto& rec : lwr.d_records) {
+      if (rec.d_type != QType::CNAME || rec.d_class != QClass::IN) {
+        continue;
+      }
+      if (auto content = getRR<CNAMERecordContent>(rec)) {
+        cnames[rec.d_name] = DNSName(content->getTarget());
+      }
+    }
+    auto initial = qname;
+    while (true) {
+      auto cnameIt = cnames.find(initial);
+      if (cnameIt == cnames.end()) {
+        break;
+      }
+      initial = cnameIt->second;
+      wildcardCandidates.emplace(initial, false);
+    }
+  }
+
   for (auto& rec : lwr.d_records) {
     if (rec.d_type == QType::OPT || rec.d_class != QClass::IN) {
       continue;
@@ -4377,6 +4403,7 @@ RCode::rcodes_ SyncRes::updateCacheFromRecords(unsigned int depth, const string&
       seenAuth = rec.d_name;
     }
 
+    const auto labelCount = rec.d_name.countLabels();
     if (rec.d_type == QType::RRSIG) {
       auto rrsig = getRR<RRSIGRecordContent>(rec);
       if (rrsig) {
@@ -4384,7 +4411,8 @@ RCode::rcodes_ SyncRes::updateCacheFromRecords(unsigned int depth, const string&
            count can be lower than the name's label count if it was
            synthesized from the wildcard. Note that the difference might
            be > 1. */
-        if (rec.d_name == qname && isWildcardExpanded(labelCount, *rrsig)) {
+        if (auto wcIt = wildcardCandidates.find(rec.d_name); wcIt != wildcardCandidates.end() && isWildcardExpanded(labelCount, *rrsig)) {
+          wcIt->second = true;
           gatherWildcardProof = true;
           if (!isWildcardExpandedOntoItself(rec.d_name, labelCount, *rrsig)) {
             /* if we have a wildcard expanded onto itself, we don't need to prove
@@ -4661,7 +4689,13 @@ RCode::rcodes_ SyncRes::updateCacheFromRecords(unsigned int depth, const string&
         if (isAA && tCacheEntry->first.type == QType::NS && s_save_parent_ns_set) {
           rememberParentSetIfNeeded(tCacheEntry->first.name, tCacheEntry->second.records, depth, prefix);
         }
-        g_recCache->replace(d_now.tv_sec, tCacheEntry->first.name, tCacheEntry->first.type, tCacheEntry->second.records, tCacheEntry->second.signatures, authorityRecs, tCacheEntry->first.type == QType::DS ? true : isAA, auth, tCacheEntry->first.place == DNSResourceRecord::ANSWER ? ednsmask : boost::none, d_routingTag, recordState, remoteIP, d_refresh, tCacheEntry->second.d_ttl_time);
+        bool thisRRNeedsWildcardProof = false;
+        if (gatherWildcardProof) {
+          if (auto wcIt = wildcardCandidates.find(tCacheEntry->first.name); wcIt != wildcardCandidates.end() && wcIt->second) {
+            thisRRNeedsWildcardProof = true;
+          }
+        }
+        g_recCache->replace(d_now.tv_sec, tCacheEntry->first.name, tCacheEntry->first.type, tCacheEntry->second.records, tCacheEntry->second.signatures, thisRRNeedsWildcardProof ? authorityRecs : std::vector<std::shared_ptr<DNSRecord>>(), tCacheEntry->first.type == QType::DS ? true : isAA, auth, tCacheEntry->first.place == DNSResourceRecord::ANSWER ? ednsmask : boost::none, d_routingTag, recordState, remoteIP, d_refresh, tCacheEntry->second.d_ttl_time);
 
         // Delete potential negcache entry. When a record recovers with serve-stale the negcache entry can cause the wrong entry to
         // be served, as negcache entries are checked before record cache entries
@@ -4669,10 +4703,11 @@ RCode::rcodes_ SyncRes::updateCacheFromRecords(unsigned int depth, const string&
           g_negCache->wipeTyped(tCacheEntry->first.name, tCacheEntry->first.type);
         }
 
-        if (g_aggressiveNSECCache && needWildcardProof && recordState == vState::Secure && tCacheEntry->first.place == DNSResourceRecord::ANSWER && tCacheEntry->first.name == qname && !tCacheEntry->second.signatures.empty() && !d_routingTag && !ednsmask) {
+        if (g_aggressiveNSECCache && thisRRNeedsWildcardProof && recordState == vState::Secure && tCacheEntry->first.place == DNSResourceRecord::ANSWER && !tCacheEntry->second.signatures.empty() && !d_routingTag && !ednsmask) {
           /* we have an answer synthesized from a wildcard and aggressive NSEC is enabled, we need to store the
              wildcard in its non-expanded form in the cache to be able to synthesize wildcard answers later */
           const auto& rrsig = tCacheEntry->second.signatures.at(0);
+          const auto labelCount = tCacheEntry->first.name.countLabels();
 
           if (isWildcardExpanded(labelCount, *rrsig) && !isWildcardExpandedOntoItself(tCacheEntry->first.name, labelCount, *rrsig)) {
             DNSName realOwner = getNSECOwnerName(tCacheEntry->first.name, tCacheEntry->second.signatures);
@@ -4705,6 +4740,13 @@ RCode::rcodes_ SyncRes::updateCacheFromRecords(unsigned int depth, const string&
     }
   }
 
+  if (gatherWildcardProof) {
+    if (auto wcIt = wildcardCandidates.find(qname); wcIt != wildcardCandidates.end() && !wcIt->second) {
+      // the queried name was not expanded from a wildcard, a record in the CNAME chain was, so we don't need to gather wildcard proof now: we will do that when looking up the CNAME chain
+      gatherWildcardProof = false;
+    }
+  }
+
   return RCode::NoError;
 }
 
index ac5cb196ec21b60b82384afa8f36da01da0d1429..862cd7d396cfa6d2f55112cc14189a3f9804d65c 100644 (file)
@@ -465,6 +465,11 @@ public:
     return d_queryValidationState;
   }
 
+  [[nodiscard]] bool getDNSSECLimitHit() const
+  {
+    return d_validationContext.d_limitHit;
+  }
+
   void setQueryReceivedOverTCP(bool tcp)
   {
     d_queryReceivedOverTCP = tcp;
index f91abdf07570c89d73be16d58235540ee3572da6..2981471f4012c25b7f91a22fd0592c559c9eda20 100644 (file)
@@ -84,8 +84,8 @@ BOOST_AUTO_TEST_CASE(test_filter_policies_basic)
     const auto matchingPolicy = dfe.getProcessingPolicy(DNSName("sub.sub.wildcard.wolf."), std::unordered_map<std::string, bool>(), DNSFilterEngine::maximumPriority);
     BOOST_CHECK(matchingPolicy.d_type == DNSFilterEngine::PolicyType::NSDName);
     BOOST_CHECK(matchingPolicy.d_kind == DNSFilterEngine::PolicyKind::Drop);
-    BOOST_CHECK_EQUAL(matchingPolicy.d_trigger, DNSName("*.wildcard.wolf.rpz-nsdname"));
-    BOOST_CHECK_EQUAL(matchingPolicy.d_hit, "sub.sub.wildcard.wolf");
+    BOOST_CHECK_EQUAL(matchingPolicy.d_hitdata->d_trigger, DNSName("*.wildcard.wolf.rpz-nsdname"));
+    BOOST_CHECK_EQUAL(matchingPolicy.d_hitdata->d_hit, "sub.sub.wildcard.wolf");
 
     /* looking for wildcard.wolf. should not match *.wildcard-blocked. */
     const auto notMatchingPolicy = dfe.getProcessingPolicy(DNSName("wildcard.wolf."), std::unordered_map<std::string, bool>(), DNSFilterEngine::maximumPriority);
@@ -97,8 +97,8 @@ BOOST_AUTO_TEST_CASE(test_filter_policies_basic)
     /* except if we look exactly for the wildcard */
     BOOST_CHECK(zone->findExactNSPolicy(nsWildcardName, zonePolicy));
     BOOST_CHECK(zonePolicy == matchingPolicy);
-    BOOST_CHECK_EQUAL(zonePolicy.d_trigger, DNSName("*.wildcard.wolf.rpz-nsdname"));
-    BOOST_CHECK_EQUAL(zonePolicy.d_hit, nsWildcardName.toStringNoDot());
+    BOOST_CHECK_EQUAL(zonePolicy.d_hitdata->d_trigger, DNSName("*.wildcard.wolf.rpz-nsdname"));
+    BOOST_CHECK_EQUAL(zonePolicy.d_hitdata->d_hit, nsWildcardName.toStringNoDot());
   }
 
   {
@@ -117,8 +117,8 @@ BOOST_AUTO_TEST_CASE(test_filter_policies_basic)
     DNSFilterEngine::Policy zonePolicy;
     BOOST_CHECK(zone->findNSIPPolicy(nsIP, zonePolicy));
     BOOST_CHECK(zonePolicy == matchingPolicy);
-    BOOST_CHECK_EQUAL(zonePolicy.d_trigger, DNSName("31.0.2.0.192.rpz-nsip"));
-    BOOST_CHECK_EQUAL(zonePolicy.d_hit, nsIP.toString());
+    BOOST_CHECK_EQUAL(zonePolicy.d_hitdata->d_trigger, DNSName("31.0.2.0.192.rpz-nsip"));
+    BOOST_CHECK_EQUAL(zonePolicy.d_hitdata->d_hit, nsIP.toString());
   }
 
   {
@@ -137,8 +137,8 @@ BOOST_AUTO_TEST_CASE(test_filter_policies_basic)
     DNSFilterEngine::Policy zonePolicy;
     BOOST_CHECK(zone->findExactQNamePolicy(blockedName, zonePolicy));
     BOOST_CHECK(zonePolicy == matchingPolicy);
-    BOOST_CHECK_EQUAL(zonePolicy.d_trigger, blockedName);
-    BOOST_CHECK_EQUAL(zonePolicy.d_hit, blockedName.toStringNoDot());
+    BOOST_CHECK_EQUAL(zonePolicy.d_hitdata->d_trigger, blockedName);
+    BOOST_CHECK_EQUAL(zonePolicy.d_hitdata->d_hit, blockedName.toStringNoDot());
 
     /* but a subdomain should not be blocked (not a wildcard, and this is not suffix domain matching */
     matchingPolicy = dfe.getQueryPolicy(DNSName("sub") + blockedName, std::unordered_map<std::string, bool>(), DNSFilterEngine::maximumPriority);
@@ -151,8 +151,8 @@ BOOST_AUTO_TEST_CASE(test_filter_policies_basic)
     const auto matchingPolicy = dfe.getQueryPolicy(DNSName("sub.sub.wildcard-blocked."), std::unordered_map<std::string, bool>(), DNSFilterEngine::maximumPriority);
     BOOST_CHECK(matchingPolicy.d_type == DNSFilterEngine::PolicyType::QName);
     BOOST_CHECK(matchingPolicy.d_kind == DNSFilterEngine::PolicyKind::Drop);
-    BOOST_CHECK_EQUAL(matchingPolicy.d_trigger, blockedWildcardName);
-    BOOST_CHECK_EQUAL(matchingPolicy.d_hit, "sub.sub.wildcard-blocked");
+    BOOST_CHECK_EQUAL(matchingPolicy.d_hitdata->d_trigger, blockedWildcardName);
+    BOOST_CHECK_EQUAL(matchingPolicy.d_hitdata->d_hit, "sub.sub.wildcard-blocked");
 
     /* looking for wildcard-blocked. should not match *.wildcard-blocked. */
     const auto notMatchingPolicy = dfe.getQueryPolicy(DNSName("wildcard-blocked."), std::unordered_map<std::string, bool>(), DNSFilterEngine::maximumPriority);
@@ -164,8 +164,8 @@ BOOST_AUTO_TEST_CASE(test_filter_policies_basic)
     /* except if we look exactly for the wildcard */
     BOOST_CHECK(zone->findExactQNamePolicy(blockedWildcardName, zonePolicy));
     BOOST_CHECK(zonePolicy == matchingPolicy);
-    BOOST_CHECK_EQUAL(zonePolicy.d_trigger, blockedWildcardName);
-    BOOST_CHECK_EQUAL(zonePolicy.d_hit, blockedWildcardName.toStringNoDot());
+    BOOST_CHECK_EQUAL(zonePolicy.d_hitdata->d_trigger, blockedWildcardName);
+    BOOST_CHECK_EQUAL(zonePolicy.d_hitdata->d_hit, blockedWildcardName.toStringNoDot());
   }
 
   {
@@ -176,8 +176,8 @@ BOOST_AUTO_TEST_CASE(test_filter_policies_basic)
     DNSFilterEngine::Policy zonePolicy;
     BOOST_CHECK(zone->findClientPolicy(clientIP, zonePolicy));
     BOOST_CHECK(zonePolicy == matchingPolicy);
-    BOOST_CHECK_EQUAL(zonePolicy.d_trigger, DNSName("31.128.2.0.192.rpz-client-ip"));
-    BOOST_CHECK_EQUAL(zonePolicy.d_hit, clientIP.toString());
+    BOOST_CHECK_EQUAL(zonePolicy.d_hitdata->d_trigger, DNSName("31.128.2.0.192.rpz-client-ip"));
+    BOOST_CHECK_EQUAL(zonePolicy.d_hitdata->d_hit, clientIP.toString());
   }
 
   {
@@ -200,8 +200,8 @@ BOOST_AUTO_TEST_CASE(test_filter_policies_basic)
     DNSFilterEngine::Policy zonePolicy;
     BOOST_CHECK(zone->findResponsePolicy(responseIP, zonePolicy));
     BOOST_CHECK(zonePolicy == matchingPolicy);
-    BOOST_CHECK_EQUAL(zonePolicy.d_trigger, DNSName("31.254.2.0.192.rpz-ip"));
-    BOOST_CHECK_EQUAL(zonePolicy.d_hit, responseIP.toString());
+    BOOST_CHECK_EQUAL(zonePolicy.d_hitdata->d_trigger, DNSName("31.254.2.0.192.rpz-ip"));
+    BOOST_CHECK_EQUAL(zonePolicy.d_hitdata->d_hit, responseIP.toString());
   }
 
   {
index 1bb73faa60f15086b29a6302dcf3557a95ba74f7..0939c12ba2855ac87e4f3903ab546e7e1be9dc80 100644 (file)
@@ -1257,6 +1257,79 @@ BOOST_AUTO_TEST_CASE(test_forward_zone_recurse_rd_dnssec_nodata_bogus)
   BOOST_CHECK_EQUAL(queriesCount, 4U);
 }
 
+BOOST_AUTO_TEST_CASE(test_forward_zone_recurse_rd_dnssec_cname_wildcard_expanded)
+{
+  std::unique_ptr<SyncRes> testSR;
+  initSR(testSR, true);
+
+  setDNSSECValidation(testSR, DNSSECMode::ValidateAll);
+
+  primeHints();
+  /* unsigned */
+  const DNSName target("test.");
+  /* signed */
+  const DNSName cnameTarget("cname.");
+  testkeysset_t keys;
+
+  auto luaconfsCopy = g_luaconfs.getCopy();
+  luaconfsCopy.dsAnchors.clear();
+  generateKeyMaterial(g_rootdnsname, DNSSECKeeper::ECDSA256, DNSSECKeeper::DIGEST_SHA256, keys, luaconfsCopy.dsAnchors);
+  generateKeyMaterial(cnameTarget, DNSSECKeeper::ECDSA256, DNSSECKeeper::DIGEST_SHA256, keys);
+  g_luaconfs.setState(luaconfsCopy);
+
+  const ComboAddress forwardedNS("192.0.2.42:53");
+  size_t queriesCount = 0;
+
+  SyncRes::AuthDomain authDomain;
+  authDomain.d_rdForward = true;
+  authDomain.d_servers.push_back(forwardedNS);
+  (*SyncRes::t_sstorage.domainmap)[g_rootdnsname] = authDomain;
+
+  testSR->setAsyncCallback([&](const ComboAddress& address, const DNSName& domain, int type, bool /* doTCP */, bool sendRDQuery, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
+    queriesCount++;
+
+    BOOST_CHECK_EQUAL(sendRDQuery, true);
+
+    if (address != forwardedNS) {
+      return LWResult::Result::Timeout;
+    }
+
+    if (type == QType::DS || type == QType::DNSKEY) {
+      return genericDSAndDNSKEYHandler(res, domain, DNSName("."), type, keys);
+    }
+
+    if (domain == target && type == QType::A) {
+
+      setLWResult(res, 0, false, false, true);
+      addRecordToLW(res, target, QType::CNAME, cnameTarget.toString());
+      addRecordToLW(res, cnameTarget, QType::A, "192.0.2.1");
+      /* the RRSIG proves that the cnameTarget was expanded from a wildcard */
+      addRRSIG(keys, res->d_records, cnameTarget, 300, false, boost::none, DNSName("*"));
+      /* we need to add the proof that this name does not exist, so the wildcard may apply */
+      addNSECRecordToLW(DNSName("cnamd."), DNSName("cnamf."), {QType::A, QType::NSEC, QType::RRSIG}, 60, res->d_records);
+      addRRSIG(keys, res->d_records, cnameTarget, 300);
+
+      return LWResult::Result::Success;
+    }
+    return LWResult::Result::Timeout;
+  });
+
+  vector<DNSRecord> ret;
+  int res = testSR->beginResolve(target, QType(QType::A), QClass::IN, ret);
+  BOOST_CHECK_EQUAL(res, RCode::NoError);
+  BOOST_CHECK_EQUAL(testSR->getValidationState(), vState::Insecure);
+  BOOST_REQUIRE_EQUAL(ret.size(), 5U);
+  BOOST_CHECK_EQUAL(queriesCount, 5U);
+
+  /* again, to test the cache */
+  ret.clear();
+  res = testSR->beginResolve(target, QType(QType::A), QClass::IN, ret);
+  BOOST_CHECK_EQUAL(res, RCode::NoError);
+  BOOST_CHECK_EQUAL(testSR->getValidationState(), vState::Insecure);
+  BOOST_REQUIRE_EQUAL(ret.size(), 5U);
+  BOOST_CHECK_EQUAL(queriesCount, 5U);
+}
+
 BOOST_AUTO_TEST_CASE(test_auth_zone_oob)
 {
   std::unique_ptr<SyncRes> sr;
diff --git a/pdns/recursordist/views.hh b/pdns/recursordist/views.hh
new file mode 120000 (symlink)
index 0000000..2213b7d
--- /dev/null
@@ -0,0 +1 @@
+../views.hh
\ No newline at end of file
index 841d9e3217e314f487ca8312862b59b9c80f310e..b048c9d1b12405474f7a3bd79e320d1da0cc8ced 100644 (file)
@@ -876,25 +876,28 @@ private:
     if (!arg) {
       return SSL_TLSEXT_ERR_ALERT_WARNING;
     }
+    // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast): OpenSSL's API
     OpenSSLTLSIOCtx* obj = reinterpret_cast<OpenSSLTLSIOCtx*>(arg);
 
-    size_t pos = 0;
-    while (pos < inlen) {
-      size_t protoLen = in[pos];
-      pos++;
-      if (protoLen > (inlen - pos)) {
-        /* something is very wrong */
-        return SSL_TLSEXT_ERR_ALERT_WARNING;
-      }
+    const pdns::views::UnsignedCharView inView(in, inlen);
+    // Server preference algorithm as per RFC 7301 section 3.2
+    for (const auto& tentative : obj->d_alpnProtos) {
+      size_t pos = 0;
+      while (pos < inView.size()) {
+        size_t protoLen = inView.at(pos);
+        pos++;
+        if (protoLen > (inlen - pos)) {
+          /* something is very wrong */
+          return SSL_TLSEXT_ERR_ALERT_WARNING;
+        }
 
-      for (const auto& tentative : obj->d_alpnProtos) {
-        if (tentative.size() == protoLen && memcmp(in + pos, tentative.data(), tentative.size()) == 0) {
-          *out = in + pos;
+        if (tentative.size() == protoLen && memcmp(&inView.at(pos), tentative.data(), tentative.size()) == 0) {
+          *out = &inView.at(pos);
           *outlen = protoLen;
           return SSL_TLSEXT_ERR_OK;
         }
+        pos += protoLen;
       }
-      pos += protoLen;
     }
 
     return SSL_TLSEXT_ERR_NOACK;
index 16d144e643b0a4d9afdcf937f51464fda4bd94ab..6833ab9baa14330139eafb0bfcd8aec5c1fe49a3 100644 (file)
@@ -176,6 +176,7 @@ bool denialProvesNoDelegation(const DNSName& zone, const std::vector<DNSRecord>&
       }
 
       if (g_maxNSEC3sPerRecordToConsider > 0 && nsec3sConsidered >= g_maxNSEC3sPerRecordToConsider) {
+        context.d_limitHit = true;
         return false;
       }
       nsec3sConsidered++;
@@ -704,6 +705,7 @@ dState getDenial(const cspmap_t &validrrsets, const DNSName& qname, const uint16
 
         if (g_maxNSEC3sPerRecordToConsider > 0 && nsec3sConsidered >= g_maxNSEC3sPerRecordToConsider) {
           VLOG(log, qname << ": Too many NSEC3s for this record"<<endl);
+          context.d_limitHit = true;          
           return dState::NODENIAL;
         }
         nsec3sConsidered++;
@@ -805,6 +807,7 @@ dState getDenial(const cspmap_t &validrrsets, const DNSName& qname, const uint16
 
             if (g_maxNSEC3sPerRecordToConsider > 0 && nsec3sConsidered >= g_maxNSEC3sPerRecordToConsider) {
               VLOG(log, qname << ": Too many NSEC3s for this record"<<endl);
+              context.d_limitHit = true;
               return dState::NODENIAL;
             }
             nsec3sConsidered++;
@@ -891,6 +894,7 @@ dState getDenial(const cspmap_t &validrrsets, const DNSName& qname, const uint16
 
             if (g_maxNSEC3sPerRecordToConsider > 0 && nsec3sConsidered >= g_maxNSEC3sPerRecordToConsider) {
               VLOG(log, qname << ": Too many NSEC3s for this record"<<endl);
+              context.d_limitHit = true;
               return dState::NODENIAL;
             }
             nsec3sConsidered++;
@@ -1031,6 +1035,7 @@ vState validateWithKeySet(time_t now, const DNSName& name, const sortedRecords_t
     if (g_maxRRSIGsPerRecordToConsider > 0 && signaturesConsidered >= g_maxRRSIGsPerRecordToConsider) {
       VLOG(log, name<<": We have already considered "<<std::to_string(signaturesConsidered)<<" RRSIG"<<addS(signaturesConsidered)<<" for this record, stopping now"<<endl;);
       // possibly going Bogus, the RRSIGs have not been validated so Insecure would be wrong
+      context.d_limitHit = true;
       break;
     }
     signaturesConsidered++;
@@ -1049,6 +1054,9 @@ vState validateWithKeySet(time_t now, const DNSName& name, const sortedRecords_t
     for (const auto& key : keysMatchingTag) {
       if (g_maxDNSKEYsToConsider > 0 && dnskeysConsidered >= g_maxDNSKEYsToConsider) {
         VLOG(log, name << ": We have already considered "<<std::to_string(dnskeysConsidered)<<" DNSKEY"<<addS(dnskeysConsidered)<<" for tag "<<std::to_string(signature->d_tag)<<" and algorithm "<<std::to_string(signature->d_algorithm)<<", not considering the remaining ones for this signature"<<endl;);
+        if (!isValid) {
+          context.d_limitHit = true;
+        }
         return isValid ? vState::Secure : vState::BogusNoValidRRSIG;
       }
       dnskeysConsidered++;
@@ -1175,6 +1183,7 @@ vState validateDNSKeysAgainstDS(time_t now, const DNSName& zone, const dsmap_t&
         // we need to break because we can have a partially validated set
         // where the KSK signs the ZSK(s), and even if we don't
         // we are going to try to get the correct EDE status (revoked, expired, ...)
+        context.d_limitHit = true;
         break;
       }
       dnskeysConsidered++;
@@ -1227,6 +1236,7 @@ vState validateDNSKeysAgainstDS(time_t now, const DNSName& zone, const dsmap_t&
       if (g_maxRRSIGsPerRecordToConsider > 0 && signaturesConsidered >= g_maxRRSIGsPerRecordToConsider) {
         VLOG(log, zone << ": We have already considered "<<std::to_string(signaturesConsidered)<<" RRSIG"<<addS(signaturesConsidered)<<" for this record, stopping now"<<endl;);
         // possibly going Bogus, the RRSIGs have not been validated so Insecure would be wrong
+        context.d_limitHit = true;
         return vState::BogusNoValidDNSKEY;
       }
 
@@ -1235,6 +1245,7 @@ vState validateDNSKeysAgainstDS(time_t now, const DNSName& zone, const dsmap_t&
       for (const auto& key : bytag) {
         if (g_maxDNSKEYsToConsider > 0 && dnskeysConsidered >= g_maxDNSKEYsToConsider) {
           VLOG(log, zone << ": We have already considered "<<std::to_string(dnskeysConsidered)<<" DNSKEY"<<addS(dnskeysConsidered)<<" for tag "<<std::to_string(sig->d_tag)<<" and algorithm "<<std::to_string(sig->d_algorithm)<<", not considering the remaining ones for this signature"<<endl;);
+          context.d_limitHit = true;
           return vState::BogusNoValidDNSKEY;
         }
         dnskeysConsidered++;
@@ -1242,6 +1253,7 @@ vState validateDNSKeysAgainstDS(time_t now, const DNSName& zone, const dsmap_t&
         if (g_maxRRSIGsPerRecordToConsider > 0 && signaturesConsidered >= g_maxRRSIGsPerRecordToConsider) {
           VLOG(log, zone << ": We have already considered "<<std::to_string(signaturesConsidered)<<" RRSIG"<<addS(signaturesConsidered)<<" for this record, stopping now"<<endl;);
           // possibly going Bogus, the RRSIGs have not been validated so Insecure would be wrong
+          context.d_limitHit = true;
           return vState::BogusNoValidDNSKEY;
         }
         //          cerr<<"validating : ";
index 7d844bf11c6d07ce66fe5137139a85512aa998cb..034839fca052173c250448da3ac8dc488c0cc04c 100644 (file)
@@ -90,6 +90,7 @@ struct ValidationContext
   Nsec3HashesCache d_nsec3Cache;
   unsigned int d_validationsCounter{0};
   unsigned int d_nsec3IterationsRemainingQuota{0};
+  bool d_limitHit{false};
 };
 
 class TooManySEC3IterationsException : public std::runtime_error
diff --git a/pdns/views.hh b/pdns/views.hh
new file mode 100644 (file)
index 0000000..c3c8c89
--- /dev/null
@@ -0,0 +1,55 @@
+/*
+ * This file is part of PowerDNS or dnsdist.
+ * Copyright -- PowerDNS.COM B.V. and its contributors
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ *
+ * In addition, for the avoidance of any doubt, permission is granted to
+ * link this program with OpenSSL and to (re)distribute the binaries
+ * produced as the result of such linking.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+#pragma once
+
+#include <string_view>
+
+namespace pdns::views
+{
+
+class UnsignedCharView
+{
+public:
+  UnsignedCharView(const char* data_, size_t size_) :
+    view(data_, size_)
+  {
+  }
+  // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast): No unsigned char view in C++17
+  UnsignedCharView(const unsigned char* data_, size_t size_) :
+    view(reinterpret_cast<const char*>(data_), size_)
+  {
+  }
+  const unsigned char& at(std::string_view::size_type pos) const
+  {
+    return reinterpret_cast<const unsigned char&>(view.at(pos));
+  }
+
+  size_t size() const
+  {
+    return view.size();
+  }
+
+private:
+  std::string_view view;
+};
+
+}
index d0cb88513a7849b38652712c7099a7e1a0c3fdd6..72f4791dd8a3956759362a0154d631dcdcc29f63 100644 (file)
 #include <fcntl.h>
 #include <iterator>
 #include <linux/bpf.h>
+#include <linux/if_ether.h>
 #include <linux/if_link.h>
 #include <linux/if_xdp.h>
 #include <linux/ip.h>
 #include <linux/ipv6.h>
 #include <linux/tcp.h>
+#include <linux/types.h>
+#include <linux/udp.h>
 #include <net/if.h>
 #include <net/if_arp.h>
 #include <netinet/in.h>
index e181e63823827e14e9c922099a579e660c805721..8d2b57d2d95c2379e4b18b2edf0c48520d050035 100644 (file)
@@ -39,9 +39,6 @@
 #include <unistd.h>
 #include <unordered_map>
 #include <vector>
-#include <linux/if_ether.h>
-#include <linux/types.h>
-#include <linux/udp.h>
 
 #include <xdp/xsk.h>
 
@@ -185,8 +182,10 @@ public:
   }
 };
 
+struct ethhdr;
 struct iphdr;
 struct ipv6hdr;
+struct udphdr;
 
 class XskPacket
 {
index 7fc186d982d47eb2580f1262bea8c45d32e1b4a9..69d76da1fd0624556bb36ee80c443eb26aef0958 100644 (file)
@@ -402,6 +402,18 @@ class DOHTests(object):
         self.assertEqual(rcode, 400)
         self.assertEqual(data, b'<html><body>This server implements RFC 8484 - DNS Queries over HTTP, and requires HTTP/2 in accordance with section 5.2 of the RFC.</body></html>\r\n')
 
+    def testDOHHTTP1NotSelectedOverH2(self):
+        """
+        DOH: Check that HTTP/1.1 is not selected over H2 when offered in the wrong order by the client
+        """
+        if self._dohLibrary == 'h2o':
+            raise unittest.SkipTest('h2o supports HTTP/1.1, this test is only relevant for nghttp2')
+        alpn = ['http/1.1', 'h2']
+        conn = self.openTLSConnection(self._dohServerPort, self._serverName, self._caCert, alpn=alpn)
+        if not hasattr(conn, 'selected_alpn_protocol'):
+            raise unittest.SkipTest('Unable to check the selected ALPN, Python version is too old to support selected_alpn_protocol')
+        self.assertEqual(conn.selected_alpn_protocol(), 'h2')
+
     def testDOHInvalid(self):
         """
         DOH: Invalid DNS query
index e130a3d0dde037266a734e1c379f86b5c13d4f15..79dfd0060ee9da046ec27fa0bab0b9645a2196ec 100644 (file)
@@ -108,7 +108,10 @@ version-string=%s
         response = self.sendUDPQuery(query)
 
         self.assertEqual(len(response.options), 1)
-        self.assertEqual(response.options[0].data, self._servername.encode('ascii'))
+        if dns.version.MAJOR < 2 or (dns.version.MAJOR == 2 and dns.version.MINOR < 6):
+            self.assertEqual(response.options[0].data, self._servername.encode('ascii'))
+        else:
+            self.assertEqual(response.options[0].to_text(), 'NSID ' + self._servername)
 
     def testNSIDTCP(self):
         """
@@ -119,4 +122,7 @@ version-string=%s
         response = self.sendTCPQuery(query)
 
         self.assertEqual(len(response.options), 1)
-        self.assertEqual(response.options[0].data, self._servername.encode('ascii'))
+        if dns.version.MAJOR < 2 or (dns.version.MAJOR == 2 and dns.version.MINOR < 6):
+            self.assertEqual(response.options[0].data, self._servername.encode('ascii'))
+        else:
+            self.assertEqual(response.options[0].to_text(), 'NSID ' + self._servername)