]> git.ipfire.org Git - thirdparty/pdns.git/commitdiff
Merge pull request #6152 from zeha/bb2-note-error
authorPieter Lexis <pieterlexis@users.noreply.github.com>
Thu, 25 Jan 2018 11:46:47 +0000 (12:46 +0100)
committerGitHub <noreply@github.com>
Thu, 25 Jan 2018 11:46:47 +0000 (12:46 +0100)
bindbackend: handle std::exception during startup zone-parsing

137 files changed:
build-scripts/build-auth-rpm
build-scripts/travis.sh
docs/manpages/dnsreplay.1.rst
docs/manpages/pdnsutil.1.rst
docs/secpoll.zone
docs/upgrading.rst
modules/gmysqlbackend/4.1.0_to_4.1.1_schema.mysql.sql [new file with mode: 0644]
modules/gmysqlbackend/Makefile.am
modules/gmysqlbackend/schema.mysql.sql
modules/ldapbackend/ldapbackend.cc
modules/luabackend/luabackend.hh
modules/luabackend/minimal.cc
modules/mydnsbackend/mydnsbackend.cc
modules/mydnsbackend/mydnsbackend.hh
modules/opendbxbackend/odbxbackend.cc
modules/opendbxbackend/odbxbackend.hh
modules/remotebackend/Gemfile.lock
modules/remotebackend/regression-tests/Gemfile.lock
modules/remotebackend/remotebackend.cc
modules/remotebackend/remotebackend.hh
modules/remotebackend/test-remotebackend.cc
pdns/backends/gsql/gsqlbackend.cc
pdns/backends/gsql/gsqlbackend.hh
pdns/backends/gsql/ssql.hh
pdns/bpf-filter.cc
pdns/dbdnsseckeeper.cc
pdns/dns.cc
pdns/dns.hh
pdns/dnsbackend.cc
pdns/dnsbackend.hh
pdns/dnsdist-cache.cc
pdns/dnsdist-cache.hh
pdns/dnsdist-console.cc
pdns/dnsdist-lua-actions.cc
pdns/dnsdist-lua-bindings-dnsquestion.cc
pdns/dnsdist-lua-rules.cc
pdns/dnsdist-lua-vars.cc
pdns/dnsdist-lua.cc
pdns/dnsdist-lua.hh
pdns/dnsdist-tcp.cc
pdns/dnsdist-web.cc
pdns/dnsdist.cc
pdns/dnsdist.hh
pdns/dnsdistdist/Makefile.am
pdns/dnsdistdist/configure.ac
pdns/dnsdistdist/docs/guides/serverpools.rst
pdns/dnsdistdist/docs/guides/webserver.rst
pdns/dnsdistdist/docs/reference/config.rst
pdns/dnsdistdist/docs/reference/constants.rst
pdns/dnsdistdist/docs/rules-actions.rst
pdns/dnsdistdist/m4/dnsdist_check_gnutls.m4 [new file with mode: 0644]
pdns/dnsdistdist/m4/dnsdist_check_libssl.m4 [new file with mode: 0644]
pdns/dnsdistdist/m4/dnsdist_enable_tls.m4 [new file with mode: 0644]
pdns/dnsdistdist/m4/pdns_check_libcrypto.m4 [new symlink]
pdns/dnsdistdist/tcpiohandler.cc [new file with mode: 0644]
pdns/dnsdistdist/tcpiohandler.hh [new file with mode: 0644]
pdns/dnsdistdist/xpf.cc [new symlink]
pdns/dnsdistdist/xpf.hh [new symlink]
pdns/dnsname.cc
pdns/dnspacket.cc
pdns/dnspacket.hh
pdns/dnsparser.cc
pdns/dnsparser.hh
pdns/dnsproxy.cc
pdns/dnsproxy.hh
pdns/dnsrecords.cc
pdns/dnsrecords.hh
pdns/dnsreplay.cc
pdns/dnssecinfra.cc
pdns/dnsseckeeper.hh
pdns/dnswriter.hh
pdns/ednsoptions.hh
pdns/iputils.cc
pdns/iputils.hh
pdns/ixfr.cc
pdns/kqueuemplexer.cc
pdns/misc.cc
pdns/mtasker.hh
pdns/packethandler.cc
pdns/pdns_recursor.cc
pdns/pdnsutil.cc
pdns/rcpgenerator.cc
pdns/rcpgenerator.hh
pdns/recursordist/Makefile.am
pdns/recursordist/docs/changelog/4.1.rst
pdns/recursordist/docs/lua-config/rpz.rst
pdns/recursordist/docs/security-advisories/powerdns-advisory-2018-01.rst [new file with mode: 0644]
pdns/recursordist/docs/settings.rst
pdns/recursordist/docs/upgrade.rst
pdns/recursordist/negcache.cc
pdns/recursordist/test-ednsoptions_cc.cc
pdns/recursordist/test-syncres_cc.cc
pdns/recursordist/test-xpf_cc.cc [new file with mode: 0644]
pdns/recursordist/xpf.cc [new symlink]
pdns/recursordist/xpf.hh [new symlink]
pdns/reczones.cc
pdns/remote_logger.cc
pdns/remote_logger.hh
pdns/resolver.cc
pdns/resolver.hh
pdns/rfc2136handler.cc
pdns/rpzloader.cc
pdns/sdig.cc
pdns/serialtweaker.cc
pdns/slavecommunicator.cc
pdns/syncres.cc
pdns/syncres.hh
pdns/tcpreceiver.cc
pdns/test-dnsdistpacketcache_cc.cc
pdns/test-dnsname_cc.cc
pdns/test-dnsrecords_cc.cc
pdns/tsig-tests.cc
pdns/tsigverifier.cc
pdns/ueberbackend.cc
pdns/ueberbackend.hh
pdns/validate.cc
pdns/ws-auth.cc
pdns/ws-recursor.cc
pdns/xpf.cc [new file with mode: 0644]
pdns/xpf.hh [new file with mode: 0644]
pdns/zoneparser-tng.cc
regression-tests.api/test_Zones.py
regression-tests.dnsdist/configCA.conf [new file with mode: 0644]
regression-tests.dnsdist/configServer.conf [new file with mode: 0644]
regression-tests.dnsdist/dnsdisttests.py
regression-tests.dnsdist/runtests
regression-tests.dnsdist/test_API.py
regression-tests.dnsdist/test_Advanced.py
regression-tests.dnsdist/test_Caching.py
regression-tests.dnsdist/test_EdnsClientSubnet.py
regression-tests.dnsdist/test_Responses.py
regression-tests.dnsdist/test_SelfAnsweredResponses.py [new file with mode: 0644]
regression-tests.dnsdist/test_TLS.py [new file with mode: 0644]
regression-tests.dnsdist/test_XPF.py [new file with mode: 0644]
regression-tests.recursor-dnssec/recursortests.py
regression-tests.recursor-dnssec/runtests
regression-tests.recursor-dnssec/test_ECS.py

index a02575c24aa7f7257bc39f641b5b89fabc6528df..62adc374c1ece841a1be4d547cf46fcdb864ff2d 100755 (executable)
@@ -471,6 +471,7 @@ fi
 %doc %{_defaultdocdir}/%{name}/nodnssec-3.x_to_3.4.0_schema.mysql.sql
 %doc %{_defaultdocdir}/%{name}/dnssec-3.x_to_3.4.0_schema.mysql.sql
 %doc %{_defaultdocdir}/%{name}/3.4.0_to_4.1.0_schema.mysql.sql
+%doc %{_defaultdocdir}/%{name}/4.1.0_to_4.1.1_schema.mysql.sql
 %{_libdir}/%{name}/libgmysqlbackend.so
 
 %files backend-postgresql
index 0c6dc42ee639aeab7d48d8e57d5445087a882e4c..bf5f2636a31ce99e0a416c0966ccb1e26cbbb407 100755 (executable)
@@ -407,6 +407,7 @@ build_dnsdist(){
     --enable-unit-tests \
     --enable-libsodium \
     --enable-dnscrypt \
+    --enable-dns-over-tls \
     --prefix=$HOME/dnsdist \
     --disable-silent-rules"
   run "make -k -j3"
index 76367df92bc4b5b46846754ac40ccb00efb3ee1b..15f796cb993d1dfffe42f129e3b93371583d7f64 100644 (file)
@@ -33,6 +33,7 @@ PORT
 --ecs-mask <VAL>         When EDNS forwarding an IP address, mask out first octet with this value
 --ecs-stamp <FLAG>       Add original IP address as EDNS Client Subnet Option when 
                          forwarding to reference server
+--pcap-dns-port <VAL>    Look at packets from or to this port in the PCAP. Default is 53.
 --packet-limit <NUM>     Stop after replaying *NUM* packets. Default for *NUM* is 0, which
                          means no limit.
 --quiet <FLAG>           If *FLAG* is set to 1. dnsreplay will not be very noisy with its
index 132de940d9c555fea1192d0191d8123ca0bc359a..32bcd72a8eb5abf68802f8de164b049c9e17429e 100644 (file)
@@ -224,6 +224,10 @@ backend-cmd *BACKEND* *CMD* [*CMD..*]
     Send a text command to a backend for execution. GSQL backends will
     take SQL commands, other backends may take different things. Be
     careful!
+bench-db [*FILE*]
+    Perform a benchmark of the backend-database.
+    *FILE* can be a file with a list, one per line, of domain names to use for this.
+    If *FILE* is not specified, powerdns.com is used.
 
 See also
 --------
index 00a822d0f36faf1ac5816fec602c9b04ce45c2fc..c95617e402d0710d107de7b980f9e529c93ca9b0 100644 (file)
@@ -1,4 +1,4 @@
-@       86400   IN  SOA pdns-public-ns1.powerdns.com. pieter\.lexis.powerdns.com. 2017121101 10800 3600 604800 10800
+@       86400   IN  SOA pdns-public-ns1.powerdns.com. pieter\.lexis.powerdns.com. 2018012201 10800 3600 604800 10800
 @       3600    IN  NS  pdns-public-ns1.powerdns.com.
 @       3600    IN  NS  pdns-public-ns2.powerdns.com.
 ; Auth
@@ -148,7 +148,8 @@ recursor-4.1.0-alpha1.security-status                   60 IN TXT "3 Unsupported
 recursor-4.1.0-rc1.security-status                      60 IN TXT "3 Unsupported pre-release (final release is out)"
 recursor-4.1.0-rc2.security-status                      60 IN TXT "3 Unsupported pre-release (final release is out)"
 recursor-4.1.0-rc3.security-status                      60 IN TXT "3 Unsupported pre-release (final release is out)"
-recursor-4.1.0.security-status                          60 IN TXT "1 OK"
+recursor-4.1.0.security-status                          60 IN TXT "3 Upgrade now, see https://doc.powerdns.com/recursor/security-advisories/powerdns-advisory-2018-01.html"
+recursor-4.1.1.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 3d979d5b8c79a4a49b5aefdd277a9f54257d4a48..a9ae0de6cf789079baae3de53d423e5799f7a884 100644 (file)
@@ -8,6 +8,16 @@ Please upgrade to the PowerDNS Authoritative Server 4.0.0 from 3.4.2+.
 See the `3.X <https://doc.powerdns.com/3/authoritative/upgrading/>`__
 upgrade notes if your version is older than 3.4.2.
 
+4.1.0 to 4.1.1
+--------------
+
+- The :doc:`Generic MySQL backend <backends/generic-mysql>` schema has
+  changed: the ``notified_serial`` column default in the ``domains``
+  table has been changed from ``INT DEFAULT NULL`` to ``INT UNSIGNED
+  DEFAULT NULL``:
+
+  - ``ALTER TABLE domains MODIFY notified_serial INT UNSIGNED DEFAULT NULL;``
+
 4.0.X to 4.1.0
 --------------
 
diff --git a/modules/gmysqlbackend/4.1.0_to_4.1.1_schema.mysql.sql b/modules/gmysqlbackend/4.1.0_to_4.1.1_schema.mysql.sql
new file mode 100644 (file)
index 0000000..6745c63
--- /dev/null
@@ -0,0 +1 @@
+ALTER TABLE domains MODIFY notified_serial INT UNSIGNED DEFAULT NULL;
index 43c09c90425d2cc4fa76e360d86aedf5b35fb671..aea6142cd7526ba2e19d08441f6025e80689e885 100644 (file)
@@ -13,6 +13,7 @@ dist_doc_DATA = \
        dnssec-3.x_to_3.4.0_schema.mysql.sql \
        nodnssec-3.x_to_3.4.0_schema.mysql.sql \
        3.4.0_to_4.1.0_schema.mysql.sql \
+       4.1.0_to_4.1.1_schema.mysql.sql \
        schema.mysql.sql
 
 libgmysqlbackend_la_SOURCES = \
index bf15329b90aa3a9b626be66ba9f557594f916cf4..1c6eee5a5b4e8dd09d0b5dc71e7d78d01c4039a0 100644 (file)
@@ -4,7 +4,7 @@ CREATE TABLE domains (
   master                VARCHAR(128) DEFAULT NULL,
   last_check            INT DEFAULT NULL,
   type                  VARCHAR(6) NOT NULL,
-  notified_serial       INT DEFAULT NULL,
+  notified_serial       INT UNSIGNED DEFAULT NULL,
   account               VARCHAR(40) CHARACTER SET 'utf8' DEFAULT NULL,
   PRIMARY KEY (id)
 ) Engine=InnoDB CHARACTER SET 'latin1';
index 7ae451bc8fe10805307f698c4ebcb92f93249a4a..2998387a3f9659c3041218be71c42878f3677ff1 100644 (file)
@@ -586,8 +586,6 @@ void LdapBackend::getUpdatedMasters( vector<DomainInfo>* domains )
     if ( !getDomainInfo( DNSName( result["associatedDomain"][0] ), di ) )
       continue;
 
-    di.backend = this;
-
     if( di.notified_serial < di.serial )
       domains->push_back( di );
   }
@@ -681,7 +679,6 @@ bool LdapBackend::getDomainInfo( const DNSName& domain, DomainInfo& di )
 {
   string filter;
   SOAData sd;
-  int msgid=0;
   PowerLDAP::sentry_t result;
   const char* attronly[] = {
     "sOARecord",
@@ -698,7 +695,7 @@ bool LdapBackend::getDomainInfo( const DNSName& domain, DomainInfo& di )
     // search for SOARecord of domain
     filter = "(&(associatedDomain=" + toLower( m_pldap->escape( domain.toStringRootDot() ) ) + ")(SOARecord=*))";
     m_msgid = m_pldap->search( getArg( "basedn" ), LDAP_SCOPE_SUBTREE, filter, attronly );
-    m_pldap->getSearchEntry( msgid, result );
+    m_pldap->getSearchEntry( m_msgid, result );
   }
   catch( LDAPTimeout &lt )
   {
@@ -762,6 +759,7 @@ bool LdapBackend::getDomainInfo( const DNSName& domain, DomainInfo& di )
       di.kind = DomainInfo::Native;
     }
 
+    di.backend = this;
     return true;
   }
 
index 52e26ea96f4ab8a2b317ca5af575a410772d542a..b96fdadff4d12de29eea13c69f7b6da70dbaf4d6 100644 (file)
@@ -58,7 +58,7 @@ public:
     void lookup(const QType &qtype, const DNSName &qname, DNSPacket *p, int domain_id) override;
     bool get(DNSResourceRecord &rr) override;
     //! fills the soadata struct with the SOA details. Returns false if there is no SOA.
-    bool getSOA(const DNSName &name, SOAData &soadata) override;
+    bool getSOA(const DNSName &name, SOAData &soadata, bool unmodifiedSerial) override;
 
 
 //  MASTER BACKEND
index 1f7015881458e45b78d19914ab0127879bed1f68..24b3e7e4afe581c7d864b0a3615234f7247ff662 100644 (file)
@@ -176,7 +176,7 @@ bool LUABackend::get(DNSResourceRecord &rr) {
     return !rr.content.empty();
 }
 
-bool LUABackend::getSOA(const DNSName &name, SOAData &soadata) {
+bool LUABackend::getSOA(const DNSName &name, SOAData &soadata, bool unmodifiedSerial) {
     if (logging)
        L << Logger::Info << backend_name << "(getsoa) BEGIN" << endl;
 
index ec521ceb49a2e7b4b085a039efd0b0722683151e..8f57af2309cf5a5ac88c2d90bb40e026e95e99dc 100644 (file)
@@ -196,7 +196,7 @@ bool MyDNSBackend::list(const DNSName &target, int zoneId, bool include_disabled
   return true;
 }
 
-bool MyDNSBackend::getSOA(const DNSName& name, SOAData& soadata) {
+bool MyDNSBackend::getSOA(const DNSName& name, SOAData& soadata, bool unmodifiedSerial) {
   string query;
   SSqlStatement::row_t rrow;
 
@@ -211,7 +211,7 @@ bool MyDNSBackend::getSOA(const DNSName& name, SOAData& soadata) {
       reset();
   }
   catch (SSqlException &e) {
-    throw PDNSException("MyDNSBackend unable to get soa for domain "+name.toString()+": "+e.txtReason());
+    throw PDNSException("MyDNSBackend unable to get soa for domain "+name.toLogString()+": "+e.txtReason());
   }
 
   if (d_result.empty()) {
@@ -269,7 +269,7 @@ void MyDNSBackend::lookup(const QType &qtype, const DNSName &qname, DNSPacket *p
           reset();
       }
       catch (SSqlException &e) {
-        throw PDNSException("MyDNSBackend unable to lookup "+qname.toString()+": "+e.txtReason());
+        throw PDNSException("MyDNSBackend unable to lookup "+qname.toLogString()+": "+e.txtReason());
       }
 
       if (d_result.empty() == false) {
@@ -292,7 +292,7 @@ void MyDNSBackend::lookup(const QType &qtype, const DNSName &qname, DNSPacket *p
         reset();
     }
     catch (SSqlException &e) {
-      throw PDNSException("MyDNSBackend unable to lookup "+qname.toString()+": "+e.txtReason());
+      throw PDNSException("MyDNSBackend unable to lookup "+qname.toLogString()+": "+e.txtReason());
     }
 
     if(d_result.empty()) {
@@ -342,7 +342,7 @@ void MyDNSBackend::lookup(const QType &qtype, const DNSName &qname, DNSPacket *p
       }
     }
     catch (SSqlException &e) {
-      throw PDNSException("MyDNSBackend unable to lookup "+qname.toString()+": "+e.txtReason());
+      throw PDNSException("MyDNSBackend unable to lookup "+qname.toLogString()+": "+e.txtReason());
     }
 
     d_qname = qname.toString();
index 6bd1e13d3401e6a59cebb4a25e7ba6e5d28ab8ed..434b51f2e88c48f1b3b387f937c733d564888e39 100644 (file)
@@ -39,8 +39,9 @@ public:
   void lookup(const QType &, const DNSName &qdomain, DNSPacket *p=0, int zoneId=-1) override;
   bool list(const DNSName &target, int domain_id, bool include_disabled=false) override;
   bool get(DNSResourceRecord &r) override;
-  bool getSOA(const DNSName& name, SOAData& soadata) override;
+  bool getSOA(const DNSName& name, SOAData& soadata, bool unmodifiedSerial) override;
   void getAllDomains(vector<DomainInfo> *domains, bool include_disabled=false) override;
+
 private:
   SMySQL *d_db; 
 
index 06fb890a892a1225d7d562c11ea1e69b3d70b3f3..e8d37f1cc99d0442cf342dc08ca98f26a1697916 100644 (file)
@@ -179,7 +179,7 @@ bool OdbxBackend::getDomainInfo( const DNSName& domain, DomainInfo& di )
 
 
 
-bool OdbxBackend::getSOA( const DNSName& domain, SOAData& sd)
+bool OdbxBackend::getSOA( const DNSName& domain, SOAData& sd, bool unmodifiedSerial)
 {
         const char* tmp;
 
@@ -210,7 +210,7 @@ bool OdbxBackend::getSOA( const DNSName& domain, SOAData& sd)
                                sd.ttl = strtoul( tmp, NULL, 10 );
                        }
 
-                       if( sd.serial == 0 && ( tmp = odbx_field_value( m_result, 1 ) ) != NULL )
+                       if( !unmodifiedSerial && sd.serial == 0 && ( tmp = odbx_field_value( m_result, 1 ) ) != NULL )
                        {
                                sd.serial = strtol( tmp, NULL, 10 );
                        }
index 97cae120702b758c468fb9456000563e0fe83d8f..6ebaf5e356299a03ede2ffeca41fad626f3ea6c9 100644 (file)
@@ -77,7 +77,7 @@ public:
         ~OdbxBackend();
 
         void lookup( const QType& qtype, const DNSName& qdomain, DNSPacket* p = 0, int zoneid = -1 ) override;
-        bool getSOA( const DNSName& domain, SOAData& sd ) override;
+        bool getSOA( const DNSName& domain, SOAData& sd, bool unmodifiedSerial ) override;
         bool list( const DNSName& target, int domain_id, bool include_disabled=false ) override;
         bool get( DNSResourceRecord& rr ) override;
 
index e140ae7888f00d6a39013f6329b002eb34c76c39..69fc677efb393d93036785e24bc2da3d6bb6a42a 100644 (file)
@@ -8,7 +8,7 @@ GEM
       ffi (~> 1.9)
     json (1.8.5)
     sqlite3 (1.3.9)
-    webrick (1.3.1)
+    webrick (1.4.2)
     zeromqrb (0.1.3)
       ffi-rzmq
 
index 1ec718b9392b85e0f917a32020b170d5b939ceab..8d52178888d1f1f68ec828edd398276ae5003f33 100644 (file)
@@ -8,7 +8,7 @@ GEM
       ffi (~> 1.9)
     json (1.8.2)
     sqlite3 (1.3.9)
-    webrick (1.3.1)
+    webrick (1.4.2)
     zeromqrb (0.1.3)
       ffi-rzmq
 
index ef2fbb2c6848e531d132f005a335c040c574339c..c3d694d11d45efb04f9830451b79f059750caa4f 100644 (file)
@@ -845,7 +845,7 @@ bool RemoteBackend::abortTransaction() {
    return true;
 }
 
-bool RemoteBackend::calculateSOASerial(const DNSName& domain, const SOAData& sd, time_t& serial) {
+bool RemoteBackend::calculateSOASerial(const DNSName& domain, const SOAData& sd, uint32_t& serial) {
    Json query = Json::object{
      { "method", "calculateSOASerial" },
      { "parameters", Json::object{
index b44af82c98c27d5a08b36795c1bdb9148093e70e..efec16bfdcf9292f0fb6fd1b1822f20385497441 100644 (file)
@@ -180,7 +180,7 @@ class RemoteBackend : public DNSBackend
   bool startTransaction(const DNSName& domain, int domain_id) override;
   bool commitTransaction() override;
   bool abortTransaction() override;
-  bool calculateSOASerial(const DNSName& domain, const SOAData& sd, time_t& serial) override;
+  bool calculateSOASerial(const DNSName& domain, const SOAData& sd, uint32_t& serial) override;
   bool setTSIGKey(const DNSName& name, const DNSName& algorithm, const string& content) override;
   bool deleteTSIGKey(const DNSName& name) override;
   bool getTSIGKeys(std::vector< struct TSIGKey > &keys) override;
index 48a240ace10e86ed2252c3276add03ed3cddd5c8..aed51ff4569342aeedce807d022c7810a0013c84 100644 (file)
@@ -326,7 +326,7 @@ BOOST_AUTO_TEST_CASE(test_method_abortTransaction) {
 
 BOOST_AUTO_TEST_CASE(test_method_calculateSOASerial) {
    SOAData sd;
-   time_t serial;
+   uint32_t serial;
  
    be->getSOA(DNSName("unit.test."),sd);
    BOOST_CHECK(be->calculateSOASerial(DNSName("unit.test."),sd,serial));
index 848fb260e180a6c040b596d4e63fe6e2bb60b273..f2d017695c1c91a283cc0dadd3bdd7046137218f 100644 (file)
@@ -261,7 +261,7 @@ bool GSQLBackend::setMaster(const DNSName &domain, const string &ip)
       reset();
   }
   catch (SSqlException &e) {
-    throw PDNSException("GSQLBackend unable to set master of domain \""+domain.toString()+"\": "+e.txtReason());
+    throw PDNSException("GSQLBackend unable to set master of domain '"+domain.toLogString()+"': "+e.txtReason());
   }
   return true;
 }
@@ -278,7 +278,7 @@ bool GSQLBackend::setKind(const DNSName &domain, const DomainInfo::DomainKind ki
       reset();
   }
   catch (SSqlException &e) {
-    throw PDNSException("GSQLBackend unable to set kind of domain \""+domain.toString()+"\": "+e.txtReason());
+    throw PDNSException("GSQLBackend unable to set kind of domain '"+domain.toLogString()+"': "+e.txtReason());
   }
   return true;
 }
@@ -295,7 +295,7 @@ bool GSQLBackend::setAccount(const DNSName &domain, const string &account)
             reset();
   }
   catch (SSqlException &e) {
-    throw PDNSException("GSQLBackend unable to set account of domain \""+domain.toString()+"\": "+e.txtReason());
+    throw PDNSException("GSQLBackend unable to set account of domain '"+domain.toLogString()+"': "+e.txtReason());
   }
   return true;
 }
@@ -547,7 +547,7 @@ bool GSQLBackend::updateEmptyNonTerminals(uint32_t domain_id, set<DNSName>& inse
           reset();
       }
       catch (SSqlException &e) {
-        throw PDNSException("GSQLBackend unable to delete empty non-terminal rr "+qname.toString()+" from domain_id "+itoa(domain_id)+": "+e.txtReason());
+        throw PDNSException("GSQLBackend unable to delete empty non-terminal rr '"+qname.toLogString()+"' from domain_id "+itoa(domain_id)+": "+e.txtReason());
         return false;
       }
     }
@@ -566,7 +566,7 @@ bool GSQLBackend::updateEmptyNonTerminals(uint32_t domain_id, set<DNSName>& inse
         reset();
     }
     catch (SSqlException &e) {
-      throw PDNSException("GSQLBackend unable to insert empty non-terminal rr "+qname.toString()+" in domain_id "+itoa(domain_id)+": "+e.txtReason());
+      throw PDNSException("GSQLBackend unable to insert empty non-terminal rr '"+qname.toLogString()+"' in domain_id "+itoa(domain_id)+": "+e.txtReason());
       return false;
     }
   }
@@ -1092,7 +1092,7 @@ bool GSQLBackend::list(const DNSName &target, int domain_id, bool include_disabl
 
 bool GSQLBackend::listSubZone(const DNSName &zone, int domain_id) {
 
-  string wildzone = "%." + toLower(zone.toStringNoDot());
+  string wildzone = "%." + zone.makeLowerCase().toStringNoDot();
 
   try {
     reconnectIfNeeded();
@@ -1184,7 +1184,7 @@ bool GSQLBackend::createDomain(const DNSName &domain, const string &type, const
       reset();
   }
   catch(SSqlException &e) {
-    throw PDNSException("Database error trying to insert new domain '"+domain.toString()+"': "+ e.txtReason());
+    throw PDNSException("Database error trying to insert new domain '"+domain.toLogString()+"': "+ e.txtReason());
   }
   return true;
 }
@@ -1218,7 +1218,7 @@ bool GSQLBackend::createSlaveDomain(const string &ip, const DNSName &domain, con
     createDomain(domain, "SLAVE", masters, account);
   }
   catch(SSqlException &e) {
-    throw PDNSException("Database error trying to insert new slave domain '"+domain.toString()+"': "+ e.txtReason());
+    throw PDNSException("Database error trying to insert new slave domain '"+domain.toLogString()+"': "+ e.txtReason());
   }
   return true;
 }
@@ -1255,7 +1255,7 @@ bool GSQLBackend::deleteDomain(const DNSName &domain)
       reset();
   }
   catch(SSqlException &e) {
-    throw PDNSException("Database error trying to delete domain '"+domain.toString()+"': "+ e.txtReason());
+    throw PDNSException("Database error trying to delete domain '"+domain.toLogString()+"': "+ e.txtReason());
   }
   return true;
 }
@@ -1506,7 +1506,7 @@ bool GSQLBackend::abortTransaction()
   return true;
 }
 
-bool GSQLBackend::calculateSOASerial(const DNSName& domain, const SOAData& sd, time_t& serial)
+bool GSQLBackend::calculateSOASerial(const DNSName& domain, const SOAData& sd, uint32_t& serial)
 {
   if (d_ZoneLastChangeQuery.empty()) {
     // query not set => fall back to default impl
index 7a6fcff9091655e080fe1b19abc27193c7e37cbd..cd591076f004f4bc78d965789e17507293e44b01 100644 (file)
@@ -207,7 +207,7 @@ public:
   bool updateEmptyNonTerminals(uint32_t domain_id, set<DNSName>& insert ,set<DNSName>& erase, bool remove) override;
   bool doesDNSSEC() override;
 
-  bool calculateSOASerial(const DNSName& domain, const SOAData& sd, time_t& serial) override;
+  bool calculateSOASerial(const DNSName& domain, const SOAData& sd, uint32_t& serial) override;
 
   bool replaceRRSet(uint32_t domain_id, const DNSName& qname, const QType& qt, const vector<DNSResourceRecord>& rrset) override;
   bool listSubZone(const DNSName &zone, int domain_id) override;
index 661b23f9145a6902153735999113bd0f0b6a6cab..d3d2285d17d0010528d9b94bfe8972458cc8fa0c 100644 (file)
@@ -60,7 +60,7 @@ public:
   virtual SSqlStatement* bind(const string& name, unsigned long long value)=0;
   virtual SSqlStatement* bind(const string& name, const std::string& value)=0;
   SSqlStatement* bind(const string& name, const DNSName& value) {
-    return bind(name, toLower(value.toStringRootDot()));
+    return bind(name, value.makeLowerCase().toStringRootDot());
   }
   virtual SSqlStatement* bindNull(const string& name)=0;
   virtual SSqlStatement* execute()=0;;
index 1b5b56a5ab7d2368044aed6fde21cf9600859584..65cf551b361403109d82178cc62ecf6ceebcb7a3 100644 (file)
@@ -294,19 +294,19 @@ void BPFFilter::block(const DNSName& qname, uint16_t qtype)
 
   std::string keyStr = qname.toDNSStringLC();
   if (keyStr.size() > sizeof(key.qname)) {
-    throw std::runtime_error("Invalid QName to block " + qname.toString());
+    throw std::runtime_error("Invalid QName to block " + qname.toLogString());
   }
   memcpy(key.qname, keyStr.c_str(), keyStr.size());
 
   {
     std::unique_lock<std::mutex> lock(d_mutex);
     if (d_qNamesCount >= d_maxQNames) {
-      throw std::runtime_error("Table full when trying to block " + qname.toString());
+      throw std::runtime_error("Table full when trying to block " + qname.toLogString());
     }
 
     int res = bpf_lookup_elem(d_qnamemap.fd, &key, &value);
     if (res != -1) {
-      throw std::runtime_error("Trying to block an already blocked qname: " + qname.toString());
+      throw std::runtime_error("Trying to block an already blocked qname: " + qname.toLogString());
     }
 
     res = bpf_update_elem(d_qnamemap.fd, &key, &value, BPF_NOEXIST);
@@ -315,7 +315,7 @@ void BPFFilter::block(const DNSName& qname, uint16_t qtype)
     }
 
     if (res != 0) {
-      throw std::runtime_error("Error adding blocked qname " + qname.toString() + ": " + std::string(strerror(errno)));
+      throw std::runtime_error("Error adding blocked qname " + qname.toLogString() + ": " + std::string(strerror(errno)));
     }
   }
 }
@@ -327,7 +327,7 @@ void BPFFilter::unblock(const DNSName& qname, uint16_t qtype)
   (void) qtype;
 
   if (keyStr.size() > sizeof(key.qname)) {
-    throw std::runtime_error("Invalid QName to block " + qname.toString());
+    throw std::runtime_error("Invalid QName to block " + qname.toLogString());
   }
   memcpy(key.qname, keyStr.c_str(), keyStr.size());
 
@@ -339,7 +339,7 @@ void BPFFilter::unblock(const DNSName& qname, uint16_t qtype)
       d_qNamesCount--;
     }
     else {
-      throw std::runtime_error("Error removing qname address " + qname.toString() + ": " + std::string(strerror(errno)));
+      throw std::runtime_error("Error removing qname address " + qname.toLogString() + ": " + std::string(strerror(errno)));
     }
   }
 }
index ee5eb5fc0ea97720ba76a125d256f03778a89ec6..0a4a548f5eab0cd4e15c85416f775fb5eb3b7210 100644 (file)
@@ -169,7 +169,7 @@ DNSSECPrivateKey DNSSECKeeper::getKeyById(const DNSName& zname, unsigned int id)
     
     return dpk;    
   }
-  throw runtime_error("Can't find a key with id "+std::to_string(id)+" for zone '"+zname.toString()+"'");
+  throw runtime_error("Can't find a key with id "+std::to_string(id)+" for zone '"+zname.toLogString()+"'");
 }
 
 
@@ -321,7 +321,7 @@ bool DNSSECKeeper::setNSEC3PARAM(const DNSName& zname, const NSEC3PARAMRecordCon
 {
   string error_msg = "";
   if (!checkNSEC3PARAM(ns3p, error_msg))
-    throw runtime_error("NSEC3PARAMs provided for zone '"+zname.toString()+"' are invalid: " + error_msg);
+    throw runtime_error("NSEC3PARAMs provided for zone '"+zname.toLogString()+"' are invalid: " + error_msg);
 
   clearCaches(zname);
   string descr = ns3p.getZoneRepresentation();
index 051e27aef78a58c4104e3c8fbfe1d7e1b191d1c0..5d40735649236b038c5c53b38812d0d94d6b5363 100644 (file)
@@ -46,7 +46,7 @@ std::vector<std::string> RCode::rcodes_s = boost::assign::list_of
   ("Err#12")
   ("Err#13")
   ("Err#14")
-  ("Err#15")
+  ("Err#15")  // Last non-extended RCode
   ("Bad OPT Version / TSIG Signature Failure")
   ("Key not recognized")
   ("Signature out of time window")
@@ -54,10 +54,17 @@ std::vector<std::string> RCode::rcodes_s = boost::assign::list_of
   ("Duplicate key name")
   ("Algorithm not supported")
   ("Bad Truncation")
+  ("Bad/missing Server Cookie")
 ;
 
-std::string RCode::to_s(unsigned short rcode) {
-  if (rcode > RCode::rcodes_s.size()-1 ) 
+std::string RCode::to_s(uint8_t rcode) {
+  if (rcode > 0xF)
+    return std::string("ErrOutOfRange");
+  return ERCode::to_s(rcode);
+}
+
+std::string ERCode::to_s(uint8_t rcode) {
+  if (rcode > RCode::rcodes_s.size()-1)
     return std::string("Err#")+std::to_string(rcode);
   return RCode::rcodes_s[rcode];
 }
index 88a658c5a52ff384a06382bbd4b7998d101d2b78..cd07856c617f34a9533fa3f8f847a799c6a474fc 100644 (file)
@@ -29,6 +29,9 @@
 #include "dnsname.hh"
 #include <time.h>
 #include <sys/types.h>
+
+#undef BADSIG  // signal.h SIG_ERR
+
 class DNSBackend;
 struct DNSRecord;
 
@@ -54,10 +57,17 @@ class RCode
 {
 public:
   enum rcodes_ { NoError=0, FormErr=1, ServFail=2, NXDomain=3, NotImp=4, Refused=5, YXDomain=6, YXRRSet=7, NXRRSet=8, NotAuth=9, NotZone=10};
-  static std::string to_s(unsigned short rcode);
+  static std::string to_s(uint8_t rcode);
   static std::vector<std::string> rcodes_s;
 };
 
+class ERCode
+{
+public:
+  enum rcodes_ { BADVERS=16, BADSIG=16, BADKEY=17, BADTIME=18, BADMODE=19, BADNAME=20, BADALG=21, BADTRUNC=22, BADCOOKIE=23 };
+  static std::string to_s(uint8_t rcode);
+};
+
 class Opcode
 {
 public:
@@ -130,7 +140,7 @@ struct EDNS0Record
 
 static_assert(sizeof(EDNS0Record) == 4, "EDNS0Record size must be 4");
 
-#if defined(__FreeBSD__) || defined(__APPLE__) || defined(__OpenBSD__) || defined(__DragonFly__) || defined(__FreeBSD_kernel__)
+#if defined(__FreeBSD__) || defined(__APPLE__) || defined(__OpenBSD__) || defined(__DragonFly__) || defined(__FreeBSD_kernel__) || defined(__NetBSD__)
 #include <machine/endian.h>
 #elif __linux__ || __GNU__
 # include <endian.h>
@@ -236,6 +246,4 @@ struct TSIGTriplet
   string secret;
 };
 
-/** for use by DNSPacket, converts a SOAData class to a ascii line again */
-string serializeSOAData(const SOAData &data);
 string &attodot(string &str);  //!< for when you need to insert an email address in the SOA
index 5fca2b903de903a6480c3c3b47fededcdc7bbe8d..dd4c3d9ba131ad2d3453b9a88d1a1644ad0e3ab8 100644 (file)
@@ -213,8 +213,9 @@ vector<DNSBackend *>BackendMakerClass::all(bool metadataOnly)
 
     \param domain Domain we want to get the SOA details of
     \param sd SOAData which is filled with the SOA details
+    \param unmodifiedSerial bool if set, serial will be returned as stored in the backend (maybe 0)
 */
-bool DNSBackend::getSOA(const DNSName &domain, SOAData &sd)
+bool DNSBackend::getSOA(const DNSName &domain, SOAData &sd, bool unmodifiedSerial)
 {
   this->lookup(QType(QType::SOA),domain);
 
@@ -247,10 +248,10 @@ bool DNSBackend::getSOA(const DNSName &domain, SOAData &sd)
       sd.hostmaster=DNSName("hostmaster")+domain;
   }
 
-  if(!sd.serial) { // magic time!
+  if(!unmodifiedSerial && !sd.serial) { // magic time!
     DLOG(L<<Logger::Warning<<"Doing SOA serial number autocalculation for "<<rr.qname<<endl);
 
-    time_t serial;
+    uint32_t serial = 0;
     if (calculateSOASerial(domain, sd, serial)) {
       sd.serial = serial;
       //DLOG(L<<"autocalculated soa serialnumber for "<<rr.qname<<" is "<<newest<<endl);
@@ -330,12 +331,12 @@ bool DNSBackend::getBeforeAndAfterNames(uint32_t id, const DNSName& zonename, co
  * \param sd Information about the SOA record already available
  * \param serial Output parameter. Only inspected when we return true
  */
-bool DNSBackend::calculateSOASerial(const DNSName& domain, const SOAData& sd, time_t& serial)
+bool DNSBackend::calculateSOASerial(const DNSName& domain, const SOAData& sd, uint32_t& serial)
 {
     // we do this by listing the domain and taking the maximum last modified timestamp
 
     DNSResourceRecord i;
-    time_t newest=0;
+    uint32_t newest=0;
 
     if(!(this->list(domain, sd.domain_id))) {
       DLOG(L<<Logger::Warning<<"Backend error trying to determine magic serial number of zone '"<<domain<<"'"<<endl);
@@ -416,12 +417,3 @@ void fillSOAData(const string &content, SOAData &data)
     throw PDNSException("Out of range exception parsing "+content);
   }
 }
-
-string serializeSOAData(const SOAData &d)
-{
-  ostringstream o;
-  //  nameservername hostmaster serial-number [refresh [retry [expire [ minimum] ] ] ]
-  o<<d.nameserver.toString()<<" "<< d.hostmaster.toString() <<" "<< d.serial <<" "<< d.refresh << " "<< d.retry << " "<< d.expire << " "<< d.default_ttl;
-
-  return o.str();
-}
index 16ec7c5c4a5ab8820b1b70d5c1674cab6f442328..4b14f57b8f308a867f7b8b5bf18b4c37723cfd45 100644 (file)
@@ -125,10 +125,10 @@ public:
   virtual ~DNSBackend(){};
 
   //! fills the soadata struct with the SOA details. Returns false if there is no SOA.
-  virtual bool getSOA(const DNSName &name, SOAData &soadata);
+  virtual bool getSOA(const DNSName &name, SOAData &soadata, bool unmodifiedSerial=false);
 
   //! Calculates a SOA serial for the zone and stores it in the third argument.
-  virtual bool calculateSOASerial(const DNSName& domain, const SOAData& sd, time_t& serial);
+  virtual bool calculateSOASerial(const DNSName& domain, const SOAData& sd, uint32_t& serial);
 
   virtual bool replaceRRSet(uint32_t domain_id, const DNSName& qname, const QType& qt, const vector<DNSResourceRecord>& rrset)
   {
index 7ecb8c2ddc858302ff9dcbeff893fdce3086c66b..50703e28c8f3984063cc571196345db55ca1bdde 100644 (file)
@@ -89,7 +89,7 @@ void DNSDistPacketCache::insertLocked(CacheShard& shard, uint32_t key, const DNS
   value = newValue;
 }
 
-void DNSDistPacketCache::insert(uint32_t key, const DNSName& qname, uint16_t qtype, uint16_t qclass, const char* response, uint16_t responseLen, bool tcp, uint8_t rcode)
+void DNSDistPacketCache::insert(uint32_t key, const DNSName& qname, uint16_t qtype, uint16_t qclass, const char* response, uint16_t responseLen, bool tcp, uint8_t rcode, boost::optional<uint32_t> tempFailureTTL)
 {
   if (responseLen < sizeof(dnsheader))
     return;
@@ -97,7 +97,7 @@ void DNSDistPacketCache::insert(uint32_t key, const DNSName& qname, uint16_t qty
   uint32_t minTTL;
 
   if (rcode == RCode::ServFail || rcode == RCode::Refused) {
-    minTTL = d_tempFailureTTL;
+    minTTL = tempFailureTTL == boost::none ? d_tempFailureTTL : *tempFailureTTL;
     if (minTTL == 0) {
       return;
     }
index 7283f96ace150c4c50f85d4bdedc5b72707fa97f..daeabc6f61fd0e2547050b907603b565979d647d 100644 (file)
@@ -33,7 +33,7 @@ public:
   DNSDistPacketCache(size_t maxEntries, uint32_t maxTTL=86400, uint32_t minTTL=0, uint32_t tempFailureTTL=60, uint32_t staleTTL=60, bool dontAge=false, uint32_t shards=1, bool deferrableInsertLock=true);
   ~DNSDistPacketCache();
 
-  void insert(uint32_t key, const DNSName& qname, uint16_t qtype, uint16_t qclass, const char* response, uint16_t responseLen, bool tcp, uint8_t rcode);
+  void insert(uint32_t key, const DNSName& qname, uint16_t qtype, uint16_t qclass, const char* response, uint16_t responseLen, bool tcp, uint8_t rcode, boost::optional<uint32_t> tempFailureTTL);
   bool get(const DNSQuestion& dq, uint16_t consumed, uint16_t queryId, char* response, uint16_t* responseLen, uint32_t* keyOut, uint32_t allowExpired=0, bool skipAging=false);
   void purgeExpired(size_t upTo=0);
   void expunge(size_t upTo=0);
index 40aa405eaf3894b63598247af3d4fcf4c601c651..1f267c0aa9ca4aa9841f8f132b25d561f0272106 100644 (file)
@@ -23,7 +23,7 @@
 #include "sodcrypto.hh"
 #include "pwd.h"
 
-#if defined (__OpenBSD__)
+#if defined (__OpenBSD__) || defined(__NetBSD__)
 #include <readline/readline.h>
 #include <readline/history.h>
 #else
@@ -284,14 +284,16 @@ void doConsole()
 const std::vector<ConsoleKeyword> g_consoleKeywords{
   /* keyword, function, parameters, description */
   { "addACL", true, "netmask", "add to the ACL set who can use this server" },
-  { "addAction", true, "DNS rule, DNS action", "add a rule" },
+  { "addAction", true, "DNS rule, DNS action [, {uuid=\"UUID\"}]", "add a rule" },
   { "addDNSCryptBind", true, "\"127.0.0.1:8443\", \"provider name\", \"/path/to/resolver.cert\", \"/path/to/resolver.key\", {reusePort=false, tcpFastOpenSize=0, interface=\"\", cpus={}}", "listen to incoming DNSCrypt queries on 127.0.0.1 port 8443, with a provider name of `provider name`, using a resolver certificate and associated key stored respectively in the `resolver.cert` and `resolver.key` files. The fifth optional parameter is a table of parameters" },
   { "addDynBlocks", true, "addresses, message[, seconds[, action]]", "block the set of addresses with message `msg`, for `seconds` seconds (10 by default), applying `action` (default to the one set with `setDynBlocksAction()`)" },
   { "addLocal", true, "addr [, {doTCP=true, reusePort=false, tcpFastOpenSize=0, interface=\"\", cpus={}}]", "add `addr` to the list of addresses we listen on" },
-  { "addLuaAction", true, "x, func", "where 'x' is all the combinations from `addAction`, and func is a function with the parameter `dq`, which returns an action to be taken on this packet. Good for rare packets but where you want to do a lot of processing" },
-  { "addLuaResponseAction", true, "x, func", "where 'x' is all the combinations from `addAction`, and func is a function with the parameter `dr`, which returns an action to be taken on this response packet. Good for rare packets but where you want to do a lot of processing" },
-  { "addCacheHitResponseAction", true, "DNS rule, DNS response action", "add a cache hit response rule" },
-  { "addResponseAction", true, "DNS rule, DNS response action", "add a response rule" },
+  { "addLuaAction", true, "x, func [, {uuid=\"UUID\"}]", "where 'x' is all the combinations from `addAction`, and func is a function with the parameter `dq`, which returns an action to be taken on this packet. Good for rare packets but where you want to do a lot of processing" },
+  { "addLuaResponseAction", true, "x, func [, {uuid=\"UUID\"}]", "where 'x' is all the combinations from `addAction`, and func is a function with the parameter `dr`, which returns an action to be taken on this response packet. Good for rare packets but where you want to do a lot of processing" },
+  { "addCacheHitResponseAction", true, "DNS rule, DNS response action [, {uuid=\"UUID\"}]", "add a cache hit response rule" },
+  { "addResponseAction", true, "DNS rule, DNS response action [, {uuid=\"UUID\"}]", "add a response rule" },
+  { "addSelfAnsweredResponseAction", true, "DNS rule, DNS response action [, {uuid=\"UUID\"}]", "add a self-answered response rule" },
+  { "addTLSLocal", true, "addr, certFile, keyFile[,params]", "listen to incoming DNS over TLS queries on the specified address using the specified certificate and key. The last parameter is a table" },
   { "AllowAction", true, "", "let these packets go through" },
   { "AllowResponseAction", true, "", "let these packets go through" },
   { "AllRule", true, "", "matches all traffic" },
@@ -326,6 +328,7 @@ const std::vector<ConsoleKeyword> g_consoleKeywords{
   { "getResponseRing", true, "", "return the current content of the response ring" },
   { "getServer", true, "n", "returns server with index n" },
   { "getServers", true, "", "returns a table with all defined servers" },
+  { "getTLSContext", true, "n", "returns the TLS context with index n" },
   { "inClientStartup", true, "", "returns true during console client parsing of configuration" },
   { "grepq", true, "Netmask|DNS Name|100ms|{\"::1\", \"powerdns.com\", \"100ms\"} [, n]", "shows the last n queries and responses matching the specified client address or range (Netmask), or the specified DNS Name, or slower than 100ms" },
   { "leastOutstanding", false, "", "Send traffic to downstream server with least outstanding queries, with the lowest 'order', and within that the lowest recent latency"},
@@ -336,11 +339,12 @@ const std::vector<ConsoleKeyword> g_consoleKeywords{
   { "mvCacheHitResponseRule", true, "from, to", "move cache hit response rule 'from' to a position where it is in front of 'to'. 'to' can be one larger than the largest rule" },
   { "mvResponseRule", true, "from, to", "move response rule 'from' to a position where it is in front of 'to'. 'to' can be one larger than the largest rule" },
   { "mvRule", true, "from, to", "move rule 'from' to a position where it is in front of 'to'. 'to' can be one larger than the largest rule, in which case the rule will be moved to the last position" },
+  { "mvSelfAnsweredResponseRule", true, "from, to", "move self-answered response rule 'from' to a position where it is in front of 'to'. 'to' can be one larger than the largest rule" },
   { "newDNSName", true, "name", "make a DNSName based on this .-terminated name" },
   { "newPacketCache", true, "maxEntries[, maxTTL=86400, minTTL=0, temporaryFailureTTL=60, staleTTL=60, dontAge=false, numberOfShards=1, deferrableInsertLock=true]", "return a new Packet Cache" },
   { "newQPSLimiter", true, "rate, burst", "configure a QPS limiter with that rate and that burst capacity" },
   { "newRemoteLogger", true, "address:port [, timeout=2, maxQueuedEntries=100, reconnectWaitTime=1]", "create a Remote Logger object, to use with `RemoteLogAction()` and `RemoteLogResponseAction()`" },
-  { "newRuleAction", true, "DNS rule, DNS action", "return a pair of DNS Rule and DNS Action, to be used with `setRules()`" },
+  { "newRuleAction", true, "DNS rule, DNS action [, {uuid=\"UUID\"}]", "return a pair of DNS Rule and DNS Action, to be used with `setRules()`" },
   { "newServer", true, "{address=\"ip:port\", qps=1000, order=1, weight=10, pool=\"abuse\", retries=5, tcpConnectTimeout=5, tcpSendTimeout=30, tcpRecvTimeout=30, checkName=\"a.root-servers.net.\", checkType=\"A\", maxCheckFailures=1, mustResolve=false, useClientSubnet=true, source=\"address|interface name|address@interface\"}", "instantiate a server" },
   { "newServerPolicy", true, "name, function", "create a policy object from a Lua function" },
   { "newSuffixMatchNode", true, "", "returns a new SuffixMatchNode" },
@@ -351,9 +355,10 @@ const std::vector<ConsoleKeyword> g_consoleKeywords{
   { "registerDynBPFFilter", true, "DynBPFFilter", "register this dynamic BPF filter into the web interface so that its counters are displayed" },
   { "RemoteLogAction", true, "RemoteLogger [, alterFunction]", "send the content of this query to a remote logger via Protocol Buffer. `alterFunction` is a callback, receiving a DNSQuestion and a DNSDistProtoBufMessage, that can be used to modify the Protocol Buffer content, for example for anonymization purposes" },
   { "RemoteLogResponseAction", true, "RemoteLogger [,alterFunction [,includeCNAME]]", "send the content of this response to a remote logger via Protocol Buffer. `alterFunction` is the same callback than the one in `RemoteLogAction` and `includeCNAME` indicates whether CNAME records inside the response should be parsed and exported. The default is to only exports A and AAAA records" },
-  { "rmCacheHitResponseRule", true, "n", "remove cache hit response rule n" },
-  { "rmResponseRule", true, "n", "remove response rule n" },
-  { "rmRule", true, "n", "remove rule n" },
+  { "rmCacheHitResponseRule", true, "id", "remove cache hit response rule in position 'id', or whose uuid matches if 'id' is an UUID string" },
+  { "rmResponseRule", true, "id", "remove response rule in position 'id', or whose uuid matches if 'id' is an UUID string" },
+  { "rmRule", true, "id", "remove rule in position 'id', or whose uuid matches if 'id' is an UUID string" },
+  { "rmSelfAnsweredResponseRule", true, "id", "remove self-answered response rule in position 'id', or whose uuid matches if 'id' is an UUID string" },
   { "rmServer", true, "n", "remove server with index n" },
   { "roundrobin", false, "", "Simple round robin over available servers" },
   { "QNameLabelsCountRule", true, "min, max", "matches if the qname has less than `min` or more than `max` labels" },
@@ -361,6 +366,7 @@ const std::vector<ConsoleKeyword> g_consoleKeywords{
   { "QNameWireLengthRule", true, "min, max", "matches if the qname's length on the wire is less than `min` or more than `max` bytes" },
   { "QTypeRule", true, "qtype", "matches queries with the specified qtype" },
   { "RCodeRule", true, "rcode", "matches responses with the specified rcode" },
+  { "ERCodeRule", true, "rcode", "matches responses with the specified extended rcode (EDNS0)" },
   { "sendCustomTrap", true, "str", "send a custom `SNMP` trap from Lua, containing the `str` string"},
   { "setACL", true, "{netmask, netmask}", "replace the ACL set with these netmasks. Use `setACL({})` to reset the list, meaning no one can use us" },
   { "setAPIWritable", true, "bool, dir", "allow modifications via the API. if `dir` is set, it must be a valid directory where the configuration files will be written by the API" },
@@ -398,17 +404,19 @@ const std::vector<ConsoleKeyword> g_consoleKeywords{
   { "show", true, "string", "outputs `string`" },
   { "showACL", true, "", "show our ACL set" },
   { "showBinds", true, "", "show listening addresses (frontends)" },
-  { "showCacheHitResponseRules", true, "", "show all defined cache hit response rules" },
+  { "showCacheHitResponseRules", true, "[showUUIDs]", "show all defined cache hit response rules, optionally with their UUIDs" },
   { "showDNSCryptBinds", true, "", "display the currently configured DNSCrypt binds" },
   { "showDynBlocks", true, "", "show dynamic blocks in force" },
   { "showPools", true, "", "show the available pools" },
   { "showPoolServerPolicy", true, "pool", "show server selection policy for this pool" },
   { "showResponseLatency", true, "", "show a plot of the response time latency distribution" },
-  { "showResponseRules", true, "", "show all defined response rules" },
-  { "showRules", true, "", "show all defined rules" },
+  { "showResponseRules", true, "[showUUIDs]", "show all defined response rules, optionally with their UUIDs" },
+  { "showRules", true, "[showUUIDs]", "show all defined rules, optionally with their UUIDs" },
+  { "showSelfAnsweredResponseRules", true, "[showUUIDs]", "show all defined self-answered response rules, optionally with their UUIDs" },
   { "showServerPolicy", true, "", "show name of currently operational server selection policy" },
   { "showServers", true, "", "output all servers" },
   { "showTCPStats", true, "", "show some statistics regarding TCP" },
+  { "showTLSContexts", true, "", "list all the available TLS contexts" },
   { "showVersion", true, "", "show the current version" },
   { "shutdown", true, "", "shut down `dnsdist`" },
   { "snmpAgent", true, "enableTraps [, masterSocket]", "enable `SNMP` support. `enableTraps` is a boolean indicating whether traps should be sent and `masterSocket` an optional string specifying how to connect to the master agent"},
@@ -419,6 +427,8 @@ const std::vector<ConsoleKeyword> g_consoleKeywords{
   { "TagResponseAction", true, "name, value", "set the tag named 'name' to the given value" },
   { "TagRule", true, "name [, value]", "matches if the tag named 'name' is present, with the given 'value' matching if any" },
   { "TCAction", true, "", "create answer to query with TC and RD bits set, to move to TCP" },
+  { "TeeAction", true, "remote [, addECS]", "send copy of query to remote, optionally adding ECS info" },
+  { "TempFailureCacheTTLAction", true, "ttl", "set packetcache TTL for temporary failure replies" },
   { "testCrypto", true, "", "test of the crypto all works" },
   { "TimedIPSetRule", true, "", "Create a rule which matches a set of IP addresses which expire"}, 
   { "topBandwidth", true, "top", "show top-`top` clients that consume the most bandwidth over length of ringbuffer" },
@@ -426,8 +436,10 @@ const std::vector<ConsoleKeyword> g_consoleKeywords{
   { "topClients", true, "n", "show top-`n` clients sending the most queries over length of ringbuffer" },
   { "topQueries", true, "n[, labels]", "show top 'n' queries, as grouped when optionally cut down to 'labels' labels" },
   { "topResponses", true, "n, kind[, labels]", "show top 'n' responses with RCODE=kind (0=NO Error, 2=ServFail, 3=ServFail), as grouped when optionally cut down to 'labels' labels" },
+  { "topCacheHitResponseRule", true, "", "move the last cache hit response rule to the first position" },
   { "topResponseRule", true, "", "move the last response rule to the first position" },
   { "topRule", true, "", "move the last rule to the first position" },
+  { "topSelfAnsweredResponseRule", true, "", "move the last self-answered response rule to the first position" },
   { "topSlow", true, "[top][, limit][, labels]", "show `top` queries slower than `limit` milliseconds, grouped by last `labels` labels" },
   { "truncateTC", true, "bool", "if set (defaults to no starting with dnsdist 1.2.0) truncate TC=1 answers so they are actually empty. Fixes an issue for PowerDNS Authoritative Server 2.9.22. Note: turning this on breaks compatibility with RFC 6891." },
   { "unregisterDynBPFFilter", true, "DynBPFFilter", "unregister this dynamic BPF filter" },
index 2c6e137521658f538a11ebdac8cba43eae9b922a..351d407fb3a9d925082528c1119f4f6effe46e99 100644 (file)
@@ -275,7 +275,7 @@ private:
 class RCodeAction : public DNSAction
 {
 public:
-  RCodeAction(int rcode) : d_rcode(rcode) {}
+  RCodeAction(uint8_t rcode) : d_rcode(rcode) {}
   DNSAction::Action operator()(DNSQuestion* dq, string* ruleresult) const override
   {
     dq->dh->rcode = d_rcode;
@@ -288,7 +288,7 @@ public:
   }
 
 private:
-  int d_rcode;
+  uint8_t d_rcode;
 };
 
 class TCAction : public DNSAction
@@ -527,6 +527,24 @@ public:
   }
 };
 
+class TempFailureCacheTTLAction : public DNSAction
+{
+public:
+  TempFailureCacheTTLAction(uint32_t ttl) : d_ttl(ttl)
+  {}
+  TempFailureCacheTTLAction::Action operator()(DNSQuestion* dq, string* ruleresult) const override
+  {
+    dq->tempFailureTTL = d_ttl;
+    return Action::None;
+  }
+  string toString() const override
+  {
+    return "set tempfailure cache ttl to "+std::to_string(d_ttl);
+  }
+private:
+  uint32_t d_ttl;
+};
+
 class ECSPrefixLengthAction : public DNSAction
 {
 public:
@@ -790,65 +808,68 @@ private:
   std::string d_value;
 };
 
+template<typename T, typename ActionT>
+static void addAction(GlobalStateHolder<vector<T> > *someRulActions, luadnsrule_t var, std::shared_ptr<ActionT> action, boost::optional<luaruleparams_t> params) {
+  setLuaSideEffect();
+
+  boost::uuids::uuid uuid;
+  parseRuleParams(params, uuid);
+
+  auto rule=makeRule(var);
+  someRulActions->modify([rule, action, uuid](vector<T>& rulactions){
+      rulactions.push_back({rule, action, uuid});
+    });
+}
+
 void setupLuaActions()
 {
-  g_lua.writeFunction("newRuleAction", [](luadnsrule_t dnsrule, std::shared_ptr<DNSAction> action) {
+  g_lua.writeFunction("newRuleAction", [](luadnsrule_t dnsrule, std::shared_ptr<DNSAction> action, boost::optional<luaruleparams_t> params) {
+      boost::uuids::uuid uuid;
+      parseRuleParams(params, uuid);
+
       auto rule=makeRule(dnsrule);
-      return std::make_shared<std::pair< luadnsrule_t, std::shared_ptr<DNSAction> > >(rule, action);
+      DNSDistRuleAction ra({rule, action, uuid});
+      return std::make_shared<DNSDistRuleAction>(ra);
     });
 
-  g_lua.writeFunction("addAction", [](luadnsrule_t var, boost::variant<std::shared_ptr<DNSAction>, std::shared_ptr<DNSResponseAction> > era) {
-      if (era.type() == typeid(std::shared_ptr<DNSResponseAction>)) {
+  g_lua.writeFunction("addAction", [](luadnsrule_t var, boost::variant<std::shared_ptr<DNSAction>, std::shared_ptr<DNSResponseAction> > era, boost::optional<luaruleparams_t> params) {
+      if (era.type() != typeid(std::shared_ptr<DNSAction>)) {
         throw std::runtime_error("addAction() can only be called with query-related actions, not response-related ones. Are you looking for addResponseAction()?");
       }
 
-      auto ea = *boost::get<std::shared_ptr<DNSAction>>(&era);
-      setLuaSideEffect();
-      auto rule=makeRule(var);
-      g_rulactions.modify([rule, ea](decltype(g_rulactions)::value_type& rulactions){
-          rulactions.push_back({rule, ea});
-        });
+      addAction(&g_rulactions, var, boost::get<std::shared_ptr<DNSAction> >(era), params);
     });
 
-  g_lua.writeFunction("addLuaAction", [](luadnsrule_t var, LuaAction::func_t func)
-                     {
-                        setLuaSideEffect();
-                       auto rule=makeRule(var);
-                       g_rulactions.modify([rule,func](decltype(g_rulactions)::value_type& rulactions){
-                           rulactions.push_back({rule,
-                                 std::make_shared<LuaAction>(func)});
-                         });
-                     });
-
-  g_lua.writeFunction("addLuaResponseAction", [](luadnsrule_t var, LuaResponseAction::func_t func) {
-      setLuaSideEffect();
-      auto rule=makeRule(var);
-      g_resprulactions.modify([rule,func](decltype(g_resprulactions)::value_type& rulactions){
-          rulactions.push_back({rule,
-                std::make_shared<LuaResponseAction>(func)});
-        });
+  g_lua.writeFunction("addLuaAction", [](luadnsrule_t var, LuaAction::func_t func, boost::optional<luaruleparams_t> params) {
+      addAction(&g_rulactions, var, std::make_shared<LuaAction>(func), params);
     });
 
-  g_lua.writeFunction("addResponseAction", [](luadnsrule_t var, boost::variant<std::shared_ptr<DNSAction>, std::shared_ptr<DNSResponseAction> > era) {
-      if (era.type() == typeid(std::shared_ptr<DNSAction>)) {
+  g_lua.writeFunction("addLuaResponseAction", [](luadnsrule_t var, LuaResponseAction::func_t func, boost::optional<luaruleparams_t> params) {
+      addAction(&g_resprulactions, var, std::make_shared<LuaResponseAction>(func), params);
+    });
+
+  g_lua.writeFunction("addResponseAction", [](luadnsrule_t var, boost::variant<std::shared_ptr<DNSAction>, std::shared_ptr<DNSResponseAction> > era, boost::optional<luaruleparams_t> params) {
+      if (era.type() != typeid(std::shared_ptr<DNSResponseAction>)) {
         throw std::runtime_error("addResponseAction() can only be called with response-related actions, not query-related ones. Are you looking for addAction()?");
       }
 
-      auto ea = *boost::get<std::shared_ptr<DNSResponseAction>>(&era);
+      addAction(&g_resprulactions, var, boost::get<std::shared_ptr<DNSResponseAction> >(era), params);
+    });
 
-      setLuaSideEffect();
-      auto rule=makeRule(var);
-      g_resprulactions.modify([rule, ea](decltype(g_resprulactions)::value_type& rulactions){
-          rulactions.push_back({rule, ea});
-        });
+  g_lua.writeFunction("addCacheHitResponseAction", [](luadnsrule_t var, boost::variant<std::shared_ptr<DNSAction>, std::shared_ptr<DNSResponseAction>> era, boost::optional<luaruleparams_t> params) {
+      if (era.type() != typeid(std::shared_ptr<DNSResponseAction>)) {
+        throw std::runtime_error("addCacheHitResponseAction() can only be called with response-related actions, not query-related ones. Are you looking for addAction()?");
+      }
+
+      addAction(&g_cachehitresprulactions, var, boost::get<std::shared_ptr<DNSResponseAction> >(era), params);
     });
 
-  g_lua.writeFunction("addCacheHitResponseAction", [](luadnsrule_t var, std::shared_ptr<DNSResponseAction> ea) {
-      setLuaSideEffect();
-      auto rule=makeRule(var);
-      g_cachehitresprulactions.modify([rule, ea](decltype(g_cachehitresprulactions)::value_type& rulactions){
-          rulactions.push_back({rule, ea});
-        });
+  g_lua.writeFunction("addSelfAnsweredResponseAction", [](luadnsrule_t var, boost::variant<std::shared_ptr<DNSAction>, std::shared_ptr<DNSResponseAction>> era, boost::optional<luaruleparams_t> params) {
+      if (era.type() != typeid(std::shared_ptr<DNSResponseAction>)) {
+        throw std::runtime_error("addSelfAnsweredResponseAction() can only be called with response-related actions, not query-related ones. Are you looking for addAction()?");
+      }
+
+      addAction(&g_selfansweredresprulactions, var, boost::get<std::shared_ptr<DNSResponseAction> >(era), params);
     });
 
   g_lua.registerFunction<void(DNSAction::*)()>("printStats", [](const DNSAction& ta) {
@@ -868,7 +889,7 @@ void setupLuaActions()
       boost::optional<std::shared_ptr<DNSAction>> ret;
       auto rulactions = g_rulactions.getCopy();
       if(num < rulactions.size())
-        ret=rulactions[num].second;
+        ret=rulactions[num].d_action;
       return ret;
     });
 
@@ -941,7 +962,7 @@ void setupLuaActions()
       return std::shared_ptr<DNSAction>(new LogAction(fname, binary ? *binary : true, append ? *append : false, buffered ? *buffered : false));
     });
 
-  g_lua.writeFunction("RCodeAction", [](int rcode) {
+  g_lua.writeFunction("RCodeAction", [](uint8_t rcode) {
       return std::shared_ptr<DNSAction>(new RCodeAction(rcode));
     });
 
@@ -949,6 +970,10 @@ void setupLuaActions()
       return std::shared_ptr<DNSAction>(new SkipCacheAction);
     });
 
+  g_lua.writeFunction("TempFailureCacheTTLAction", [](int maxTTL) {
+      return std::shared_ptr<DNSAction>(new TempFailureCacheTTLAction(maxTTL));
+    });
+
   g_lua.writeFunction("DropResponseAction", []() {
       return std::shared_ptr<DNSResponseAction>(new DropResponseAction);
     });
index 7f40574d3feca9cbe2f25e31df48757ff844aaa4..dcd47fff21364ec34964d07123e8aca076964860 100644 (file)
@@ -43,6 +43,14 @@ void setupLuaBindingsDNSQuestion()
   g_lua.registerMember<bool (DNSQuestion::*)>("useECS", [](const DNSQuestion& dq) -> bool { return dq.useECS; }, [](DNSQuestion& dq, bool useECS) { dq.useECS = useECS; });
   g_lua.registerMember<bool (DNSQuestion::*)>("ecsOverride", [](const DNSQuestion& dq) -> bool { return dq.ecsOverride; }, [](DNSQuestion& dq, bool ecsOverride) { dq.ecsOverride = ecsOverride; });
   g_lua.registerMember<uint16_t (DNSQuestion::*)>("ecsPrefixLength", [](const DNSQuestion& dq) -> uint16_t { return dq.ecsPrefixLength; }, [](DNSQuestion& dq, uint16_t newPrefixLength) { dq.ecsPrefixLength = newPrefixLength; });
+  g_lua.registerMember<boost::optional<uint32_t> (DNSQuestion::*)>("tempFailureTTL",
+      [](const DNSQuestion& dq) -> boost::optional<uint32_t> {
+        return dq.tempFailureTTL;
+      },
+      [](DNSQuestion& dq, boost::optional<uint32_t> newValue) {
+        dq.tempFailureTTL = newValue;
+      }
+    );
   g_lua.registerFunction<bool(DNSQuestion::*)()>("getDO", [](const DNSQuestion& dq) {
       return getEDNSZ((const char*)dq.dh, dq.len) & EDNS_HEADER_FLAG_DO;
     });
index a995d67726723c5fd7e6f175fdc78ad975cbd803..a6ffe645545bbef7adb7bc0c978820a196263140 100644 (file)
@@ -20,6 +20,7 @@
  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
  */
 #include "dnsdist.hh"
+#include "dnsdist-ecs.hh"
 #include "dnsdist-lua.hh"
 
 #include "dnsparser.hh"
@@ -723,7 +724,7 @@ private:
 class RCodeRule : public DNSRule
 {
 public:
-  RCodeRule(int rcode) : d_rcode(rcode)
+  RCodeRule(uint8_t rcode) : d_rcode(rcode)
   {
   }
   bool matches(const DNSQuestion* dq) const override
@@ -735,7 +736,54 @@ public:
     return "rcode=="+RCode::to_s(d_rcode);
   }
 private:
-  int d_rcode;
+  uint8_t d_rcode;
+};
+
+class ERCodeRule : public DNSRule
+{
+public:
+  ERCodeRule(uint8_t rcode) : d_rcode(rcode & 0xF), d_extrcode(rcode >> 4)
+  {
+  }
+  bool matches(const DNSQuestion* dq) const override
+  {
+    // avoid parsing EDNS OPT RR when not needed.
+    if (d_rcode != dq->dh->rcode) {
+      return false;
+    }
+
+    char * optStart = NULL;
+    size_t optLen = 0;
+    bool last = false;
+    int res = locateEDNSOptRR(const_cast<char*>(reinterpret_cast<const char*>(dq->dh)), dq->len, &optStart, &optLen, &last);
+    if (res != 0) {
+      // no EDNS OPT RR
+      return d_extrcode == 0;
+    }
+
+    // root label (1), type (2), class (2), ttl (4) + rdlen (2)
+    if (optLen < 11) {
+      return false;
+    }
+
+    if (*optStart != 0) {
+      // OPT RR Name != '.'
+      return false;
+    }
+    EDNS0Record edns0;
+    static_assert(sizeof(EDNS0Record) == sizeof(uint32_t), "sizeof(EDNS0Record) must match sizeof(uint32_t) AKA RR TTL size");
+    // copy out 4-byte "ttl" (really the EDNS0 record), after root label (1) + type (2) + class (2).
+    memcpy(&edns0, optStart + 5, sizeof edns0);
+
+    return d_extrcode == edns0.extRCode;
+  }
+  string toString() const override
+  {
+    return "ercode=="+ERCode::to_s(d_rcode | (d_extrcode << 4));
+  }
+private:
+  uint8_t d_rcode;     // plain DNS Rcode
+  uint8_t d_extrcode;  // upper bits in EDNS0 record
 };
 
 class RDRule : public DNSRule
@@ -844,158 +892,174 @@ std::shared_ptr<DNSRule> makeRule(const luadnsrule_t& var)
     return std::make_shared<NetmaskGroupRule>(nmg, true);
 }
 
+static boost::uuids::uuid makeRuleID(std::string& id)
+{
+  if (id.empty()) {
+    return t_uuidGenerator();
+  }
+
+  boost::uuids::string_generator gen;
+  return gen(id);
+}
+
+void parseRuleParams(boost::optional<luaruleparams_t> params, boost::uuids::uuid& uuid)
+{
+  string uuidStr;
+
+  if (params) {
+    if (params->count("uuid")) {
+      uuidStr = boost::get<std::string>((*params)["uuid"]);
+    }
+  }
+
+  uuid = makeRuleID(uuidStr);
+}
+
+template<typename T>
+static void showRules(GlobalStateHolder<vector<T> > *someRulActions, boost::optional<bool> showUUIDs) {
+  setLuaNoSideEffect();
+  int num=0;
+  if (showUUIDs.get_value_or(false)) {
+    boost::format fmt("%-3d %-38s %9d %-56s %s\n");
+    g_outputBuffer += (fmt % "#" % "UUID" % "Matches" % "Rule" % "Action").str();
+    for(const auto& lim : someRulActions->getCopy()) {
+      string name = lim.d_rule->toString();
+      g_outputBuffer += (fmt % num % boost::uuids::to_string(lim.d_id) % lim.d_rule->d_matches % name % lim.d_action->toString()).str();
+      ++num;
+    }
+  }
+  else {
+    boost::format fmt("%-3d %9d %-56s %s\n");
+    g_outputBuffer += (fmt % "#" % "Matches" % "Rule" % "Action").str();
+    for(const auto& lim : someRulActions->getCopy()) {
+      string name = lim.d_rule->toString();
+      g_outputBuffer += (fmt % num % lim.d_rule->d_matches % name % lim.d_action->toString()).str();
+      ++num;
+    }
+  }
+}
+
+template<typename T>
+static void rmRule(GlobalStateHolder<vector<T> > *someRulActions, boost::variant<unsigned int, std::string> id) {
+  setLuaSideEffect();
+  auto rules = someRulActions->getCopy();
+  if (auto str = boost::get<std::string>(&id)) {
+    boost::uuids::string_generator gen;
+    const auto uuid = gen(*str);
+    if (rules.erase(std::remove_if(rules.begin(),
+                                    rules.end(),
+                                    [uuid](const T& a) { return a.d_id == uuid; }),
+                    rules.end()) == rules.end()) {
+      g_outputBuffer = "Error: no rule matched\n";
+      return;
+    }
+  }
+  else if (auto pos = boost::get<unsigned int>(&id)) {
+    if (*pos >= rules.size()) {
+      g_outputBuffer = "Error: attempt to delete non-existing rule\n";
+      return;
+    }
+    rules.erase(rules.begin()+*pos);
+  }
+  someRulActions->setState(rules);
+}
+
+template<typename T>
+static void topRule(GlobalStateHolder<vector<T> > *someRulActions) {
+  setLuaSideEffect();
+  auto rules = someRulActions->getCopy();
+  if(rules.empty())
+    return;
+  auto subject = *rules.rbegin();
+  rules.erase(std::prev(rules.end()));
+  rules.insert(rules.begin(), subject);
+  someRulActions->setState(rules);
+}
+
+template<typename T>
+static void mvRule(GlobalStateHolder<vector<T> > *someRespRulActions, unsigned int from, unsigned int to) {
+  setLuaSideEffect();
+  auto rules = someRespRulActions->getCopy();
+  if(from >= rules.size() || to > rules.size()) {
+    g_outputBuffer = "Error: attempt to move rules from/to invalid index\n";
+    return;
+  }
+  auto subject = rules[from];
+  rules.erase(rules.begin()+from);
+  if(to == rules.size())
+    rules.push_back(subject);
+  else {
+    if(from < to)
+      --to;
+    rules.insert(rules.begin()+to, subject);
+  }
+  someRespRulActions->setState(rules);
+}
+
 void setupLuaRules()
 {
   g_lua.writeFunction("makeRule", makeRule);
 
   g_lua.registerFunction<string(std::shared_ptr<DNSRule>::*)()>("toString", [](const std::shared_ptr<DNSRule>& rule) { return rule->toString(); });
 
-  g_lua.writeFunction("showResponseRules", []() {
-      setLuaNoSideEffect();
-      boost::format fmt("%-3d %9d %-50s %s\n");
-      g_outputBuffer += (fmt % "#" % "Matches" % "Rule" % "Action").str();
-      int num=0;
-      for(const auto& lim : g_resprulactions.getCopy()) {
-        string name = lim.first->toString();
-        g_outputBuffer += (fmt % num % lim.first->d_matches % name % lim.second->toString()).str();
-        ++num;
-      }
+  g_lua.writeFunction("showResponseRules", [](boost::optional<bool> showUUIDs) {
+      showRules(&g_resprulactions, showUUIDs);
     });
 
-  g_lua.writeFunction("rmResponseRule", [](unsigned int num) {
-      setLuaSideEffect();
-      auto rules = g_resprulactions.getCopy();
-      if(num >= rules.size()) {
-        g_outputBuffer = "Error: attempt to delete non-existing rule\n";
-        return;
-      }
-      rules.erase(rules.begin()+num);
-      g_resprulactions.setState(rules);
+  g_lua.writeFunction("rmResponseRule", [](boost::variant<unsigned int, std::string> id) {
+      rmRule(&g_resprulactions, id);
     });
 
   g_lua.writeFunction("topResponseRule", []() {
-      setLuaSideEffect();
-      auto rules = g_resprulactions.getCopy();
-      if(rules.empty())
-          return;
-      auto subject = *rules.rbegin();
-      rules.erase(std::prev(rules.end()));
-      rules.insert(rules.begin(), subject);
-      g_resprulactions.setState(rules);
+      topRule(&g_resprulactions);
     });
 
   g_lua.writeFunction("mvResponseRule", [](unsigned int from, unsigned int to) {
-      setLuaSideEffect();
-      auto rules = g_resprulactions.getCopy();
-      if(from >= rules.size() || to > rules.size()) {
-        g_outputBuffer = "Error: attempt to move rules from/to invalid index\n";
-        return;
-      }
-      auto subject = rules[from];
-      rules.erase(rules.begin()+from);
-      if(to == rules.size())
-        rules.push_back(subject);
-      else {
-        if(from < to)
-          --to;
-        rules.insert(rules.begin()+to, subject);
-      }
-      g_resprulactions.setState(rules);
+      mvRule(&g_resprulactions, from, to);
     });
 
-  g_lua.writeFunction("showCacheHitResponseRules", []() {
-      setLuaNoSideEffect();
-      boost::format fmt("%-3d %9d %-50s %s\n");
-      g_outputBuffer += (fmt % "#" % "Matches" % "Rule" % "Action").str();
-      int num=0;
-      for(const auto& lim : g_cachehitresprulactions.getCopy()) {
-        string name = lim.first->toString();
-        g_outputBuffer += (fmt % num % lim.first->d_matches % name % lim.second->toString()).str();
-        ++num;
-      }
+  g_lua.writeFunction("showCacheHitResponseRules", [](boost::optional<bool> showUUIDs) {
+      showRules(&g_cachehitresprulactions, showUUIDs);
     });
 
-  g_lua.writeFunction("rmCacheHitResponseRule", [](unsigned int num) {
-      setLuaSideEffect();
-      auto rules = g_cachehitresprulactions.getCopy();
-      if(num >= rules.size()) {
-        g_outputBuffer = "Error: attempt to delete non-existing rule\n";
-        return;
-      }
-      rules.erase(rules.begin()+num);
-      g_cachehitresprulactions.setState(rules);
+  g_lua.writeFunction("rmCacheHitResponseRule", [](boost::variant<unsigned int, std::string> id) {
+      rmRule(&g_cachehitresprulactions, id);
     });
 
   g_lua.writeFunction("topCacheHitResponseRule", []() {
-      setLuaSideEffect();
-      auto rules = g_cachehitresprulactions.getCopy();
-      if(rules.empty())
-        return;
-      auto subject = *rules.rbegin();
-      rules.erase(std::prev(rules.end()));
-      rules.insert(rules.begin(), subject);
-      g_cachehitresprulactions.setState(rules);
+      topRule(&g_cachehitresprulactions);
     });
 
   g_lua.writeFunction("mvCacheHitResponseRule", [](unsigned int from, unsigned int to) {
-      setLuaSideEffect();
-      auto rules = g_cachehitresprulactions.getCopy();
-      if(from >= rules.size() || to > rules.size()) {
-        g_outputBuffer = "Error: attempt to move rules from/to invalid index\n";
-        return;
-      }
-      auto subject = rules[from];
-      rules.erase(rules.begin()+from);
-      if(to == rules.size())
-        rules.push_back(subject);
-      else {
-        if(from < to)
-          --to;
-        rules.insert(rules.begin()+to, subject);
-      }
-      g_cachehitresprulactions.setState(rules);
+      mvRule(&g_cachehitresprulactions, from, to);
     });
 
-  g_lua.writeFunction("rmRule", [](unsigned int num) {
-      setLuaSideEffect();
-      auto rules = g_rulactions.getCopy();
-      if(num >= rules.size()) {
-       g_outputBuffer = "Error: attempt to delete non-existing rule\n";
-       return;
-      }
-      rules.erase(rules.begin()+num);
-      g_rulactions.setState(rules);
+  g_lua.writeFunction("showSelfAnsweredResponseRules", [](boost::optional<bool> showUUIDs) {
+      showRules(&g_selfansweredresprulactions, showUUIDs);
+    });
+
+  g_lua.writeFunction("rmSelfAnsweredResponseRule", [](boost::variant<unsigned int, std::string> id) {
+      rmRule(&g_selfansweredresprulactions, id);
+    });
+
+  g_lua.writeFunction("topSelfAnsweredResponseRule", []() {
+      topRule(&g_selfansweredresprulactions);
+    });
+
+  g_lua.writeFunction("mvSelfAnsweredResponseRule", [](unsigned int from, unsigned int to) {
+      mvRule(&g_selfansweredresprulactions, from, to);
+    });
+
+  g_lua.writeFunction("rmRule", [](boost::variant<unsigned int, std::string> id) {
+      rmRule(&g_rulactions, id);
     });
 
   g_lua.writeFunction("topRule", []() {
-      setLuaSideEffect();
-      auto rules = g_rulactions.getCopy();
-      if(rules.empty())
-       return;
-      auto subject = *rules.rbegin();
-      rules.erase(std::prev(rules.end()));
-      rules.insert(rules.begin(), subject);
-      g_rulactions.setState(rules);
+      topRule(&g_rulactions);
     });
 
   g_lua.writeFunction("mvRule", [](unsigned int from, unsigned int to) {
-      setLuaSideEffect();
-      auto rules = g_rulactions.getCopy();
-      if(from >= rules.size() || to > rules.size()) {
-       g_outputBuffer = "Error: attempt to move rules from/to invalid index\n";
-       return;
-      }
-
-      auto subject = rules[from];
-      rules.erase(rules.begin()+from);
-      if(to == rules.size())
-       rules.push_back(subject);
-      else {
-       if(from < to)
-         --to;
-       rules.insert(rules.begin()+to, subject);
-      }
-      g_rulactions.setState(rules);
+      mvRule(&g_rulactions, from, to);
     });
 
   g_lua.writeFunction("clearRules", []() {
@@ -1005,14 +1069,14 @@ void setupLuaRules()
         });
     });
 
-  g_lua.writeFunction("setRules", [](std::vector< std::pair<int, std::shared_ptr<std::pair<luadnsrule_t, std::shared_ptr<DNSAction> > > > > newruleactions) {
+  g_lua.writeFunction("setRules", [](std::vector<DNSDistRuleAction>& newruleactions) {
       setLuaSideEffect();
       g_rulactions.modify([newruleactions](decltype(g_rulactions)::value_type& gruleactions) {
           gruleactions.clear();
           for (const auto& newruleaction : newruleactions) {
-            if (newruleaction.second) {
-              auto rule=makeRule(newruleaction.second->first);
-              gruleactions.push_back({rule, newruleaction.second->second});
+            if (newruleaction.d_action) {
+              auto rule=makeRule(newruleaction.d_rule);
+              gruleactions.push_back({rule, newruleaction.d_action, newruleaction.d_id});
             }
           }
         });
@@ -1160,20 +1224,16 @@ void setupLuaRules()
       return std::shared_ptr<DNSRule>(new QNameWireLengthRule(min, max));
     });
 
-  g_lua.writeFunction("RCodeRule", [](int rcode) {
+  g_lua.writeFunction("RCodeRule", [](uint8_t rcode) {
       return std::shared_ptr<DNSRule>(new RCodeRule(rcode));
     });
 
-  g_lua.writeFunction("showRules", []() {
-     setLuaNoSideEffect();
-     boost::format fmt("%-3d %9d %-50s %s\n");
-     g_outputBuffer += (fmt % "#" % "Matches" % "Rule" % "Action").str();
-     int num=0;
-      for(const auto& lim : g_rulactions.getCopy()) {
-        string name = lim.first->toString();
-       g_outputBuffer += (fmt % num % lim.first->d_matches % name % lim.second->toString()).str();
-       ++num;
-      }
+  g_lua.writeFunction("ERCodeRule", [](uint8_t rcode) {
+      return std::shared_ptr<DNSRule>(new ERCodeRule(rcode));
+    });
+
+  g_lua.writeFunction("showRules", [](boost::optional<bool> showUUIDs) {
+      showRules(&g_rulactions, showUUIDs);
     });
 
   g_lua.writeFunction("RDRule", []() {
index cbf482e916e3e92b923976c79804e26ace4d4646..76e43058c7337da6ad3d26891a2bf0d84eaf768d 100644 (file)
@@ -75,7 +75,16 @@ void setupLuaVars()
                                        {"YXRRSET",  RCode::YXRRSet  },
                                        {"NXRRSET",  RCode::NXRRSet  },
                                        {"NOTAUTH",  RCode::NotAuth  },
-                                       {"NOTZONE",  RCode::NotZone  }
+                                       {"NOTZONE",  RCode::NotZone  },
+                                       {"BADVERS",  ERCode::BADVERS },
+                                       {"BADSIG",   ERCode::BADSIG  },
+                                       {"BADKEY",   ERCode::BADKEY  },
+                                       {"BADTIME",  ERCode::BADTIME   },
+                                       {"BADMODE",  ERCode::BADMODE   },
+                                       {"BADNAME",  ERCode::BADNAME   },
+                                       {"BADALG",   ERCode::BADALG    },
+                                       {"BADTRUNC", ERCode::BADTRUNC  },
+                                       {"BADCOOKIE",ERCode::BADCOOKIE },
   };
   vector<pair<string, int> > dd;
   for(const auto& n : QType::names)
index 78e67b3064ea3bf262c89b60a5ff602b0ab01ebd..d8a8c916f2e1e16637922fcba8aa2960452ef3f6 100644 (file)
@@ -35,6 +35,7 @@
 #include "dnswriter.hh"
 #include "dolog.hh"
 #include "lock.hh"
+#include "protobuf.hh"
 #include "sodcrypto.hh"
 
 #include <boost/logic/tribool.hpp>
@@ -308,6 +309,10 @@ void setupLuaConfig(bool client)
                          ret->ipBindAddrNoPort=boost::get<bool>(vars["ipBindAddrNoPort"]);
                        }
 
+                       if(vars.count("addXPF")) {
+                          ret->xpfRRCode=std::stoi(boost::get<string>(vars["addXPF"]));
+                       }
+
                        if(vars.count("maxCheckFailures")) {
                          ret->maxCheckFailures=std::stoi(boost::get<string>(vars["maxCheckFailures"]));
                        }
@@ -490,7 +495,18 @@ void setupLuaConfig(bool client)
   g_lua.writeFunction("shutdown", []() {
 #ifdef HAVE_SYSTEMD
       sd_notify(0, "STOPPING=1");
-#endif
+#endif /* HAVE_SYSTEMD */
+#if 0
+      // Useful for debugging leaks, but might lead to race under load
+      // since other threads are still runing.
+      for(auto& frontend : g_tlslocals) {
+        frontend->cleanup();
+      }
+      g_tlslocals.clear();
+#ifdef HAVE_PROTOBUF
+      google::protobuf::ShutdownProtobufLibrary();
+#endif /* HAVE_PROTOBUF */
+#endif /* 0 */
       _exit(0);
   } );
 
@@ -906,6 +922,7 @@ void setupLuaConfig(bool client)
 #else
       g_outputBuffer="Error: DNSCrypt support is not enabled.\n";
 #endif
+
     });
 
   g_lua.writeFunction("showDNSCryptBinds", []() {
@@ -1282,7 +1299,9 @@ void setupLuaConfig(bool client)
       g_useTCPSinglePipe = flag;
     });
 
-  g_lua.writeFunction("snmpAgent", [](bool enableTraps, boost::optional<std::string> masterSocket) {
+  g_lua.writeFunction("snmpAgent", [client](bool enableTraps, boost::optional<std::string> masterSocket) {
+      if(client)
+        return;
 #ifdef HAVE_NET_SNMP
       if (g_configurationDone) {
         errlog("snmpAgent() cannot be used at runtime!");
@@ -1361,6 +1380,116 @@ void setupLuaConfig(bool client)
       g_outputBuffer="recvmmsg support is not available!\n";
 #endif
     });
+
+    g_lua.writeFunction("addTLSLocal", [client](const std::string& addr, const std::string& certFile, const std::string& keyFile, boost::optional<localbind_t> vars) {
+        if (client)
+          return;
+#ifdef HAVE_DNS_OVER_TLS
+        setLuaSideEffect();
+        if (g_configurationDone) {
+          g_outputBuffer="addTLSLocal cannot be used at runtime!\n";
+          return;
+        }
+        shared_ptr<TLSFrontend> frontend = std::make_shared<TLSFrontend>();
+        frontend->d_certFile = certFile;
+        frontend->d_keyFile = keyFile;
+
+        if (vars) {
+          bool doTCP = true;
+          parseLocalBindVars(vars, doTCP, frontend->d_reusePort, frontend->d_tcpFastOpenQueueSize, frontend->d_interface, frontend->d_cpus);
+
+          if (vars->count("provider")) {
+            frontend->d_provider = boost::get<const string>((*vars)["provider"]);
+          }
+
+          if (vars->count("ciphers")) {
+            frontend->d_ciphers = boost::get<const string>((*vars)["ciphers"]);
+          }
+
+          if (vars->count("ticketKeyFile")) {
+            frontend->d_ticketKeyFile = boost::get<const string>((*vars)["ticketKeyFile"]);
+          }
+
+          if (vars->count("ticketsKeysRotationDelay")) {
+            frontend->d_ticketsKeyRotationDelay = std::stoi(boost::get<const string>((*vars)["ticketsKeysRotationDelay"]));
+          }
+
+          if (vars->count("numberOfTicketsKeys")) {
+            frontend->d_numberOfTicketsKeys = std::stoi(boost::get<const string>((*vars)["numberOfTicketsKeys"]));
+          }
+        }
+
+        try {
+          frontend->d_addr = ComboAddress(addr, 853);
+          vinfolog("Loading TLS provider %s", frontend->d_provider);
+          g_tlslocals.push_back(frontend); /// only works pre-startup, so no sync necessary
+        }
+        catch(const std::exception& e) {
+          g_outputBuffer="Error: "+string(e.what())+"\n";
+        }
+#else
+        g_outputBuffer="DNS over TLS support is not present!\n";
+#endif
+      });
+
+    g_lua.writeFunction("showTLSContexts", [client]() {
+#ifdef HAVE_DNS_OVER_TLS
+        setLuaNoSideEffect();
+        try {
+          ostringstream ret;
+          boost::format fmt("%1$-3d %2$-20.20s %|25t|%3$-14d %|40t|%4$-14d %|54t|%5$-21.21s");
+          //             1    2           3                 4                  5
+          ret << (fmt % "#" % "Address" % "# ticket keys" % "Rotation delay" % "Next rotation" ) << endl;
+          size_t counter = 0;
+          for (const auto& ctx : g_tlslocals) {
+            ret << (fmt % counter % ctx->d_addr.toStringWithPort() % ctx->getTicketsKeysCount() % ctx->getTicketsKeyRotationDelay() % ctx->getNextTicketsKeyRotation()) << endl;
+            counter++;
+          }
+          g_outputBuffer = ret.str();
+        }
+        catch(const std::exception& e) {
+          g_outputBuffer = e.what();
+          throw;
+        }
+#else
+        g_outputBuffer="DNS over TLS support is not present!\n";
+#endif
+      });
+
+    g_lua.writeFunction("getTLSContext", [client](size_t index) {
+        std::shared_ptr<TLSCtx> result = nullptr;
+#ifdef HAVE_DNS_OVER_TLS
+        setLuaNoSideEffect();
+        try {
+          if (index < g_tlslocals.size()) {
+            result = g_tlslocals.at(index)->getContext();
+          }
+          else {
+            errlog("Error: trying to get TLS context with index %zu but we only have %zu\n", index, g_tlslocals.size());
+            g_outputBuffer="Error: trying to get TLS context with index " + std::to_string(index) + " but we only have " + std::to_string(g_tlslocals.size()) + "\n";
+          }
+        }
+        catch(const std::exception& e) {
+          g_outputBuffer="Error: "+string(e.what())+"\n";
+          errlog("Error: %s\n", string(e.what()));
+        }
+#else
+        g_outputBuffer="DNS over TLS support is not present!\n";
+#endif
+        return result;
+      });
+
+    g_lua.registerFunction<void(std::shared_ptr<TLSCtx>::*)()>("rotateTicketsKey", [](std::shared_ptr<TLSCtx> ctx) {
+        if (ctx != nullptr) {
+          ctx->rotateTicketsKey(time(nullptr));
+        }
+      });
+
+    g_lua.registerFunction<void(std::shared_ptr<TLSCtx>::*)(const std::string&)>("loadTicketsKeys", [](std::shared_ptr<TLSCtx> ctx, const std::string& file) {
+        if (ctx != nullptr) {
+          ctx->loadTicketsKeys(file);
+        }
+      });
 }
 
 vector<std::function<void(void)>> setupLua(bool client, const std::string& config)
index 32eabb3d33a6bae5fccf7e0efb95dc24962962e0..ee5a9ba6c3932243a5be1474753728c58d61a0f2 100644 (file)
@@ -99,6 +99,8 @@ private:
 
 typedef boost::variant<string, vector<pair<int, string>>, std::shared_ptr<DNSRule>, DNSName, vector<pair<int, DNSName> > > luadnsrule_t;
 std::shared_ptr<DNSRule> makeRule(const luadnsrule_t& var);
+typedef std::unordered_map<std::string, boost::variant<std::string> > luaruleparams_t;
+void parseRuleParams(boost::optional<luaruleparams_t> params, boost::uuids::uuid& uuid);
 
 typedef NetmaskTree<DynBlock> nmts_t;
 
index 61630b5333e79c10bee7f1d681a2e388ff25c489..ae141f6683ff84e36e40170cdcb9705bd59d76cc 100644 (file)
 #include "dolog.hh"
 #include "lock.hh"
 #include "gettime.hh"
+#include "tcpiohandler.hh"
 #include <thread>
 #include <atomic>
 
 using std::thread;
 using std::atomic;
 
-/* TCP: the grand design. 
-   We forward 'messages' between clients and downstream servers. Messages are 65k bytes large, tops. 
-   An answer might theoretically consist of multiple messages (for example, in the case of AXFR), initially 
+/* TCP: the grand design.
+   We forward 'messages' between clients and downstream servers. Messages are 65k bytes large, tops.
+   An answer might theoretically consist of multiple messages (for example, in the case of AXFR), initially
    we will not go there.
 
    In a sense there is a strong symmetry between UDP and TCP, once a connection to a downstream has been setup.
@@ -184,9 +185,18 @@ catch(...) {
   return false;
 }
 
-static bool sendResponseToClient(int fd, const char* response, uint16_t responseLen)
+static bool getNonBlockingMsgLenFromClient(TCPIOHandler& handler, uint16_t* len)
+try
 {
-  return sendSizeAndMsgWithTimeout(fd, responseLen, response, g_tcpSendTimeout, nullptr, nullptr, 0, 0, 0);
+  uint16_t raw;
+  size_t ret = handler.read(&raw, sizeof raw, g_tcpRecvTimeout);
+  if(ret != sizeof raw)
+    return false;
+  *len = ntohs(raw);
+  return true;
+}
+catch(...) {
+  return false;
 }
 
 static bool maxConnectionDurationReached(unsigned int maxConnectionDuration, time_t start, unsigned int& remainingTime)
@@ -224,7 +234,7 @@ void* tcpClientThread(int pipefd)
 {
   /* we get launched with a pipe on which we receive file descriptors from clients that we own
      from that point on */
-     
+
   bool outstanding = false;
   time_t lastTCPCleanup = time(nullptr);
   
@@ -249,7 +259,7 @@ void* tcpClientThread(int pipefd)
 
     g_tcpclientthreads->decrementQueuedCount();
     ci=*citmp;
-    delete citmp;    
+    delete citmp;
 
     uint16_t qlen, rlen;
     vector<uint8_t> rewrittenResponse;
@@ -267,12 +277,14 @@ void* tcpClientThread(int pipefd)
     }
 
     try {
+      TCPIOHandler handler(ci.fd, g_tcpRecvTimeout, ci.cs->tlsFrontend ? ci.cs->tlsFrontend->getContext() : nullptr, connectionStartTime);
+
       for(;;) {
         unsigned int remainingTime = 0;
         ds = nullptr;
         outstanding = false;
 
-        if(!getNonBlockingMsgLen(ci.fd, &qlen, g_tcpRecvTimeout)) {
+        if(!getNonBlockingMsgLenFromClient(handler, &qlen)) {
           break;
         }
 
@@ -303,8 +315,7 @@ void* tcpClientThread(int pipefd)
         queryBuffer.reserve(qlen + 512);
 
         char* query = &queryBuffer[0];
-        readn2WithTimeout(ci.fd, query, qlen, g_tcpRecvTimeout, remainingTime);
-
+        handler.read(query, qlen, g_tcpRecvTimeout, remainingTime);
 #ifdef HAVE_DNSCRYPT
         std::shared_ptr<DnsCryptQuery> dnsCryptQuery = nullptr;
 
@@ -316,7 +327,7 @@ void* tcpClientThread(int pipefd)
 
           if (!decrypted) {
             if (response.size() > 0) {
-              sendResponseToClient(ci.fd, reinterpret_cast<char*>(response.data()), (uint16_t) response.size());
+              handler.writeSizeAndMsg(response.data(), response.size(), g_tcpSendTimeout);
             }
             break;
           }
@@ -350,12 +361,23 @@ void* tcpClientThread(int pipefd)
 
        if(dq.dh->qr) { // something turned it into a response
           restoreFlags(dh, origFlags);
+
+          DNSResponse dr(dq.qname, dq.qtype, dq.qclass, dq.local, dq.remote, reinterpret_cast<dnsheader*>(query), dq.size, dq.len, true, &queryRealTime);
+#ifdef HAVE_PROTOBUF
+          dr.uniqueId = dq.uniqueId;
+#endif
+          dr.qTag = dq.qTag;
+
+          if (!processResponse(holders.selfAnsweredRespRulactions, dr, &delayMsec)) {
+            goto drop;
+          }
+
 #ifdef HAVE_DNSCRYPT
           if (!encryptResponse(query, &dq.len, dq.size, true, dnsCryptQuery, nullptr, nullptr)) {
             goto drop;
           }
 #endif
-          sendResponseToClient(ci.fd, query, dq.len);
+          handler.writeSizeAndMsg(query, dq.len, g_tcpSendTimeout);
          g_stats.selfAnswered++;
          continue;
        }
@@ -402,7 +424,7 @@ void* tcpClientThread(int pipefd)
               goto drop;
             }
 #endif
-            sendResponseToClient(ci.fd, cachedResponse, cachedResponseSize);
+            handler.writeSizeAndMsg(cachedResponse, cachedResponseSize, g_tcpSendTimeout);
             g_stats.cacheHits++;
             continue;
           }
@@ -417,18 +439,32 @@ void* tcpClientThread(int pipefd)
             dq.dh->rcode = RCode::ServFail;
             dq.dh->qr = true;
 
+            DNSResponse dr(dq.qname, dq.qtype, dq.qclass, dq.local, dq.remote, reinterpret_cast<dnsheader*>(query), dq.size, dq.len, false, &queryRealTime);
+#ifdef HAVE_PROTOBUF
+            dr.uniqueId = dq.uniqueId;
+#endif
+            dr.qTag = dq.qTag;
+
+            if (!processResponse(holders.selfAnsweredRespRulactions, dr, &delayMsec)) {
+              goto drop;
+            }
+
 #ifdef HAVE_DNSCRYPT
             if (!encryptResponse(query, &dq.len, dq.size, true, dnsCryptQuery, nullptr, nullptr)) {
               goto drop;
             }
 #endif
-            sendResponseToClient(ci.fd, query, dq.len);
+            handler.writeSizeAndMsg(query, dq.len, g_tcpSendTimeout);
             continue;
           }
 
           break;
         }
 
+        if (dq.addXPF && ds->xpfRRCode != 0) {
+          addXPF(dq, ds->xpfRRCode);
+        }
+
        int dsock = -1;
        uint16_t downstreamFailures=0;
 #ifdef MSG_FASTOPEN
@@ -473,7 +509,7 @@ void* tcpClientThread(int pipefd)
           sendSizeAndMsgWithTimeout(dsock, dq.len, query, ds->tcpSendTimeout, &ds->remote, &ds->sourceAddr, ds->sourceItf, 0, socketFlags);
         }
         catch(const runtime_error& e) {
-          vinfolog("Downstream connection to %s died on us, getting a new one!", ds->getName());
+          vinfolog("Downstream connection to %s died on us (%s), getting a new one!", ds->getName(), e.what());
           close(dsock);
           dsock=-1;
           sockets.erase(ds->remote);
@@ -491,7 +527,7 @@ void* tcpClientThread(int pipefd)
         if (isXFR) {
           dq.skipCache = true;
         }
-
+        bool firstPacket=true;
       getpacket:;
 
         if(!getNonBlockingMsgLen(dsock, &rlen, ds->tcpRecvTimeout)) {
@@ -533,10 +569,10 @@ void* tcpClientThread(int pipefd)
           break;
         }
 
-        if (!responseContentMatches(response, responseLen, qname, qtype, qclass, ds->remote)) {
+        if (firstPacket && !responseContentMatches(response, responseLen, qname, qtype, qclass, ds->remote)) {
           break;
         }
-
+        firstPacket=false;
         if (!fixUpResponse(&response, &responseLen, &responseSize, qname, origFlags, ednsAdded, ecsAdded, rewrittenResponse, addRoom)) {
           break;
         }
@@ -553,7 +589,7 @@ void* tcpClientThread(int pipefd)
         }
 
        if (packetCache && !dq.skipCache) {
-         packetCache->insert(cacheKey, qname, qtype, qclass, response, responseLen, true, dh->rcode);
+         packetCache->insert(cacheKey, qname, qtype, qclass, response, responseLen, true, dh->rcode, dq.tempFailureTTL);
        }
 
 #ifdef HAVE_DNSCRYPT
@@ -561,7 +597,7 @@ void* tcpClientThread(int pipefd)
           goto drop;
         }
 #endif
-        if (!sendResponseToClient(ci.fd, response, responseLen)) {
+        if (!handler.writeSizeAndMsg(response, responseLen, g_tcpSendTimeout)) {
           break;
         }
 
@@ -595,15 +631,16 @@ void* tcpClientThread(int pipefd)
         rewrittenResponse.clear();
       }
     }
-    catch(...){}
+    catch(...) {}
 
   drop:;
-    
+
     vinfolog("Closing TCP client connection with %s", ci.remote.toStringWithPort());
     if (ci.fd >= 0) {
       close(ci.fd);
     }
     ci.fd = -1;
+
     if (ds && outstanding) {
       outstanding = false;
       --ds->outstanding;
@@ -727,7 +764,6 @@ void* tcpAcceptorThread(void* p)
   return 0;
 }
 
-
 bool getMsgLen32(int fd, uint32_t* len)
 try
 {
index 3d5186156062284917e56b997c9b7f9dddcb8209..b2bca433514cbd52292c94488c1b5bc8d61efa8b 100644 (file)
@@ -219,6 +219,26 @@ static void addCustomHeaders(YaHTTP::Response& resp, const boost::optional<std::
   }
 }
 
+template<typename T>
+static json11::Json::array someResponseRulesToJson(GlobalStateHolder<vector<T>>* someResponseRules)
+{
+  using namespace json11;
+  Json::array responseRules;
+  int num=0;
+  auto localResponseRules = someResponseRules->getCopy();
+  for(const auto& a : localResponseRules) {
+    Json::object rule{
+      {"id", num++},
+      {"uuid", boost::uuids::to_string(a.d_id)},
+      {"matches", (double)a.d_rule->d_matches},
+      {"rule", a.d_rule->toString()},
+      {"action", a.d_action->toString()},
+    };
+    responseRules.push_back(rule);
+  }
+  return responseRules;
+}
+
 static void connectionThread(int sock, ComboAddress remote, string password, string apiKey, const boost::optional<std::map<std::string, std::string> >& customHeaders)
 {
   using namespace json11;
@@ -448,39 +468,18 @@ static void connectionThread(int sock, ComboAddress remote, string password, str
       for(const auto& a : localRules) {
        Json::object rule{
           {"id", num++},
-          {"matches", (double)a.first->d_matches},
-          {"rule", a.first->toString()},
-          {"action", a.second->toString()},
-          {"action-stats", a.second->getStats()}
+          {"uuid", boost::uuids::to_string(a.d_id)},
+          {"matches", (double)a.d_rule->d_matches},
+          {"rule", a.d_rule->toString()},
+          {"action", a.d_action->toString()},
+          {"action-stats", a.d_action->getStats()}
         };
        rules.push_back(rule);
       }
       
-      Json::array responseRules;
-      auto localResponseRules = g_resprulactions.getCopy();
-      num=0;
-      for(const auto& a : localResponseRules) {
-        Json::object rule{
-          {"id", num++},
-          {"matches", (double)a.first->d_matches},
-          {"rule", a.first->toString()},
-          {"action", a.second->toString()},
-        };
-        responseRules.push_back(rule);
-      }
-
-      Json::array cacheHitResponseRules;
-      num=0;
-      auto localCacheHitResponseRules = g_cachehitresprulactions.getCopy();
-      for(const auto& a : localCacheHitResponseRules) {
-        Json::object rule{
-          {"id", num++},
-          {"matches", (double)a.first->d_matches},
-          {"rule", a.first->toString()},
-          {"action", a.second->toString()},
-        };
-        cacheHitResponseRules.push_back(rule);
-      }
+      auto responseRules = someResponseRulesToJson(&g_resprulactions);
+      auto cacheHitResponseRules = someResponseRulesToJson(&g_cachehitresprulactions);
+      auto selfAnsweredResponseRules = someResponseRulesToJson(&g_selfansweredresprulactions);
 
       string acl;
 
@@ -506,6 +505,7 @@ static void connectionThread(int sock, ComboAddress remote, string password, str
         { "rules", rules},
         { "response-rules", responseRules},
         { "cache-hit-response-rules", cacheHitResponseRules},
+        { "self-answered-response-rules", selfAnsweredResponseRules},
         { "acl", acl},
         { "local", localaddresses}
       };
index c3018e0bd018c460386a524b5e73ff47378761ff..bd464f8ff430abdd076e83b304a088101ccef3d3 100644 (file)
  * along with this program; if not, write to the Free Software
  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
  */
-#include "dnsdist.hh"
-#include "dnsdist-ecs.hh"
-#include "sstuff.hh"
-#include "misc.hh"
-#include <netinet/tcp.h>
-#include <limits>
-#include "dolog.hh"
 
-#if defined (__OpenBSD__)
-#include <readline/readline.h>
-#else
-#include <editline/readline.h>
-#endif
+#include "config.h"
 
-#include "dnsname.hh"
-#include "dnswriter.hh"
-#include "base64.hh"
 #include <fstream>
-#include "delaypipe.hh"
-#include <unistd.h>
-#include "sodcrypto.hh"
-#include "dnsdist-lua.hh"
+#include <getopt.h>
 #include <grp.h>
+#include <limits>
+#include <netinet/tcp.h>
 #include <pwd.h>
-#include "lock.hh"
-#include <getopt.h>
 #include <sys/resource.h>
-#include "dnsdist-cache.hh"
-#include "gettime.hh"
-#include "ednsoptions.hh"
+#include <unistd.h>
+
+#if defined (__OpenBSD__) || defined(__NetBSD__)
+#include <readline/readline.h>
+#else
+#include <editline/readline.h>
+#endif
 
 #ifdef HAVE_SYSTEMD
 #include <systemd/sd-daemon.h>
 #endif
 
+#include "dnsdist.hh"
+#include "dnsdist-cache.hh"
+#include "dnsdist-ecs.hh"
+#include "dnsdist-lua.hh"
+
+#include "base64.hh"
+#include "delaypipe.hh"
+#include "dolog.hh"
+#include "dnsname.hh"
+#include "dnswriter.hh"
+#include "ednsoptions.hh"
+#include "gettime.hh"
+#include "lock.hh"
+#include "misc.hh"
+#include "sodcrypto.hh"
+#include "sstuff.hh"
+#include "xpf.hh"
+
 #ifdef HAVE_PROTOBUF
 thread_local boost::uuids::random_generator t_uuidGenerator;
 #endif
@@ -83,7 +88,9 @@ bool g_syslog{true};
 
 GlobalStateHolder<NetmaskGroup> g_ACL;
 string g_outputBuffer;
+
 vector<std::tuple<ComboAddress, bool, bool, int, string, std::set<int>>> g_locals;
+std::vector<std::shared_ptr<TLSFrontend>> g_tlslocals;
 #ifdef HAVE_DNSCRYPT
 std::vector<std::tuple<ComboAddress,DnsCryptContext,bool, int, string, std::set<int>>> g_dnsCryptLocals;
 #endif
@@ -132,9 +139,11 @@ DNSDistSNMPAgent* g_snmpAgent{nullptr};
 
    If all downstreams are over QPS, we pick the fastest server */
 
-GlobalStateHolder<vector<pair<std::shared_ptr<DNSRule>, std::shared_ptr<DNSAction> > > > g_rulactions;
-GlobalStateHolder<vector<pair<std::shared_ptr<DNSRule>, std::shared_ptr<DNSResponseAction> > > > g_resprulactions;
-GlobalStateHolder<vector<pair<std::shared_ptr<DNSRule>, std::shared_ptr<DNSResponseAction> > > > g_cachehitresprulactions;
+GlobalStateHolder<vector<DNSDistRuleAction> > g_rulactions;
+GlobalStateHolder<vector<DNSDistResponseRuleAction> > g_resprulactions;
+GlobalStateHolder<vector<DNSDistResponseRuleAction> > g_cachehitresprulactions;
+GlobalStateHolder<vector<DNSDistResponseRuleAction> > g_selfansweredresprulactions;
+
 Rings g_rings;
 QueryCount g_qcount;
 
@@ -467,7 +476,7 @@ try {
       }
 
       if (ids->packetCache && !ids->skipCache) {
-        ids->packetCache->insert(ids->cacheKey, ids->qname, ids->qtype, ids->qclass, response, responseLen, false, dh->rcode);
+        ids->packetCache->insert(ids->cacheKey, ids->qname, ids->qtype, ids->qclass, response, responseLen, false, dh->rcode, ids->tempFailureTTL);
       }
 
       if (ids->cs && !ids->cs->muted) {
@@ -979,9 +988,9 @@ bool processQuery(LocalHolders& holders, DNSQuestion& dq, string& poolname, int*
   DNSAction::Action action=DNSAction::Action::None;
   string ruleresult;
   for(const auto& lr : *holders.rulactions) {
-    if(lr.first->matches(&dq)) {
-      lr.first->d_matches++;
-      action=(*lr.second)(&dq, &ruleresult);
+    if(lr.d_rule->matches(&dq)) {
+      lr.d_rule->d_matches++;
+      action=(*lr.d_action)(&dq, &ruleresult);
 
       switch(action) {
       case DNSAction::Action::Allow:
@@ -1032,14 +1041,14 @@ bool processQuery(LocalHolders& holders, DNSQuestion& dq, string& poolname, int*
   return true;
 }
 
-bool processResponse(LocalStateHolder<vector<pair<std::shared_ptr<DNSRule>, std::shared_ptr<DNSResponseAction> > > >& localRespRulactions, DNSResponse& dr, int* delayMsec)
+bool processResponse(LocalStateHolder<vector<DNSDistResponseRuleAction> >& localRespRulactions, DNSResponse& dr, int* delayMsec)
 {
   DNSResponseAction::Action action=DNSResponseAction::Action::None;
   std::string ruleresult;
   for(const auto& lr : *localRespRulactions) {
-    if(lr.first->matches(&dr)) {
-      lr.first->d_matches++;
-      action=(*lr.second)(&dr, &ruleresult);
+    if(lr.d_rule->matches(&dr)) {
+      lr.d_rule->d_matches++;
+      action=(*lr.d_action)(&dr, &ruleresult);
       switch(action) {
       case DNSResponseAction::Action::Allow:
         return true;
@@ -1093,6 +1102,38 @@ static ssize_t udpClientSendRequestToBackend(DownstreamState* ss, const int sd,
   return result;
 }
 
+bool addXPF(DNSQuestion& dq, uint16_t optionCode)
+{
+  std::string payload = generateXPFPayload(dq.tcp, *dq.remote, *dq.local);
+  uint8_t root = '\0';
+  dnsrecordheader drh;
+  drh.d_type = htons(optionCode);
+  drh.d_class = htons(QClass::IN);
+  drh.d_ttl = 0;
+  drh.d_clen = htons(payload.size());
+  size_t recordHeaderLen = sizeof(root) + sizeof(drh);
+
+  size_t available = dq.size - dq.len;
+
+  if ((payload.size() + recordHeaderLen) > available) {
+    return false;
+  }
+
+  size_t pos = dq.len;
+  memcpy(reinterpret_cast<char*>(dq.dh) + pos, &root, sizeof(root));
+  pos += sizeof(root);
+  memcpy(reinterpret_cast<char*>(dq.dh) + pos, &drh, sizeof(drh));
+  pos += sizeof(drh);
+  memcpy(reinterpret_cast<char*>(dq.dh) + pos, payload.data(), payload.size());
+  pos += payload.size();
+
+  dq.len = pos;
+
+  dq.dh->arcount = htons(ntohs(dq.dh->arcount) + 1);
+
+  return true;
+}
+
 static bool isUDPQueryAcceptable(ClientState& cs, LocalHolders& holders, const struct msghdr* msgh, const ComboAddress& remote, ComboAddress& dest)
 {
   if (msgh->msg_flags & MSG_TRUNC) {
@@ -1235,6 +1276,16 @@ static void processUDPQuery(ClientState& cs, LocalHolders& holders, const struct
         char* response = query;
         uint16_t responseLen = dq.len;
 
+        DNSResponse dr(dq.qname, dq.qtype, dq.qclass, dq.local, dq.remote, reinterpret_cast<dnsheader*>(response), dq.size, responseLen, false, &realTime);
+#ifdef HAVE_PROTOBUF
+        dr.uniqueId = dq.uniqueId;
+#endif
+        dr.qTag = dq.qTag;
+
+        if (!processResponse(holders.selfAnsweredRespRulactions, dr, &delayMsec)) {
+          return;
+        }
+
 #ifdef HAVE_DNSCRYPT
         if (!encryptResponse(response, &responseLen, dq.size, false, dnsCryptQuery, nullptr, nullptr)) {
           return;
@@ -1329,6 +1380,16 @@ static void processUDPQuery(ClientState& cs, LocalHolders& holders, const struct
         dq.dh->rcode = RCode::ServFail;
         dq.dh->qr = true;
 
+        DNSResponse dr(dq.qname, dq.qtype, dq.qclass, dq.local, dq.remote, reinterpret_cast<dnsheader*>(response), dq.size, responseLen, false, &realTime);
+#ifdef HAVE_PROTOBUF
+        dr.uniqueId = dq.uniqueId;
+#endif
+        dr.qTag = dq.qTag;
+
+        if (!processResponse(holders.selfAnsweredRespRulactions, dr, &delayMsec)) {
+          return;
+        }
+
 #ifdef HAVE_DNSCRYPT
         if (!encryptResponse(response, &responseLen, dq.size, false, dnsCryptQuery, nullptr, nullptr)) {
           return;
@@ -1349,6 +1410,10 @@ static void processUDPQuery(ClientState& cs, LocalHolders& holders, const struct
       return;
     }
 
+    if (dq.addXPF && ss->xpfRRCode != 0) {
+      addXPF(dq, ss->xpfRRCode);
+    }
+
     ss->queries++;
 
     unsigned int idOffset = (ss->idOffset++) % ss->idStates.size();
@@ -1371,6 +1436,7 @@ static void processUDPQuery(ClientState& cs, LocalHolders& holders, const struct
     ids->qtype = dq.qtype;
     ids->qclass = dq.qclass;
     ids->delayMsec = delayMsec;
+    ids->tempFailureTTL = dq.tempFailureTTL;
     ids->origFlags = origFlags;
     ids->cacheKey = cacheKey;
     ids->skipCache = dq.skipCache;
@@ -2078,6 +2144,16 @@ try
       cout<<"dnsdist "<<VERSION<<" ("<<LUA_RELEASE<<")"<<endl;
 #endif
       cout<<"Enabled features: ";
+#ifdef HAVE_DNS_OVER_TLS
+      cout<<"dns-over-tls(";
+#ifdef HAVE_GNUTLS
+      cout<<"gnutls ";
+#endif
+#ifdef HAVE_LIBSSL
+      cout<<"openssl";
+#endif
+      cout<<") ";
+#endif
 #ifdef HAVE_DNSCRYPT
       cout<<"dnscrypt ";
 #endif
@@ -2226,7 +2302,7 @@ try
 
     SSetsockopt(cs->tcpFD, SOL_SOCKET, SO_REUSEADDR, 1);
 #ifdef TCP_DEFER_ACCEPT
-    SSetsockopt(cs->tcpFD, SOL_TCP,TCP_DEFER_ACCEPT, 1);
+    SSetsockopt(cs->tcpFD, IPPROTO_TCP, TCP_DEFER_ACCEPT, 1);
 #endif
     if (std::get<3>(local) > 0) {
 #ifdef TCP_FASTOPEN
@@ -2329,7 +2405,7 @@ try
     cs->tcpFD = SSocket(cs->local.sin4.sin_family, SOCK_STREAM, 0);
     SSetsockopt(cs->tcpFD, SOL_SOCKET, SO_REUSEADDR, 1);
 #ifdef TCP_DEFER_ACCEPT
-    SSetsockopt(cs->tcpFD, SOL_TCP,TCP_DEFER_ACCEPT, 1);
+    SSetsockopt(cs->tcpFD, IPPROTO_TCP, TCP_DEFER_ACCEPT, 1);
 #endif
     if (std::get<3>(dcLocal) > 0) {
 #ifdef TCP_FASTOPEN
@@ -2379,6 +2455,62 @@ try
   }
 #endif
 
+  for(auto& frontend : g_tlslocals) {
+    ClientState* cs = new ClientState;
+    cs->local = frontend->d_addr;
+    cs->tcpFD = SSocket(cs->local.sin4.sin_family, SOCK_STREAM, 0);
+    SSetsockopt(cs->tcpFD, SOL_SOCKET, SO_REUSEADDR, 1);
+#ifdef TCP_DEFER_ACCEPT
+    SSetsockopt(cs->tcpFD, IPPROTO_TCP, TCP_DEFER_ACCEPT, 1);
+#endif
+    if (frontend->d_tcpFastOpenQueueSize > 0) {
+#ifdef TCP_FASTOPEN
+      SSetsockopt(cs->tcpFD, IPPROTO_TCP, TCP_FASTOPEN, frontend->d_tcpFastOpenQueueSize);
+#else
+      warnlog("TCP Fast Open has been configured on local address '%s' but is not supported", cs->local.toStringWithPort());
+#endif
+    }
+    if (frontend->d_reusePort) {
+#ifdef SO_REUSEPORT
+      SSetsockopt(cs->tcpFD, SOL_SOCKET, SO_REUSEPORT, 1);
+#else
+      warnlog("SO_REUSEPORT has been configured on local address '%s' but is not supported", cs.local.toStringWithPort());
+#endif
+    }
+    if(cs->local.sin4.sin_family == AF_INET6) {
+      SSetsockopt(cs->tcpFD, IPPROTO_IPV6, IPV6_V6ONLY, 1);
+    }
+
+    if (!frontend->d_interface.empty()) {
+#ifdef SO_BINDTODEVICE
+      int res = setsockopt(cs->tcpFD, SOL_SOCKET, SO_BINDTODEVICE, frontend->d_interface.c_str(), frontend->d_interface.length());
+      if (res != 0) {
+        warnlog("Error setting up the interface on local address '%s': %s", cs->local.toStringWithPort(), strerror(errno));
+      }
+#else
+      warnlog("An interface has been configured on local address '%s' but SO_BINDTODEVICE is not supported", cs->local.toStringWithPort());
+#endif
+    }
+
+    cs->cpus = frontend->d_cpus;
+
+    bindAny(cs->local.sin4.sin_family, cs->tcpFD);
+    if (frontend->setupTLS()) {
+      cs->tlsFrontend = frontend;
+      SBind(cs->tcpFD, cs->local);
+      SListen(cs->tcpFD, 64);
+      warnlog("Listening on %s for TLS", cs->local.toStringWithPort());
+      toLaunch.push_back(cs);
+      g_frontends.push_back(cs);
+      tcpBindsCount++;
+    }
+    else {
+      delete cs;
+      errlog("Error while setting up TLS on local address '%s', exiting", cs->local.toStringWithPort());
+      _exit(EXIT_FAILURE);
+    }
+  }
+
   if(g_cmdLine.beDaemon) {
     g_console=false;
     daemonize();
index b8a05d5235b14a51e5d51f12c8624e1e73534c9a..f20b1a1fdb5f881e3ed8f9bc4a600212e8970659 100644 (file)
 #include "bpf-filter.hh"
 #include <string>
 #include <unordered_map>
+#include "tcpiohandler.hh"
 
-
-#ifdef HAVE_PROTOBUF
 #include <boost/uuid/uuid.hpp>
 #include <boost/uuid/uuid_generators.hpp>
-#endif
+#include <boost/uuid/uuid_io.hpp>
 
 void* carbonDumpThread();
 uint64_t uptimeOfProcess(const std::string& str);
@@ -123,13 +122,11 @@ private:
   static constexpr char const *strSep = "\t";
 };
 
-#ifdef HAVE_PROTOBUF
 extern thread_local boost::uuids::random_generator t_uuidGenerator;
-#endif
 
 struct DNSQuestion
 {
-  DNSQuestion(const DNSName* name, uint16_t type, uint16_t class_, const ComboAddress* lc, const ComboAddress* rem, struct dnsheader* header, size_t bufferSize, uint16_t queryLen, bool isTcp): qname(name), qtype(type), qclass(class_), local(lc), remote(rem), dh(header), size(bufferSize), len(queryLen), ecsPrefixLength(rem->sin4.sin_family == AF_INET ? g_ECSSourcePrefixV4 : g_ECSSourcePrefixV6), tcp(isTcp), ecsOverride(g_ECSOverride) { }
+  DNSQuestion(const DNSName* name, uint16_t type, uint16_t class_, const ComboAddress* lc, const ComboAddress* rem, struct dnsheader* header, size_t bufferSize, uint16_t queryLen, bool isTcp): qname(name), qtype(type), qclass(class_), local(lc), remote(rem), dh(header), size(bufferSize), len(queryLen), ecsPrefixLength(rem->sin4.sin_family == AF_INET ? g_ECSSourcePrefixV4 : g_ECSSourcePrefixV6), tempFailureTTL(boost::none), tcp(isTcp), ecsOverride(g_ECSOverride) { }
 
 #ifdef HAVE_PROTOBUF
   boost::optional<boost::uuids::uuid> uniqueId;
@@ -144,10 +141,12 @@ struct DNSQuestion
   size_t size;
   uint16_t len;
   uint16_t ecsPrefixLength;
+  boost::optional<uint32_t> tempFailureTTL;
   const bool tcp;
   bool skipCache{false};
   bool ecsOverride;
   bool useECS{true};
+  bool addXPF{true};
 };
 
 struct DNSResponse : DNSQuestion
@@ -386,7 +385,7 @@ struct ClientState;
 
 struct IDState
 {
-  IDState() : origFD(-1), sentTime(true), delayMsec(0) { origDest.sin4.sin_family = 0;}
+  IDState() : origFD(-1), sentTime(true), delayMsec(0), tempFailureTTL(boost::none) { origDest.sin4.sin_family = 0;}
   IDState(const IDState& orig)
   {
     origFD = orig.origFD;
@@ -394,6 +393,7 @@ struct IDState
     origRemote = orig.origRemote;
     origDest = orig.origDest;
     delayMsec = orig.delayMsec;
+    tempFailureTTL = orig.tempFailureTTL;
     age.store(orig.age.load());
   }
 
@@ -419,6 +419,7 @@ struct IDState
   uint16_t origID;                                            // 2
   uint16_t origFlags;                                         // 2
   int delayMsec;
+  boost::optional<uint32_t> tempFailureTTL;
   bool ednsAdded{false};
   bool ecsAdded{false};
   bool skipCache{false};
@@ -496,6 +497,7 @@ struct ClientState
 #ifdef HAVE_DNSCRYPT
   DnsCryptContext* dnscryptCtx{0};
 #endif
+  shared_ptr<TLSFrontend> tlsFrontend;
   std::atomic<uint64_t> queries{0};
   int udpFD{-1};
   int tcpFD{-1};
@@ -622,6 +624,7 @@ struct DownstreamState
   int tcpSendTimeout{30};
   unsigned int sourceItf{0};
   uint16_t retries{5};
+  uint16_t xpfRRCode{0};
   uint8_t currentCheckFailures{0};
   uint8_t maxCheckFailures{1};
   StopWatch sw;
@@ -724,6 +727,20 @@ enum ednsHeaderFlags {
   EDNS_HEADER_FLAG_DO = 32768
 };
 
+struct DNSDistRuleAction
+{
+  std::shared_ptr<DNSRule> d_rule;
+  std::shared_ptr<DNSAction> d_action;
+  boost::uuids::uuid d_id;
+};
+
+struct DNSDistResponseRuleAction
+{
+  std::shared_ptr<DNSRule> d_rule;
+  std::shared_ptr<DNSResponseAction> d_action;
+  boost::uuids::uuid d_id;
+};
+
 extern GlobalStateHolder<SuffixMatchTree<DynBlock>> g_dynblockSMT;
 extern DNSAction::Action g_dynBlockAction;
 
@@ -731,14 +748,16 @@ extern GlobalStateHolder<vector<CarbonConfig> > g_carbon;
 extern GlobalStateHolder<ServerPolicy> g_policy;
 extern GlobalStateHolder<servers_t> g_dstates;
 extern GlobalStateHolder<pools_t> g_pools;
-extern GlobalStateHolder<vector<pair<std::shared_ptr<DNSRule>, std::shared_ptr<DNSAction> > > > g_rulactions;
-extern GlobalStateHolder<vector<pair<std::shared_ptr<DNSRule>, std::shared_ptr<DNSResponseAction> > > > g_resprulactions;
-extern GlobalStateHolder<vector<pair<std::shared_ptr<DNSRule>, std::shared_ptr<DNSResponseAction> > > > g_cachehitresprulactions;
+extern GlobalStateHolder<vector<DNSDistRuleAction> > g_rulactions;
+extern GlobalStateHolder<vector<DNSDistResponseRuleAction> > g_resprulactions;
+extern GlobalStateHolder<vector<DNSDistResponseRuleAction> > g_cachehitresprulactions;
+extern GlobalStateHolder<vector<DNSDistResponseRuleAction> > g_selfansweredresprulactions;
 extern GlobalStateHolder<NetmaskGroup> g_ACL;
 
 extern ComboAddress g_serverControl; // not changed during runtime
 
 extern std::vector<std::tuple<ComboAddress, bool, bool, int, std::string, std::set<int>>> g_locals; // not changed at runtime (we hope XXX)
+extern std::vector<shared_ptr<TLSFrontend>> g_tlslocals;
 extern vector<ClientState*> g_frontends;
 extern std::string g_key; // in theory needs locking
 extern bool g_truncateTC;
@@ -791,14 +810,15 @@ extern std::vector<std::shared_ptr<DynBPFFilter> > g_dynBPFFilters;
 
 struct LocalHolders
 {
-  LocalHolders(): acl(g_ACL.getLocal()), policy(g_policy.getLocal()), rulactions(g_rulactions.getLocal()), cacheHitRespRulactions(g_cachehitresprulactions.getLocal()), servers(g_dstates.getLocal()), dynNMGBlock(g_dynblockNMG.getLocal()), dynSMTBlock(g_dynblockSMT.getLocal()), pools(g_pools.getLocal())
+  LocalHolders(): acl(g_ACL.getLocal()), policy(g_policy.getLocal()), rulactions(g_rulactions.getLocal()), cacheHitRespRulactions(g_cachehitresprulactions.getLocal()), selfAnsweredRespRulactions(g_selfansweredresprulactions.getLocal()), servers(g_dstates.getLocal()), dynNMGBlock(g_dynblockNMG.getLocal()), dynSMTBlock(g_dynblockSMT.getLocal()), pools(g_pools.getLocal())
   {
   }
 
   LocalStateHolder<NetmaskGroup> acl;
   LocalStateHolder<ServerPolicy> policy;
-  LocalStateHolder<vector<pair<std::shared_ptr<DNSRule>, std::shared_ptr<DNSAction> > > > rulactions;
-  LocalStateHolder<vector<pair<std::shared_ptr<DNSRule>, std::shared_ptr<DNSResponseAction> > > > cacheHitRespRulactions;
+  LocalStateHolder<vector<DNSDistRuleAction> > rulactions;
+  LocalStateHolder<vector<DNSDistResponseRuleAction> > cacheHitRespRulactions;
+  LocalStateHolder<vector<DNSDistResponseRuleAction> > selfAnsweredRespRulactions;
   LocalStateHolder<servers_t> servers;
   LocalStateHolder<NetmaskTree<DynBlock> > dynNMGBlock;
   LocalStateHolder<SuffixMatchTree<DynBlock> > dynSMTBlock;
@@ -839,7 +859,7 @@ void resetLuaSideEffect(); // reset to indeterminate state
 
 bool responseContentMatches(const char* response, const uint16_t responseLen, const DNSName& qname, const uint16_t qtype, const uint16_t qclass, const ComboAddress& remote);
 bool processQuery(LocalHolders& holders, DNSQuestion& dq, string& poolname, int* delayMsec, const struct timespec& now);
-bool processResponse(LocalStateHolder<vector<pair<std::shared_ptr<DNSRule>, std::shared_ptr<DNSResponseAction> > > >& localRespRulactions, DNSResponse& dr, int* delayMsec);
+bool processResponse(LocalStateHolder<vector<DNSDistResponseRuleAction> >& localRespRulactions, DNSResponse& dr, int* delayMsec);
 bool fixUpResponse(char** response, uint16_t* responseLen, size_t* responseSize, const DNSName& qname, uint16_t origFlags, bool ednsAdded, bool ecsAdded, std::vector<uint8_t>& rewrittenResponse, uint16_t addRoom);
 void restoreFlags(struct dnsheader* dh, uint16_t origFlags);
 bool checkQueryHeaders(const struct dnsheader* dh);
@@ -851,6 +871,8 @@ int handleDnsCryptQuery(DnsCryptContext* ctx, char* packet, uint16_t len, std::s
 bool encryptResponse(char* response, uint16_t* responseLen, size_t responseSize, bool tcp, std::shared_ptr<DnsCryptQuery> dnsCryptQuery, dnsheader** dh, dnsheader* dhCopy);
 #endif
 
+bool addXPF(DNSQuestion& dq, uint16_t optionCode);
+
 #include "dnsdist-snmp.hh"
 
 extern bool g_snmpEnabled;
index 48165539e97f5ea8eeb52b081180c0dd1954245a..30b4eddab5feebe3aa85f6c2e4b4386f4131b9af 100644 (file)
@@ -28,6 +28,15 @@ if HAVE_RE2
 AM_CPPFLAGS += $(RE2_CFLAGS)
 endif
 
+if HAVE_DNS_OVER_TLS
+if HAVE_LIBSSL
+AM_CPPFLAGS += $(LIBSSL_CFLAGS)
+endif
+
+if HAVE_GNUTLS
+AM_CPPFLAGS += $(GNUTLS_CFLAGS)
+endif
+endif
 
 EXTRA_DIST=dnslabeltext.rl \
           dnsdistconf.lua \
@@ -116,6 +125,8 @@ dnsdist_SOURCES = \
        sodcrypto.cc sodcrypto.hh \
        sstuff.hh \
        statnode.cc statnode.hh \
+       tcpiohandler.cc tcpiohandler.hh \
+       xpf.cc xpf.hh \
        ext/luawrapper/include/LuaContext.hpp \
        ext/json11/json11.cpp \
        ext/json11/json11.hpp \
@@ -141,6 +152,16 @@ if HAVE_RE2
 dnsdist_LDADD += $(RE2_LIBS)
 endif
 
+if HAVE_DNS_OVER_TLS
+if HAVE_GNUTLS
+dnsdist_LDADD += -lgnutls
+endif
+
+if HAVE_LIBSSL
+dnsdist_LDADD += $(LIBSSL_LIBS) $(LIBCRYPTO_LIBS)
+endif
+endif
+
 if !HAVE_LUA_HPP
 BUILT_SOURCES += lua.hpp
 nodist_dnsdist_SOURCES = lua.hpp
@@ -204,7 +225,8 @@ testrunner_SOURCES = \
        sholder.hh \
        sodcrypto.cc \
        sstuff.hh \
-       testrunner.cc
+       testrunner.cc \
+       xpf.cc xpf.hh
 
 testrunner_LDFLAGS = \
        $(AM_LDFLAGS) \
index a30e79c4f7e51880252f31df7efcddd37683b74c..5354fea36dc46281d92b874721d174e1a96fcc6e 100644 (file)
@@ -22,15 +22,10 @@ PDNS_CHECK_OS
 PDNS_CHECK_NETWORK_LIBS
 PDNS_CHECK_PTHREAD_NP
 
-boost_required_version=1.35
 
 PDNS_WITH_PROTOBUF
-AS_IF([test "x$PROTOBUF_LIBS" != "x" -a x"$PROTOC" != "x"],
-  # The protobuf code needs boost::uuid, which is available from 1.42 onward
-  [AC_MSG_WARN([Bumping minimal Boost requirement to 1.42. To keep the requirement at 1.35, disable protobuf support])
-  boost_required_version=1.42]
-)
 
+boost_required_version=1.42
 BOOST_REQUIRE([$boost_required_version])
 
 PDNS_ENABLE_UNIT_TESTS
@@ -54,6 +49,19 @@ AS_IF([test "x$LUAPC" = "x" -a "x$LUAJITPC" = "x"], [
 ])
 PDNS_CHECK_LUA_HPP
 
+DNSDIST_ENABLE_DNS_OVER_TLS
+DNSDIST_CHECK_GNUTLS
+DNSDIST_CHECK_LIBSSL
+AS_IF([test "x$enable_dns_over_tls" != "xno"], [
+  AS_IF([test "$HAVE_LIBSSL" = "1"], [
+    # we need libcrypto if libssl is enabled
+    PDNS_CHECK_LIBCRYPTO
+  ])
+  AS_IF([test "$HAVE_GNUTLS" = "0" -a "$HAVE_LIBSSL" = "0"], [
+    AC_MSG_ERROR([DNS over TLS support requested but neither GnuTLS nor OpenSSL are available])
+  ])
+])
+
 AX_CXX_COMPILE_STDCXX_11([ext], [mandatory])
 
 AC_MSG_CHECKING([whether we will enable compiler security checks])
@@ -145,4 +153,16 @@ AS_IF([test "x$NET_SNMP_LIBS" != "x"],
   [AC_MSG_NOTICE([SNMP: yes])],
   [AC_MSG_NOTICE([SNMP: no])]
 )
+AS_IF([test "x$enable_dns_over_tls" != "xno"],
+  [AC_MSG_NOTICE([DNS over TLS: yes])],
+  [AC_MSG_NOTICE([DNS over TLS: no])]
+)
+AS_IF([test "x$GNUTLS_LIBS" != "x"],
+  [AC_MSG_NOTICE([GnuTLS: yes])],
+  [AC_MSG_NOTICE([GnuTLS: no])]
+)
+AS_IF([test "x$LIBSSL_LIBS" != "x"],
+  [AC_MSG_NOTICE([OpenSSL: yes])],
+  [AC_MSG_NOTICE([OpenSSL: no])]
+)
 AC_MSG_NOTICE([])
index 23e429b0fbdcd62bd29a9b02170742fcfd4ab869..dd2c2962f3020f93a5751fec2a4f4a87e94b0e59 100644 (file)
@@ -9,7 +9,7 @@ Either we block it outright, like this:
 
 .. code-block:: lua
 
-  addAction("bad-domain.example.", dropAction())
+  addAction("bad-domain.example.", DropAction())
 
 Or we configure a server pool dedicated to receiving the nasty stuff:
 
index 6f3767a37e88071ddb7601d312839eca8217d589..9bfc57216b4d71d6f07c8fe8cf3b83339494bb07 100644 (file)
@@ -103,6 +103,7 @@ URL Endpoints
 
   :>json string acl: A string of comma-separated netmasks currently allowed by the :ref:`ACL <ACL>`.
   :>json list cache-hit-response-rules: A list of :json:object:`ResponseRule` objects applied on cache hits
+  :>json list self-answered-response-rules: A list of :json:object:`ResponseRule` objects applied on self-answered queries
   :>json string daemon_type: The type of daemon, always "dnsdist"
   :>json list frontends: A list of :json:object:`Frontend` objects
   :>json list pools: A list of :json:object:`Pool` objects
@@ -187,9 +188,10 @@ JSON Objects
 
   :property string action: The action taken when the rule matches (e.g. "to pool abuse")
   :property dict action-stats: A list of statistics whose content varies depending on the kind of rule
-  :property integer id: The identifier (or order) of this rule
+  :property integer id: The position of this rule
   :property integer matches: How many times this rule was hit
   :property string rule: The matchers for the packet (e.g. "qname==bad-domain1.example., bad-domain2.example.")
+  :property string uuid: The UUID of this rule
 
 .. json:object:: ResponseRule
 
index d80dbced845b956c5084a5fe2cd3d25a9af89cef..e397c15db32da7438c2ea00482111226be0f1cd9 100644 (file)
@@ -83,6 +83,31 @@ Listen Sockets
                                   higher than 0 to enable TCP Fast Open when available.
                                   Default is 0.
 
+.. function:: addTLSLocal(address, certFile, keyFile[, options])
+
+  .. versionadded:: 1.3.0
+
+  Listen on the specified address and TCP port for incoming DNS over TLS connections, presenting the specified X.509 certificate.
+
+  :param str address: The IP Address with an optional port to listen on.
+                      The default port is 853.
+  :param str certFile: The path to a X.509 certificate file in PEM format.
+  :param str keyFile: The path to the private key file corresponding to the certificate.
+  :param table options: A table with key: value pairs with listen options.
+
+  Options:
+
+  * ``doTCP=true``: bool - Also bind on TCP on ``address``.
+  * ``reusePort=false``: bool - Set the ``SO_REUSEPORT`` socket option.
+  * ``tcpFastOpenSize=0``: int - Set the TCP Fast Open queue size, enabling TCP Fast Open when available and the value is larger than 0.
+  * ``interface=""``: str - Set the network interface to use.
+  * ``cpus={}``: table - Set the CPU affinity for this listener thread, asking the scheduler to run it on a single CPU id, or a set of CPU ids. This parameter is only available if the OS provides the pthread_setaffinity_np() function.
+  * ``provider``: str - The TLS library to use between GnuTLS and OpenSSL, if they were available and enabled at compilation time.
+  * ``ciphers``: str - The TLS ciphers to use. The exact format depends on the provider used.
+  * ``numberOfTicketsKeys``: int - The maximum number of tickets keys to keep in memory at the same time, if the provider supports it (GnuTLS doesn't, OpenSSL does). Only one key is marked as active and used to encrypt new tickets while the remaining ones can still be used to decrypt existing tickets after a rotation. Default to 5.
+  * ``ticketKeyFile``: str - The path to a file from where TLS tickets keys should be loaded, to support RFC 5077. These keys should be rotated often and never written to persistent storage to preserve forward secrecy. The default is to generate a random key. The OpenSSL provider supports several tickets keys to be able to decrypt existing sessions after the rotation, while the GnuTLS provider only supports one key.
+  * ``ticketsKeysRotationDelay``: int - Set the delay before the TLS tickets key is rotated, in seconds. Default is 43200 (12h).
+
 .. function:: setLocal(address[, options])
 
   .. versionadded:: 1.2.0
@@ -235,14 +260,16 @@ Servers
       checkName=STRING,      -- Use STRING as QNAME in the health-check query, default: "a.root-servers.net."
       checkType=STRING,      -- Use STRING as QTYPE in the health-check query, default: "A"
       setCD=BOOL,            -- Set the CD (Checking Disabled) flag in the health-check query, default: false
-      maxCheckFailures=NUM,  -- Allow NUM check failures before declaring the backend down, default: false
+      maxCheckFailures=NUM,  -- Allow NUM check failures before declaring the backend down, default: 1
       mustResolve=BOOL,      -- Set to true when the health check MUST return a NOERROR RCODE and an answer
       useClientSubnet=BOOL,  -- Add the client's IP address in the EDNS Client Subnet option when forwarding the query to this backend
-      source=STRING          -- The source address or interface to use for queries to this backend, by default this is left to the kernel's address selection
+      source=STRING,         -- The source address or interface to use for queries to this backend, by default this is left to the kernel's address selection
                              -- The following formats are supported:
                              --   "address", e.g. "192.0.2.2"
                              --   "interface name", e.g. "eth0"
                              --   "address@interface", e.g. "192.0.2.2@eth0"
+      addXPF=NUM             -- Add the client's IP address and port to the query, along with the original destination address and port,
+                             -- using the experimental XPF record from `draft-bellis-dnsop-xpf <https://datatracker.ietf.org/doc/draft-bellis-dnsop-xpf/>`_ and the specified option code. Default is disabled (0)
     })
 
   :param str server_string: A simple IP:PORT string.
@@ -519,6 +546,11 @@ Status, Statistics and More
 
   Print all statistics dnsdist gathers
 
+.. function:: getTLSContext(idx)
+  .. versionadded:: 1.3.0
+
+  Return the TLSContext object for the context of index ``idx``.
+
 .. function:: grepq(selector[, num])
               grepq(selectors[, num])
 
@@ -568,6 +600,11 @@ Status, Statistics and More
 
   Show some statistics regarding TCP
 
+.. function:: showTLSContexts()
+  .. versionadded:: 1.3.0
+
+  Print the list of all availables DNS over TLS contexts.
+
 .. function:: showVersion()
 
   Print the version of dnsdist
@@ -689,3 +726,21 @@ Other functions
 
   If this function exists, it is called every second to so regular tasks.
   This can be used for e.g. :doc:`Dynamic Blocks <../guides/dynblocks>`.
+
+TLSContext
+~~~~~~~~~~
+
+.. class:: TLSContext
+  .. versionadded:: 1.3.0
+
+  This object represents an address and port dnsdist is listening on for DNS over TLS queries.
+
+.. classmethod:: TLSContext:rotateTicketsKey()
+
+   Replace the current TLS tickets key by a new random one.
+
+.. classmethod:: TLSContext:loadTicketsKeys(ticketsKeysFile)
+
+   Load new tickets keys from the selected file, replacing the existing ones. These keys should be rotated often and never written to persistent storage to preserve forward secrecy. The default is to generate a random key. The OpenSSL provider supports several tickets keys to be able to decrypt existing sessions after the rotation, while the GnuTLS provider only supports one key.
+
+  :param str ticketsKeysFile: The path to a file from where TLS tickets keys should be loaded.
index b7bdcb187c6a7861cd3b271b6c2005c4dc743a3c..38b994d8cc99a684e08d679b29d0a8755e83c55d 100644 (file)
@@ -14,6 +14,8 @@ OPCode
 - ``DNSOpcode.Notify``
 - ``DNSOpcode.Update``
 
+Reference: https://www.iana.org/assignments/dns-parameters/dns-parameters.xhtml#dns-parameters-5
+
 .. _DNSQClass:
 
 QClass
@@ -24,6 +26,8 @@ QClass
 - ``QClass.NONE``
 - ``QClass.ANY``
 
+Reference: https://www.iana.org/assignments/dns-parameters/dns-parameters.xhtml#dns-parameters-2
+
 .. _DNSRCode:
 
 RCode
@@ -40,6 +44,19 @@ RCode
 - ``dnsdist.NXRRSET``
 - ``dnsdist.NOTAUTH``
 - ``dnsdist.NOTZONE``
+- ``dnsdist.BADVERS``
+- ``dnsdist.BADSIG``
+- ``dnsdist.BADKEY``
+- ``dnsdist.BADTIME``
+- ``dnsdist.BADMODE``
+- ``dnsdist.BADNAME``
+- ``dnsdist.BADALG``
+- ``dnsdist.BADTRUNC``
+- ``dnsdist.BADCOOKIE``
+
+RCodes below and including ``BADVERS`` are extended RCodes that can only be matched using :func:`ERCodeRule`.
+
+Reference: https://www.iana.org/assignments/dns-parameters/dns-parameters.xhtml#dns-parameters-6
 
 .. _DNSSection:
 
index 726ef951e81e64ae6ddaafc272ffd25702a85e49..e704162a5a970378610a54ea8e7dbb970979313d 100644 (file)
@@ -143,17 +143,27 @@ Rule Generators
   :param string domain: Domain name to spoof for
   :param string cname: Domain name to add CNAME to
 
-.. function:: addLuaAction(DNSrule, function)
+.. function:: addLuaAction(DNSrule, function [, options])
+
+  .. versionchanged:: 1.3.0
+    Added the optional parameter ``options``.
 
   Invoke a Lua function that accepts a :class:`DNSQuestion`.
   This function works similar to using :func:`LuaAction`.
-
   The ``function`` should return a :ref:`DNSAction`.
 
   :param DNSRule: match queries based on this rule
   :param string function: the name of a Lua function
+  :param table options: A table with key: value pairs with options.
+
+  Options:
+
+  * ``uuid``: string - UUID to assign to the new rule. By default a random UUID is generated for each rule.
 
-.. function:: addLuaResponseAction(DNSrule, function)
+.. function:: addLuaResponseAction(DNSrule, function [, options])
+
+  .. versionchanged:: 1.3.0
+    Added the optional parameter ``options``.
 
   Invoke a Lua function that accepts a :class:`DNSQuestion` on the response.
   This function works similar to using :func:`LuaAction`.
@@ -162,6 +172,11 @@ Rule Generators
 
   :param DNSRule: match queries based on this rule
   :param string function: the name of a Lua function
+  :param table options: A table with key: value pairs with options.
+
+  Options:
+
+  * ``uuid``: string - UUID to assign to the new rule. By default a random UUID is generated for each rule.
 
 .. function:: addNoRecurseRule(DNSrule)
 
@@ -233,12 +248,20 @@ Active Rules can be shown with :func:`showRules` and removed with :func:`rmRule`
 
 For Rules related to the incoming query:
 
-.. function:: addAction(DNSrule, action)
+.. function:: addAction(DNSrule, action [, options])
+
+  .. versionchanged:: 1.3.0
+    Added the optional parameter ``options``.
 
   Add a Rule and Action to the existing rules.
 
   :param DNSrule rule: A DNSRule, e.g. an :func:`allRule` or a compounded bunch of rules using e.g. :func:`AndRule`
   :param action: The action to take
+  :param table options: A table with key: value pairs with options.
+
+  Options:
+
+  * ``uuid``: string - UUID to assign to the new rule. By default a random UUID is generated for each rule.
 
 .. function:: clearRules()
 
@@ -258,12 +281,20 @@ For Rules related to the incoming query:
   :param int from: Rule number to move
   :param int to: Location to more the Rule to
 
-.. function:: newRuleAction(rule, action)
+.. function:: newRuleAction(rule, action[, options])
+
+  .. versionchanged:: 1.3.0
+    Added the optional parameter ``options``.
 
   Return a pair of DNS Rule and DNS Action, to be used with :func:`setRules`.
 
   :param Rule rule: A `Rule <#traffic-matching>`_
   :param Action action: The `Action <#actions>`_ to apply to the matched traffic
+  :param table options: A table with key: value pairs with options.
+
+  Options:
+
+  * ``uuid``: string - UUID to assign to the new rule. By default a random UUID is generated for each rule.
 
 .. function:: setRules(rules)
 
@@ -271,28 +302,41 @@ For Rules related to the incoming query:
 
   :param [RuleAction] rules: A list of RuleActions
 
-.. function:: showRules()
+.. function:: showRules([showUUIDs])
+
+  Show all defined rules for queries, optionally displaying their UUIDs.
 
-  Show all defined rules for queries.
+  :param bool showUUIDs: Whether to display the UUIDs, defaults to false
 
 .. function:: topRule()
 
   Move the last rule to the first position.
 
-.. function:: rmRule(n)
+.. function:: rmRule(id)
+
+  .. versionchanged:: 1.3.0
+    ``id`` can now be an UUID.
 
-  Remove rule ``n``.
+  Remove rule ``id``.
 
-  :param int n: Rule number to remove
+  :param int id: The UUID of the rule to remove if ``id`` is an UUID, its position otherwise
 
 For Rules related to responses:
 
-.. function:: addResponseAction(DNSRule, action)
+.. function:: addResponseAction(DNSRule, action [, options])
+
+  .. versionchanged:: 1.3.0
+    Added the optional parameter ``options``.
 
   Add a Rule and Action for responses to the existing rules.
 
   :param DNSRule: A DNSRule, e.g. an :func:`allRule` or a compounded bunch of rules using e.g. :func:`AndRule`
   :param action: The action to take
+  :param table options: A table with key: value pairs with options.
+
+  Options:
+
+  * ``uuid``: string - UUID to assign to the new rule. By default a random UUID is generated for each rule.
 
 .. function:: mvResponseRule(from, to)
 
@@ -302,30 +346,43 @@ For Rules related to responses:
   :param int from: Rule number to move
   :param int to: Location to more the Rule to
 
-.. function:: rmResponseRule(n)
+.. function:: rmResponseRule(id)
+
+  .. versionchanged:: 1.3.0
+    ``id`` can now be an UUID.
 
-  Remove response rule ``n``.
+  Remove response rule ``id``.
 
-  :param int n: Rule number to remove
+  :param int id: The UUID of the rule to remove if ``id`` is an UUID, its position otherwise
 
-.. function:: showResponseRules()
+.. function:: showResponseRules([showUUIDs])
 
-  Show all defined response rules.
+  Show all defined response rules, optionally displaying their UUIDs.
+
+  :param bool showUUIDs: Whether to display the UUIDs, defaults to false
 
 .. function:: topResponseRule()
 
   Move the last response rule to the first position.
 
-Functions for manipulation Cache Hit Rules:
+Functions for manipulating Cache Hit Respone Rules:
 
-.. function:: addCacheHitAction(DNSRule, action)
+.. function:: addCacheHitResponseAction(DNSRule, action [, options])
 
   .. versionadded:: 1.2.0
 
-  Add a Rule and Action for Cache Hits to the existing rules.
+  .. versionchanged:: 1.3.0
+    Added the optional parameter ``options``.
+
+  Add a Rule and ResponseAction for Cache Hits to the existing rules.
 
   :param DNSRule: A DNSRule, e.g. an :func:`allRule` or a compounded bunch of rules using e.g. :func:`AndRule`
   :param action: The action to take
+  :param table options: A table with key: value pairs with options.
+
+  Options:
+
+  * ``uuid``: string - UUID to assign to the new rule. By default a random UUID is generated for each rule.
 
 .. function:: mvCacheHitResponseRule(from, to)
 
@@ -337,19 +394,22 @@ Functions for manipulation Cache Hit Rules:
   :param int from: Rule number to move
   :param int to: Location to more the Rule to
 
-.. function:: rmCacheHitResponseRule(n)
+.. function:: rmCacheHitResponseRule(id)
 
   .. versionadded:: 1.2.0
 
-  Remove cache hit response rule ``n``.
+  .. versionchanged:: 1.3.0
+    ``id`` can now be an UUID.
 
-  :param int n: Rule number to remove
+  :param int id: The UUID of the rule to remove if ``id`` is an UUID, its position otherwise
 
-.. function:: showCacheHitResponseRules()
+.. function:: showCacheHitResponseRules([showUUIDs])
 
   .. versionadded:: 1.2.0
 
-  Show all defined cache hit response rules.
+  Show all defined cache hit response rules, optionally displaying their UUIDs.
+
+  :param bool showUUIDs: Whether to display the UUIDs, defaults to false
 
 .. function:: topCacheHitResponseRule()
 
@@ -357,6 +417,49 @@ Functions for manipulation Cache Hit Rules:
 
   Move the last cache hit response rule to the first position.
 
+Functions for manipulating Self-Answered Response Rules:
+
+.. function:: addSelfAnsweredResponseAction(DNSRule, action [, options])
+
+  .. versionadded:: 1.3.0
+
+  Add a Rule and Action for Self-Answered queries to the existing rules.
+
+  :param DNSRule: A DNSRule, e.g. an :func:`allRule` or a compounded bunch of rules using e.g. :func:`AndRule`
+  :param action: The action to take
+
+.. function:: mvSelfAnsweredResponseRule(from, to)
+
+  .. versionadded:: 1.3.0
+
+  Move self answered response rule ``from`` to a position where it is in front of ``to``.
+  ``to`` can be one larger than the largest rule, in which case the rule will be moved to the last position.
+
+  :param int from: Rule number to move
+  :param int to: Location to more the Rule to
+
+.. function:: rmSelfAnsweredResponseRule(id)
+
+  .. versionadded:: 1.3.0
+
+  Remove self answered response rule ``id``.
+
+  :param int id: The UUID of the rule to remove if ``id`` is an UUID, its position otherwise
+
+.. function:: showSelfAnsweredResponseRules([showUUIDs])
+
+  .. versionadded:: 1.3.0
+
+  Show all defined self answered response rules, optionally displaying their UUIDs.
+
+  :param bool showUUIDs: Whether to display the UUIDs, defaults to false
+
+.. function:: topSelfAnsweredResponseRule()
+
+  .. versionadded:: 1.3.0
+
+  Move the last self answered response rule to the first position.
+
 .. _RulesIntro:
 
 Matching Packets (Selectors)
@@ -462,8 +565,17 @@ These ``DNSRule``\ s be one of the following items:
 
 .. function:: RCodeRule(rcode)
 
-  Matches queries or responses the specified ``rcode``.
-  ``rcode`` can be specified as an integer or as one of the built-in `RCode <DNSRcode>`.
+  Matches queries or responses with the specified ``rcode``.
+  ``rcode`` can be specified as an integer or as one of the built-in :ref:`DNSRCode`.
+  Only the non-extended RCode is matched (lower 4bits).
+
+  :param int rcode: The RCODE to match on
+
+.. function:: ERCodeRule(rcode)
+
+  Matches queries or responses with the specified ``rcode``.
+  ``rcode`` can be specified as an integer or as one of the built-in :ref:`DNSRCode`.
+  The full 16bit RCode will be matched. If no EDNS OPT RR is present, the upper 12 bits are treated as 0.
 
   :param int rcode: The RCODE to match on
 
@@ -548,7 +660,7 @@ These ``DNSRule``\ s be one of the following items:
 Combining Rules
 ~~~~~~~~~~~~~~~
 
-.. function:: andRule(selectors)
+.. function:: AndRule(selectors)
 
   Matches traffic if all ``selectors`` match.
 
@@ -566,8 +678,8 @@ Combining Rules
 
   :param {Rule} selector: A table of Rules
 
-Convience Functions
-~~~~~~~~~~~~~~~~~~~
+Convenience Functions
+~~~~~~~~~~~~~~~~~~~~~
 
 .. function:: makeRule(rule)
 
@@ -709,7 +821,7 @@ The following actions exist.
 .. function:: RCodeAction(rcode)
 
   Reply immediatly by turning the query into a response with the specified ``rcode``.
-  ``rcode`` can be specified as an integer or as one of the built-in `RCode <#rcode>`_.
+  ``rcode`` can be specified as an integer or as one of the built-in :ref:`DNSRCode`.
 
   :param int rcode: The RCODE to respond with.
 
@@ -791,4 +903,8 @@ The following actions exist.
   :param string remote: An IP:PORT conbination to send the copied queries to
   :param bool addECS: Whether or not to add ECS information. Default false
 
+.. function:: TempFailureCacheTTLAction(ttl)
+
+  Set the cache TTL to use for ServFail and Refused replies. TTL is not applied for successful replies.
 
+  :param int ttl: Cache TTL for temporary failure replies
diff --git a/pdns/dnsdistdist/m4/dnsdist_check_gnutls.m4 b/pdns/dnsdistdist/m4/dnsdist_check_gnutls.m4
new file mode 100644 (file)
index 0000000..4d8eaf7
--- /dev/null
@@ -0,0 +1,26 @@
+AC_DEFUN([DNSDIST_CHECK_GNUTLS], [
+  AC_MSG_CHECKING([whether we will be linking in GnuTLS])
+  HAVE_GNUTLS=0
+  AC_ARG_ENABLE([gnutls],
+    AS_HELP_STRING([--enable-gnutls],[use GnuTLS @<:@default=auto@:>@]),
+    [enable_gnutls=$enableval],
+    [enable_gnutls=auto],
+  )
+  AC_MSG_RESULT([$enable_gnutls])
+
+  AS_IF([test "x$enable_gnutls" != "xno"], [
+    AS_IF([test "x$enable_gnutls" = "xyes" -o "x$enable_gnutls" = "xauto"], [
+      # we require gnutls_certificate_set_x509_key_file, added in 3.1.11
+      PKG_CHECK_MODULES([GNUTLS], [gnutls >= 3.1.11], [
+        [HAVE_GNUTLS=1]
+        AC_DEFINE([HAVE_GNUTLS], [1], [Define to 1 if you have GnuTLS])
+      ], [ : ])
+    ])
+  ])
+  AM_CONDITIONAL([HAVE_GNUTLS], [test "x$GNUTLS_LIBS" != "x"])
+  AS_IF([test "x$enable_gnutls" = "xyes"], [
+    AS_IF([test x"$GNUTLS_LIBS" = "x"], [
+      AC_MSG_ERROR([GnuTLS requested but libraries were not found])
+    ])
+  ])
+])
diff --git a/pdns/dnsdistdist/m4/dnsdist_check_libssl.m4 b/pdns/dnsdistdist/m4/dnsdist_check_libssl.m4
new file mode 100644 (file)
index 0000000..dbf1961
--- /dev/null
@@ -0,0 +1,25 @@
+AC_DEFUN([DNSDIST_CHECK_LIBSSL], [
+  AC_MSG_CHECKING([whether we will be linking in OpenSSL libssl])
+  HAVE_LIBSSL=0
+  AC_ARG_ENABLE([libssl],
+    AS_HELP_STRING([--enable-libssl],[use OpenSSL libssl @<:@default=auto@:>@]),
+    [enable_libssl=$enableval],
+    [enable_libssl=auto],
+  )
+  AC_MSG_RESULT([$enable_libssl])
+
+  AS_IF([test "x$enable_libssl" != "xno"], [
+    AS_IF([test "x$enable_libssl" = "xyes" -o "x$enable_libssl" = "xauto"], [
+      PKG_CHECK_MODULES([LIBSSL], [libssl], [
+        [HAVE_LIBSSL=1]
+        AC_DEFINE([HAVE_LIBSSL], [1], [Define to 1 if you have OpenSSL libssl])
+      ], [ : ])
+    ])
+  ])
+  AM_CONDITIONAL([HAVE_LIBSSL], [test "x$LIBSSL_LIBS" != "x"])
+  AS_IF([test "x$enable_libssl" = "xyes"], [
+    AS_IF([test x"$LIBSSL_LIBS" = "x"], [
+      AC_MSG_ERROR([OpenSSL libssl requested but libraries were not found])
+    ])
+  ])
+])
diff --git a/pdns/dnsdistdist/m4/dnsdist_enable_tls.m4 b/pdns/dnsdistdist/m4/dnsdist_enable_tls.m4
new file mode 100644 (file)
index 0000000..0a9539f
--- /dev/null
@@ -0,0 +1,14 @@
+AC_DEFUN([DNSDIST_ENABLE_DNS_OVER_TLS], [
+  AC_MSG_CHECKING([whether to enable DNS over TLS support])
+  AC_ARG_ENABLE([dns-over-tls],
+    AS_HELP_STRING([--enable-dns-over-tls], [enable DNS over TLS support (requires GnuTLS or OpenSSL) @<:@default=no@:>@]),
+    [enable_dns_over_tls=$enableval],
+    [enable_dns_over_tls=no]
+  )
+  AC_MSG_RESULT([$enable_dns_over_tls])
+  AM_CONDITIONAL([HAVE_DNS_OVER_TLS], [test "x$enable_dns_over_tls" != "xno"])
+
+  AM_COND_IF([HAVE_DNS_OVER_TLS], [
+    AC_DEFINE([HAVE_DNS_OVER_TLS], [1], [Define to 1 if you enable DNS over TLS support])
+  ])
+])
diff --git a/pdns/dnsdistdist/m4/pdns_check_libcrypto.m4 b/pdns/dnsdistdist/m4/pdns_check_libcrypto.m4
new file mode 120000 (symlink)
index 0000000..b56d43a
--- /dev/null
@@ -0,0 +1 @@
+../../../m4/pdns_check_libcrypto.m4
\ No newline at end of file
diff --git a/pdns/dnsdistdist/tcpiohandler.cc b/pdns/dnsdistdist/tcpiohandler.cc
new file mode 100644 (file)
index 0000000..f2dddc9
--- /dev/null
@@ -0,0 +1,865 @@
+#include <fstream>
+
+#include "config.h"
+#include "dolog.hh"
+#include "iputils.hh"
+#include "lock.hh"
+#include "tcpiohandler.hh"
+
+#ifdef HAVE_LIBSODIUM
+#include <sodium.h>
+#endif /* HAVE_LIBSODIUM */
+
+#ifdef HAVE_DNS_OVER_TLS
+#ifdef HAVE_LIBSSL
+#include <openssl/conf.h>
+#include <openssl/err.h>
+#include <openssl/rand.h>
+#include <openssl/ssl.h>
+
+#include <boost/circular_buffer.hpp>
+
+#if (OPENSSL_VERSION_NUMBER < 0x1010000fL || defined LIBRESSL_VERSION_NUMBER)
+/* OpenSSL < 1.1.0 needs support for threading/locking in the calling application. */
+static pthread_mutex_t *openssllocks{nullptr};
+
+extern "C" {
+static void openssl_pthreads_locking_callback(int mode, int type, const char *file, int line)
+{
+  if (mode & CRYPTO_LOCK) {
+    pthread_mutex_lock(&(openssllocks[type]));
+
+  } else {
+    pthread_mutex_unlock(&(openssllocks[type]));
+  }
+}
+
+static unsigned long openssl_pthreads_id_callback()
+{
+  return (unsigned long)pthread_self();
+}
+}
+
+static void openssl_thread_setup()
+{
+  openssllocks = (pthread_mutex_t*)OPENSSL_malloc(CRYPTO_num_locks() * sizeof(pthread_mutex_t));
+
+  for (int i = 0; i < CRYPTO_num_locks(); i++)
+    pthread_mutex_init(&(openssllocks[i]), NULL);
+
+  CRYPTO_set_id_callback(openssl_pthreads_id_callback);
+  CRYPTO_set_locking_callback(openssl_pthreads_locking_callback);
+}
+
+static void openssl_thread_cleanup()
+{
+  CRYPTO_set_locking_callback(NULL);
+
+  for (int i=0; i<CRYPTO_num_locks(); i++) {
+    pthread_mutex_destroy(&(openssllocks[i]));
+  }
+
+  OPENSSL_free(openssllocks);
+}
+
+#else
+static void openssl_thread_setup()
+{
+}
+
+static void openssl_thread_cleanup()
+{
+}
+#endif /* (OPENSSL_VERSION_NUMBER < 0x1010000fL || defined LIBRESSL_VERSION_NUMBER) */
+
+/* From rfc5077 Section 4. Recommended Ticket Construction */
+#define TLS_TICKETS_KEY_NAME_SIZE (16)
+
+/* AES-256 */
+#define TLS_TICKETS_CIPHER_KEY_SIZE (32)
+#define TLS_TICKETS_CIPHER_ALGO (EVP_aes_256_cbc)
+
+/* HMAC SHA-256 */
+#define TLS_TICKETS_MAC_KEY_SIZE (32)
+#define TLS_TICKETS_MAC_ALGO (EVP_sha256)
+
+static int s_ticketsKeyIndex{-1};
+
+class OpenSSLTLSTicketKey
+{
+public:
+  OpenSSLTLSTicketKey()
+  {
+    if (RAND_bytes(d_name, sizeof(d_name)) != 1) {
+      throw std::runtime_error("Error while generating the name of the OpenSSL TLS ticket key");
+    }
+
+    if (RAND_bytes(d_cipherKey, sizeof(d_cipherKey)) != 1) {
+      throw std::runtime_error("Error while generating the cipher key of the OpenSSL TLS ticket key");
+    }
+
+    if (RAND_bytes(d_hmacKey, sizeof(d_hmacKey)) != 1) {
+      throw std::runtime_error("Error while generating the HMAC key of the OpenSSL TLS ticket key");
+    }
+#ifdef HAVE_LIBSODIUM
+    sodium_mlock(d_name, sizeof(d_name));
+    sodium_mlock(d_cipherKey, sizeof(d_cipherKey));
+    sodium_mlock(d_hmacKey, sizeof(d_hmacKey));
+#endif /* HAVE_LIBSODIUM */
+  }
+
+  OpenSSLTLSTicketKey(ifstream& file)
+  {
+    file.read(reinterpret_cast<char*>(d_name), sizeof(d_name));
+    file.read(reinterpret_cast<char*>(d_cipherKey), sizeof(d_cipherKey));
+    file.read(reinterpret_cast<char*>(d_hmacKey), sizeof(d_hmacKey));
+
+    if (file.fail()) {
+      throw std::runtime_error("Unable to load a ticket key from the OpenSSL tickets key file");
+    }
+#ifdef HAVE_LIBSODIUM
+    sodium_mlock(d_name, sizeof(d_name));
+    sodium_mlock(d_cipherKey, sizeof(d_cipherKey));
+    sodium_mlock(d_hmacKey, sizeof(d_hmacKey));
+#endif /* HAVE_LIBSODIUM */
+  }
+
+  ~OpenSSLTLSTicketKey()
+  {
+#ifdef HAVE_LIBSODIUM
+    sodium_munlock(d_name, sizeof(d_name));
+    sodium_munlock(d_cipherKey, sizeof(d_cipherKey));
+    sodium_munlock(d_hmacKey, sizeof(d_hmacKey));
+#else
+    OPENSSL_cleanse(d_name, sizeof(d_name));
+    OPENSSL_cleanse(d_cipherKey, sizeof(d_cipherKey));
+    OPENSSL_cleanse(d_hmacKey, sizeof(d_hmacKey));
+#endif /* HAVE_LIBSODIUM */
+  }
+
+  bool nameMatches(const unsigned char name[TLS_TICKETS_KEY_NAME_SIZE]) const
+  {
+    return (memcmp(d_name, name, sizeof(d_name)) == 0);
+  }
+
+  int encrypt(unsigned char keyName[TLS_TICKETS_KEY_NAME_SIZE], unsigned char *iv, EVP_CIPHER_CTX *ectx, HMAC_CTX *hctx) const
+  {
+    memcpy(keyName, d_name, sizeof(d_name));
+
+    if (RAND_bytes(iv, EVP_MAX_IV_LENGTH) != 1) {
+      return -1;
+    }
+
+    if (EVP_EncryptInit_ex(ectx, TLS_TICKETS_CIPHER_ALGO(), nullptr, d_cipherKey, iv) != 1) {
+      return -1;
+    }
+
+    if (HMAC_Init_ex(hctx, d_hmacKey, sizeof(d_hmacKey), TLS_TICKETS_MAC_ALGO(), nullptr) != 1) {
+      return -1;
+    }
+
+    return 1;
+  }
+
+  bool decrypt(const unsigned char* iv, EVP_CIPHER_CTX *ectx, HMAC_CTX *hctx) const
+  {
+    if (HMAC_Init_ex(hctx, d_hmacKey, sizeof(d_hmacKey), TLS_TICKETS_MAC_ALGO(), nullptr) != 1) {
+      return false;
+    }
+
+    if (EVP_DecryptInit_ex(ectx, TLS_TICKETS_CIPHER_ALGO(), nullptr, d_cipherKey, iv) != 1) {
+      return false;
+    }
+
+    return true;
+  }
+
+private:
+  unsigned char d_name[TLS_TICKETS_KEY_NAME_SIZE];
+  unsigned char d_cipherKey[TLS_TICKETS_CIPHER_KEY_SIZE];
+  unsigned char d_hmacKey[TLS_TICKETS_MAC_KEY_SIZE];
+};
+
+class OpenSSLTLSTicketKeysRing
+{
+public:
+  OpenSSLTLSTicketKeysRing(size_t capacity)
+  {
+    pthread_rwlock_init(&d_lock, nullptr);
+    d_ticketKeys.set_capacity(capacity);
+  }
+
+  ~OpenSSLTLSTicketKeysRing()
+  {
+    pthread_rwlock_destroy(&d_lock);
+  }
+
+  void addKey(std::shared_ptr<OpenSSLTLSTicketKey> newKey)
+  {
+    WriteLock wl(&d_lock);
+    d_ticketKeys.push_back(newKey);
+  }
+
+  std::shared_ptr<OpenSSLTLSTicketKey> getEncryptionKey()
+  {
+    ReadLock rl(&d_lock);
+    return d_ticketKeys.front();
+  }
+
+  std::shared_ptr<OpenSSLTLSTicketKey> getDecryptionKey(unsigned char name[TLS_TICKETS_KEY_NAME_SIZE], bool& activeKey)
+  {
+    ReadLock rl(&d_lock);
+    for (auto& key : d_ticketKeys) {
+      if (key->nameMatches(name)) {
+        activeKey = (key == d_ticketKeys.front());
+        return key;
+      }
+    }
+    return nullptr;
+  }
+
+  size_t getKeysCount()
+  {
+    ReadLock rl(&d_lock);
+    return d_ticketKeys.size();
+  }
+
+private:
+  boost::circular_buffer<std::shared_ptr<OpenSSLTLSTicketKey> > d_ticketKeys;
+  pthread_rwlock_t d_lock;
+};
+
+class OpenSSLTLSConnection: public TLSConnection
+{
+public:
+  OpenSSLTLSConnection(int socket, unsigned int timeout, SSL_CTX* tlsCtx)
+  {
+    d_socket = socket;
+    d_conn = SSL_new(tlsCtx);
+
+    if (!d_conn) {
+      vinfolog("Error creating TLS object");
+      if (g_verbose) {
+        ERR_print_errors_fp(stderr);
+      }
+      throw std::runtime_error("Error creating TLS object");
+    }
+
+    if (!SSL_set_fd(d_conn, d_socket)) {
+      throw std::runtime_error("Error assigning socket");
+    }
+
+    int res = 0;
+    do {
+      res = SSL_accept(d_conn);
+      if (res < 0) {
+        handleIORequest(res, timeout);
+      }
+    }
+    while (res < 0);
+
+    if (res != 1) {
+      throw std::runtime_error("Error accepting TLS connection");
+    }
+  }
+
+  virtual ~OpenSSLTLSConnection() override
+  {
+    if (d_conn) {
+      SSL_free(d_conn);
+    }
+  }
+
+  void handleIORequest(int res, unsigned int timeout)
+  {
+    int error = SSL_get_error(d_conn, res);
+    if (error == SSL_ERROR_WANT_READ) {
+      res = waitForData(d_socket, timeout);
+      if (res <= 0) {
+        throw std::runtime_error("Error reading from TLS connection");
+      }
+    }
+    else if (error == SSL_ERROR_WANT_WRITE) {
+      res = waitForRWData(d_socket, false, timeout, 0);
+      if (res <= 0) {
+        throw std::runtime_error("Error waiting to write to TLS connection");
+      }
+    }
+    else {
+      throw std::runtime_error("Error writing to TLS connection");
+    }
+  }
+
+  size_t read(void* buffer, size_t bufferSize, unsigned int readTimeout, unsigned int totalTimeout) override
+  {
+    size_t got = 0;
+    time_t start = 0;
+    unsigned int remainingTime = totalTimeout;
+    if (totalTimeout) {
+      start = time(nullptr);
+    }
+
+    do {
+      int res = SSL_read(d_conn, (reinterpret_cast<char *>(buffer) + got), static_cast<int>(bufferSize - got));
+      if (res == 0) {
+        throw std::runtime_error("Error reading from TLS connection");
+      }
+      else if (res < 0) {
+        handleIORequest(res, readTimeout);
+      }
+      else {
+        got += (size_t) res;
+      }
+
+      if (totalTimeout) {
+        time_t now = time(nullptr);
+        unsigned int elapsed = now - start;
+        if (now < start || elapsed >= remainingTime) {
+          throw runtime_error("Timeout while reading data");
+        }
+        start = now;
+        remainingTime -= elapsed;
+      }
+    }
+    while (got < bufferSize);
+
+    return got;
+  }
+
+  size_t write(const void* buffer, size_t bufferSize, unsigned int writeTimeout) override
+  {
+    size_t got = 0;
+    do {
+      int res = SSL_write(d_conn, (reinterpret_cast<const char *>(buffer) + got), static_cast<int>(bufferSize - got));
+      if (res == 0) {
+        throw std::runtime_error("Error writing to TLS connection");
+      }
+      else if (res < 0) {
+        handleIORequest(res, writeTimeout);
+      }
+      else {
+        got += (size_t) res;
+      }
+    }
+    while (got < bufferSize);
+
+    return got;
+  }
+  void close() override
+  {
+    if (d_conn) {
+      SSL_shutdown(d_conn);
+    }
+  }
+
+private:
+  SSL* d_conn{nullptr};
+};
+
+class OpenSSLTLSIOCtx: public TLSCtx
+{
+public:
+  OpenSSLTLSIOCtx(const TLSFrontend& fe): d_ticketKeys(fe.d_numberOfTicketsKeys)
+  {
+    d_ticketsKeyRotationDelay = fe.d_ticketsKeyRotationDelay;
+
+    static const int sslOptions =
+      SSL_OP_NO_SSLv2 |
+      SSL_OP_NO_SSLv3 |
+      SSL_OP_NO_COMPRESSION |
+      SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION |
+      SSL_OP_SINGLE_DH_USE |
+      SSL_OP_SINGLE_ECDH_USE |
+      SSL_OP_CIPHER_SERVER_PREFERENCE;
+
+    if (s_users.fetch_add(1) == 0) {
+      ERR_load_crypto_strings();
+      OpenSSL_add_ssl_algorithms();
+      openssl_thread_setup();
+
+      s_ticketsKeyIndex = SSL_CTX_get_ex_new_index(0, nullptr, nullptr, nullptr, nullptr);
+
+      if (s_ticketsKeyIndex == -1) {
+        throw std::runtime_error("Error getting an index for tickets key");
+      }
+    }
+
+    d_tlsCtx = SSL_CTX_new(SSLv23_server_method());
+    if (!d_tlsCtx) {
+      ERR_print_errors_fp(stderr);
+      throw std::runtime_error("Error creating TLS context on " + fe.d_addr.toStringWithPort());
+    }
+
+    /* use the internal built-in cache to store sessions */
+    SSL_CTX_set_session_cache_mode(d_tlsCtx, SSL_SESS_CACHE_SERVER);
+    /* use our own ticket keys handler so we can rotate them */
+    SSL_CTX_set_tlsext_ticket_key_cb(d_tlsCtx, &OpenSSLTLSIOCtx::ticketKeyCb);
+    SSL_CTX_set_ex_data(d_tlsCtx, s_ticketsKeyIndex, this);
+    SSL_CTX_set_options(d_tlsCtx, sslOptions);
+#if defined(SSL_CTX_set_ecdh_auto)
+    SSL_CTX_set_ecdh_auto(d_tlsCtx, 1);
+#endif
+    SSL_CTX_use_certificate_chain_file(d_tlsCtx, fe.d_certFile.c_str());
+    SSL_CTX_use_PrivateKey_file(d_tlsCtx, fe.d_keyFile.c_str(), SSL_FILETYPE_PEM);
+
+    if (!fe.d_ciphers.empty()) {
+      SSL_CTX_set_cipher_list(d_tlsCtx, fe.d_ciphers.c_str());
+    }
+
+    try {
+      if (fe.d_ticketKeyFile.empty()) {
+        handleTicketsKeyRotation(time(nullptr));
+      }
+      else {
+        loadTicketsKeys(fe.d_ticketKeyFile);
+      }
+    }
+    catch (const std::exception& e) {
+      SSL_CTX_free(d_tlsCtx);
+      throw;
+    }
+  }
+
+  virtual ~OpenSSLTLSIOCtx() override
+  {
+    if (d_tlsCtx) {
+      SSL_CTX_free(d_tlsCtx);
+    }
+
+    if (s_users.fetch_sub(1) == 1) {
+      ERR_free_strings();
+
+      EVP_cleanup();
+
+      CONF_modules_finish();
+      CONF_modules_free();
+      CONF_modules_unload(1);
+
+      CRYPTO_cleanup_all_ex_data();
+      openssl_thread_cleanup();
+    }
+  }
+
+  static int ticketKeyCb(SSL *s, unsigned char keyName[TLS_TICKETS_KEY_NAME_SIZE], unsigned char *iv, EVP_CIPHER_CTX *ectx, HMAC_CTX *hctx, int enc)
+  {
+    SSL_CTX* sslCtx = SSL_get_SSL_CTX(s);
+    if (sslCtx == nullptr) {
+      return -1;
+    }
+
+    OpenSSLTLSIOCtx* ctx = reinterpret_cast<OpenSSLTLSIOCtx*>(SSL_CTX_get_ex_data(sslCtx, s_ticketsKeyIndex));
+    if (ctx == nullptr) {
+      return -1;
+    }
+
+    if (enc) {
+      const auto key = ctx->d_ticketKeys.getEncryptionKey();
+      if (key == nullptr) {
+        return -1;
+      }
+
+      return key->encrypt(keyName, iv, ectx, hctx);
+    }
+
+    bool activeEncryptionKey = false;
+
+    const auto key = ctx->d_ticketKeys.getDecryptionKey(keyName, activeEncryptionKey);
+    if (key == nullptr) {
+      /* we don't know this key, just create a new ticket */
+      return 0;
+    }
+
+    if (key->decrypt(iv, ectx, hctx) == false) {
+      return -1;
+    }
+
+    if (!activeEncryptionKey) {
+      /* this key is not active, please encrypt the ticket content with the currently active one */
+      return 2;
+    }
+
+    return 1;
+  }
+
+  std::unique_ptr<TLSConnection> getConnection(int socket, unsigned int timeout, time_t now) override
+  {
+    handleTicketsKeyRotation(now);
+
+    return std::unique_ptr<OpenSSLTLSConnection>(new OpenSSLTLSConnection(socket, timeout, d_tlsCtx));
+  }
+
+  void rotateTicketsKey(time_t now) override
+  {
+    auto newKey = std::make_shared<OpenSSLTLSTicketKey>();
+    d_ticketKeys.addKey(newKey);
+
+    if (d_ticketsKeyRotationDelay > 0) {
+      d_ticketsKeyNextRotation = time(nullptr) + d_ticketsKeyRotationDelay;
+    }
+  }
+
+  void loadTicketsKeys(const std::string& keyFile) override
+  {
+    bool keyLoaded = false;
+    ifstream file(keyFile);
+    try {
+      do {
+        auto newKey = std::make_shared<OpenSSLTLSTicketKey>(file);
+        d_ticketKeys.addKey(newKey);
+        keyLoaded = true;
+      }
+      while (!file.fail());
+    }
+    catch (const std::exception& e) {
+      /* if we haven't been able to load at least one key, fail */
+      if (!keyLoaded) {
+        throw;
+      }
+    }
+
+    if (d_ticketsKeyRotationDelay > 0) {
+      d_ticketsKeyNextRotation = time(nullptr) + d_ticketsKeyRotationDelay;
+    }
+
+    file.close();
+  }
+
+  size_t getTicketsKeysCount() override
+  {
+    return d_ticketKeys.getKeysCount();
+  }
+
+private:
+  OpenSSLTLSTicketKeysRing d_ticketKeys;
+  SSL_CTX* d_tlsCtx{nullptr};
+  static std::atomic<uint64_t> s_users;
+};
+
+std::atomic<uint64_t> OpenSSLTLSIOCtx::s_users(0);
+
+#endif /* HAVE_LIBSSL */
+
+#ifdef HAVE_GNUTLS
+#include <gnutls/gnutls.h>
+#include <gnutls/x509.h>
+
+class GnuTLSTicketsKey
+{
+public:
+  GnuTLSTicketsKey()
+  {
+    if (gnutls_session_ticket_key_generate(&d_key) != GNUTLS_E_SUCCESS) {
+      throw std::runtime_error("Error generating tickets key for TLS context");
+    }
+
+#ifdef HAVE_LIBSODIUM
+    sodium_mlock(d_key.data, d_key.size);
+#endif /* HAVE_LIBSODIUM */
+  }
+
+  GnuTLSTicketsKey(const std::string& keyFile)
+  {
+    /* to be sure we are loading the correct amount of data, which
+       may change between versions, let's generate a correct key first */
+    if (gnutls_session_ticket_key_generate(&d_key) != GNUTLS_E_SUCCESS) {
+      throw std::runtime_error("Error generating tickets key (before parsing key file) for TLS context");
+    }
+
+#ifdef HAVE_LIBSODIUM
+    sodium_mlock(d_key.data, d_key.size);
+#endif /* HAVE_LIBSODIUM */
+
+    try {
+      ifstream file(keyFile);
+      file.read(reinterpret_cast<char*>(d_key.data), d_key.size);
+
+      if (file.fail()) {
+        file.close();
+        throw std::runtime_error("Invalid GnuTLS tickets key file " + keyFile);
+      }
+
+      file.close();
+    }
+    catch (const std::exception& e) {
+#ifdef HAVE_LIBSODIUM
+      sodium_munlock(d_key.data, d_key.size);
+#endif /* HAVE_LIBSODIUM */
+      gnutls_free(d_key.data);
+      throw;
+    }
+  }
+
+  ~GnuTLSTicketsKey()
+  {
+    if (d_key.data != nullptr && d_key.size > 0) {
+#ifdef HAVE_LIBSODIUM
+      sodium_munlock(d_key.data, d_key.size);
+#else
+      gnutls_memset(d_key.data, 0, d_key.size);
+#endif /* HAVE_LIBSODIUM */
+    }
+    gnutls_free(d_key.data);
+  }
+  const gnutls_datum_t& getKey() const
+  {
+    return d_key;
+  }
+
+private:
+  gnutls_datum_t d_key{nullptr, 0};
+};
+
+class GnuTLSConnection: public TLSConnection
+{
+public:
+
+  GnuTLSConnection(int socket, unsigned int timeout, const gnutls_certificate_credentials_t creds, const gnutls_priority_t priorityCache, std::shared_ptr<GnuTLSTicketsKey> ticketsKey): d_ticketsKey(ticketsKey)
+  {
+    d_socket = socket;
+
+    if (gnutls_init(&d_conn, GNUTLS_SERVER
+#ifdef GNUTLS_NO_SIGNAL
+                    | GNUTLS_NO_SIGNAL
+#endif
+          ) != GNUTLS_E_SUCCESS) {
+      throw std::runtime_error("Error creating TLS connection");
+    }
+
+    if (gnutls_credentials_set(d_conn, GNUTLS_CRD_CERTIFICATE, creds) != GNUTLS_E_SUCCESS) {
+      gnutls_deinit(d_conn);
+      throw std::runtime_error("Error setting certificate and key to TLS connection");
+    }
+
+    if (gnutls_priority_set(d_conn, priorityCache) != GNUTLS_E_SUCCESS) {
+      gnutls_deinit(d_conn);
+      throw std::runtime_error("Error setting ciphers to TLS connection");
+    }
+
+    if (d_ticketsKey) {
+      const gnutls_datum_t& key = d_ticketsKey->getKey();
+      if (gnutls_session_ticket_enable_server(d_conn, &key) != GNUTLS_E_SUCCESS) {
+        gnutls_deinit(d_conn);
+        throw std::runtime_error("Error setting the tickets key to TLS connection");
+      }
+    }
+
+    gnutls_transport_set_int(d_conn, d_socket);
+
+    /* timeouts are in milliseconds */
+    gnutls_handshake_set_timeout(d_conn, timeout * 1000);
+    gnutls_record_set_timeout(d_conn, timeout * 1000);
+
+    int ret = 0;
+    do {
+      ret = gnutls_handshake(d_conn);
+    }
+    while (ret < 0 && gnutls_error_is_fatal(ret) == 0);
+  }
+
+  virtual ~GnuTLSConnection() override
+  {
+    if (d_conn) {
+      gnutls_deinit(d_conn);
+    }
+  }
+
+  size_t read(void* buffer, size_t bufferSize, unsigned int readTimeout, unsigned int totalTimeout) override
+  {
+    size_t got = 0;
+    time_t start = 0;
+    unsigned int remainingTime = totalTimeout;
+    if (totalTimeout) {
+      start = time(nullptr);
+    }
+
+    do {
+      ssize_t res = gnutls_record_recv(d_conn, (reinterpret_cast<char *>(buffer) + got), bufferSize - got);
+      if (res == 0) {
+        throw std::runtime_error("Error reading from TLS connection");
+      }
+      else if (res > 0) {
+        got += (size_t) res;
+      }
+      else if (res < 0) {
+        if (gnutls_error_is_fatal(res)) {
+          throw std::runtime_error("Error reading from TLS connection");
+        }
+        warnlog("Warning, non-fatal error while reading from TLS connection: %s", gnutls_strerror(res));
+      }
+
+      if (totalTimeout) {
+        time_t now = time(nullptr);
+        unsigned int elapsed = now - start;
+        if (now < start || elapsed >= remainingTime) {
+          throw runtime_error("Timeout while reading data");
+        }
+        start = now;
+        remainingTime -= elapsed;
+      }
+    }
+    while (got < bufferSize);
+
+    return got;
+  }
+
+  size_t write(const void* buffer, size_t bufferSize, unsigned int writeTimeout) override
+  {
+    size_t got = 0;
+
+    do {
+      ssize_t res = gnutls_record_send(d_conn, (reinterpret_cast<const char *>(buffer) + got), bufferSize - got);
+      if (res == 0) {
+        throw std::runtime_error("Error writing to TLS connection");
+      }
+      else if (res > 0) {
+        got += (size_t) res;
+      }
+      else if (res < 0) {
+        if (gnutls_error_is_fatal(res)) {
+          throw std::runtime_error("Error writing to TLS connection");
+        }
+        warnlog("Warning, non-fatal error while writing to TLS connection: %s", gnutls_strerror(res));
+      }
+    }
+    while (got < bufferSize);
+
+    return got;
+  }
+
+  void close() override
+  {
+    if (d_conn) {
+      gnutls_bye(d_conn, GNUTLS_SHUT_WR);
+    }
+  }
+
+private:
+  gnutls_session_t d_conn{nullptr};
+  std::shared_ptr<GnuTLSTicketsKey> d_ticketsKey;
+};
+
+class GnuTLSIOCtx: public TLSCtx
+{
+public:
+  GnuTLSIOCtx(const TLSFrontend& fe)
+  {
+    int rc = 0;
+    d_ticketsKeyRotationDelay = fe.d_ticketsKeyRotationDelay;
+
+    rc = gnutls_certificate_allocate_credentials(&d_creds);
+    if (rc != GNUTLS_E_SUCCESS) {
+      throw std::runtime_error("Error allocating credentials for TLS context on " + fe.d_addr.toStringWithPort() + ": " + gnutls_strerror(rc));
+    }
+
+    rc = gnutls_certificate_set_x509_key_file(d_creds, fe.d_certFile.c_str(), fe.d_keyFile.c_str(), GNUTLS_X509_FMT_PEM);
+    if (rc != GNUTLS_E_SUCCESS) {
+      gnutls_certificate_free_credentials(d_creds);
+      throw std::runtime_error("Error loading certificate and key for TLS context on " + fe.d_addr.toStringWithPort() + ": " + gnutls_strerror(rc));
+    }
+
+#if GNUTLS_VERSION_NUMBER >= 0x030600
+    rc = gnutls_certificate_set_known_dh_params(d_creds, GNUTLS_SEC_PARAM_HIGH);
+    if (rc != GNUTLS_E_SUCCESS) {
+      gnutls_certificate_free_credentials(d_creds);
+      throw std::runtime_error("Error setting DH params for TLS context on " + fe.d_addr.toStringWithPort() + ": " + gnutls_strerror(rc));
+    }
+#endif
+
+    rc = gnutls_priority_init(&d_priorityCache, fe.d_ciphers.empty() ? "NORMAL" : fe.d_ciphers.c_str(), nullptr);
+    if (rc != GNUTLS_E_SUCCESS) {
+      warnlog("Error setting up TLS cipher preferences to %s (%s), skipping.", fe.d_ciphers.c_str(), gnutls_strerror(rc));
+    }
+
+    try {
+      if (fe.d_ticketKeyFile.empty()) {
+        handleTicketsKeyRotation(time(nullptr));
+      }
+      else {
+        loadTicketsKeys(fe.d_ticketKeyFile);
+      }
+    }
+    catch(const std::runtime_error& e) {
+      gnutls_certificate_free_credentials(d_creds);
+      throw std::runtime_error("Error generating tickets key for TLS context on " + fe.d_addr.toStringWithPort() + ": " + e.what());
+    }
+  }
+
+  virtual ~GnuTLSIOCtx() override
+  {
+    if (d_creds) {
+      gnutls_certificate_free_credentials(d_creds);
+    }
+    if (d_priorityCache) {
+      gnutls_priority_deinit(d_priorityCache);
+    }
+  }
+
+  std::unique_ptr<TLSConnection> getConnection(int socket, unsigned int timeout, time_t now) override
+  {
+    handleTicketsKeyRotation(now);
+
+    return std::unique_ptr<GnuTLSConnection>(new GnuTLSConnection(socket, timeout, d_creds, d_priorityCache, d_ticketsKey));
+  }
+
+  void rotateTicketsKey(time_t now) override
+  {
+    auto newKey = std::make_shared<GnuTLSTicketsKey>();
+    d_ticketsKey = newKey;
+    if (d_ticketsKeyRotationDelay > 0) {
+      d_ticketsKeyNextRotation = time(nullptr) + d_ticketsKeyRotationDelay;
+    }
+  }
+
+  void loadTicketsKeys(const std::string& file) override
+  {
+    auto newKey = std::make_shared<GnuTLSTicketsKey>(file);
+    d_ticketsKey = newKey;
+    if (d_ticketsKeyRotationDelay > 0) {
+      d_ticketsKeyNextRotation = time(nullptr) + d_ticketsKeyRotationDelay;
+    }
+  }
+
+  size_t getTicketsKeysCount() override
+  {
+    return d_ticketsKey != nullptr ? 1 : 0;
+  }
+
+private:
+  gnutls_certificate_credentials_t d_creds{nullptr};
+  gnutls_priority_t d_priorityCache{nullptr};
+  std::shared_ptr<GnuTLSTicketsKey> d_ticketsKey{nullptr};
+};
+
+#endif /* HAVE_GNUTLS */
+
+#endif /* HAVE_DNS_OVER_TLS */
+
+bool TLSFrontend::setupTLS()
+{
+#ifdef HAVE_DNS_OVER_TLS
+  /* get the "best" available provider */
+  if (!d_provider.empty()) {
+#ifdef HAVE_GNUTLS
+    if (d_provider == "gnutls") {
+      d_ctx = std::make_shared<GnuTLSIOCtx>(*this);
+      return true;
+    }
+#endif /* HAVE_GNUTLS */
+#ifdef HAVE_LIBSSL
+    if (d_provider == "openssl") {
+      d_ctx = std::make_shared<OpenSSLTLSIOCtx>(*this);
+      return true;
+    }
+#endif /* HAVE_LIBSSL */
+  }
+#ifdef HAVE_GNUTLS
+  d_ctx = std::make_shared<GnuTLSIOCtx>(*this);
+#else /* HAVE_GNUTLS */
+#ifdef HAVE_LIBSSL
+  d_ctx = std::make_shared<OpenSSLTLSIOCtx>(*this);
+#endif /* HAVE_LIBSSL */
+#endif /* HAVE_GNUTLS */
+
+#endif /* HAVE_DNS_OVER_TLS */
+  return true;
+}
diff --git a/pdns/dnsdistdist/tcpiohandler.hh b/pdns/dnsdistdist/tcpiohandler.hh
new file mode 100644 (file)
index 0000000..4f37804
--- /dev/null
@@ -0,0 +1,201 @@
+
+#pragma once
+#include <memory>
+
+#include "misc.hh"
+
+class TLSConnection
+{
+public:
+  virtual ~TLSConnection() { }
+  virtual size_t read(void* buffer, size_t bufferSize, unsigned int readTimeout, unsigned int totalTimeout=0) = 0;
+  virtual size_t write(const void* buffer, size_t bufferSize, unsigned int writeTimeout) = 0;
+  virtual void close() = 0;
+
+protected:
+  int d_socket{-1};
+};
+
+class TLSCtx
+{
+public:
+  TLSCtx()
+  {
+    d_rotatingTicketsKey.clear();
+  }
+  virtual ~TLSCtx() {}
+  virtual std::unique_ptr<TLSConnection> getConnection(int socket, unsigned int timeout, time_t now) = 0;
+  virtual void rotateTicketsKey(time_t now) = 0;
+  virtual void loadTicketsKeys(const std::string& file)
+  {
+    throw std::runtime_error("This TLS backend does not have the capability to load a tickets key from a file");
+  }
+
+  void handleTicketsKeyRotation(time_t now)
+  {
+    if (d_ticketsKeyRotationDelay != 0 && now > d_ticketsKeyNextRotation) {
+      if (d_rotatingTicketsKey.test_and_set()) {
+        /* someone is already rotating */
+        return;
+      }
+      try {
+        rotateTicketsKey(now);
+        d_rotatingTicketsKey.clear();
+      }
+      catch(const std::runtime_error& e) {
+        d_rotatingTicketsKey.clear();
+        throw std::runtime_error("Error generating a new tickets key for TLS context");
+      }
+    }
+  }
+
+  time_t getNextTicketsKeyRotation() const
+  {
+    return d_ticketsKeyNextRotation;
+  }
+
+  virtual size_t getTicketsKeysCount() = 0;
+
+protected:
+  std::atomic_flag d_rotatingTicketsKey;
+  time_t d_ticketsKeyRotationDelay{0};
+  time_t d_ticketsKeyNextRotation{0};
+};
+
+class TLSFrontend
+{
+public:
+  bool setupTLS();
+
+  void rotateTicketsKey(time_t now)
+  {
+    if (d_ctx != nullptr) {
+      d_ctx->rotateTicketsKey(now);
+    }
+  }
+
+  void loadTicketsKeys(const std::string& file)
+  {
+    if (d_ctx != nullptr) {
+      d_ctx->loadTicketsKeys(file);
+    }
+  }
+
+  std::shared_ptr<TLSCtx> getContext()
+  {
+    return d_ctx;
+  }
+
+  void cleanup()
+  {
+    d_ctx.reset();
+  }
+
+  size_t getTicketsKeysCount()
+  {
+    if (d_ctx != nullptr) {
+      return d_ctx->getTicketsKeysCount();
+    }
+
+    return 0;
+  }
+
+  static std::string timeToString(time_t rotationTime)
+  {
+    char buf[20];
+    struct tm date_tm;
+
+    localtime_r(&rotationTime, &date_tm);
+    strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S", &date_tm);
+
+    return std::string(buf);
+  }
+
+  time_t getTicketsKeyRotationDelay() const
+  {
+    return d_ticketsKeyRotationDelay;
+  }
+
+  std::string getNextTicketsKeyRotation() const
+  {
+    std::string res;
+
+    if (d_ctx != nullptr) {
+      res = timeToString(d_ctx->getNextTicketsKeyRotation());
+    }
+
+    return res;
+  }
+
+  std::set<int> d_cpus;
+  ComboAddress d_addr;
+  std::string d_certFile;
+  std::string d_keyFile;
+  std::string d_ciphers;
+  std::string d_provider;
+  std::string d_interface;
+  std::string d_ticketKeyFile;
+
+  time_t d_ticketsKeyRotationDelay{43200};
+  int d_tcpFastOpenQueueSize{0};
+  uint8_t d_numberOfTicketsKeys{5};
+  bool d_reusePort{false};
+
+private:
+  std::shared_ptr<TLSCtx> d_ctx{nullptr};
+};
+
+class TCPIOHandler
+{
+public:
+  TCPIOHandler(int socket, unsigned int timeout, std::shared_ptr<TLSCtx> ctx, time_t now): d_socket(socket)
+  {
+    if (ctx) {
+      d_conn = ctx->getConnection(d_socket, timeout, now);
+    }
+  }
+  ~TCPIOHandler()
+  {
+    if (d_conn) {
+      d_conn->close();
+    }
+    else if (d_socket != -1) {
+      shutdown(d_socket, SHUT_RDWR);
+    }
+  }
+  size_t read(void* buffer, size_t bufferSize, unsigned int readTimeout, unsigned int totalTimeout=0)
+  {
+    if (d_conn) {
+      return d_conn->read(buffer, bufferSize, readTimeout, totalTimeout);
+    } else {
+      return readn2WithTimeout(d_socket, buffer, bufferSize, readTimeout, totalTimeout);
+    }
+  }
+  size_t write(const void* buffer, size_t bufferSize, unsigned int writeTimeout)
+  {
+    if (d_conn) {
+      return d_conn->write(buffer, bufferSize, writeTimeout);
+    }
+    else {
+      return writen2WithTimeout(d_socket, buffer, bufferSize, writeTimeout);
+    }
+  }
+
+  bool writeSizeAndMsg(const void* buffer, size_t bufferSize, unsigned int writeTimeout)
+  {
+    if (d_conn) {
+      uint16_t size = htons(bufferSize);
+      if (d_conn->write(&size, sizeof(size), writeTimeout) != sizeof(size)) {
+        return false;
+      }
+      return (d_conn->write(buffer, bufferSize, writeTimeout) == bufferSize);
+    }
+    else {
+      return sendSizeAndMsgWithTimeout(d_socket, bufferSize, static_cast<const char*>(buffer), writeTimeout, nullptr, nullptr, 0, 0, 0);
+    }
+  }
+
+private:
+  std::unique_ptr<TLSConnection> d_conn{nullptr};
+  int d_socket{-1};
+};
diff --git a/pdns/dnsdistdist/xpf.cc b/pdns/dnsdistdist/xpf.cc
new file mode 120000 (symlink)
index 0000000..98ab722
--- /dev/null
@@ -0,0 +1 @@
+../xpf.cc
\ No newline at end of file
diff --git a/pdns/dnsdistdist/xpf.hh b/pdns/dnsdistdist/xpf.hh
new file mode 120000 (symlink)
index 0000000..023c8c4
--- /dev/null
@@ -0,0 +1 @@
+../xpf.hh
\ No newline at end of file
index c9dee777048ce09c761fdbd59706373d5e34b9cd..ecfda97bec5015ae82db6fdcf247afa740f16d1b 100644 (file)
@@ -423,7 +423,7 @@ string DNSName::escapeLabel(const std::string& label)
       ret+="\\.";
     else if(p=='\\')
       ret+="\\\\";
-    else if(p > 0x21 && p < 0x7e)
+    else if(p > 0x20 && p < 0x7f)
       ret.append(1, (char)p);
     else {
       ret+="\\" + (boost::format("%03d") % (unsigned int)p).str();
index e03f7fad9b5d86f15e4e77cce8b3017d62d1607e..dc89f342bf1cffedf6fa8ce3a11e71124c94793d 100644 (file)
@@ -36,6 +36,7 @@
 #include "dnsseckeeper.hh"
 #include "dns.hh"
 #include "dnsbackend.hh"
+#include "ednsoptions.hh"
 #include "pdnsexception.hh"
 #include "dnspacket.hh"
 #include "logger.hh"
@@ -105,7 +106,6 @@ DNSPacket::DNSPacket(const DNSPacket &orig)
   qdomainwild=orig.qdomainwild;
   qdomainzone=orig.qdomainzone;
   d_maxreplylen = orig.d_maxreplylen;
-  d_ednsping = orig.d_ednsping;
   d_wantsnsid = orig.d_wantsnsid;
   d_anyLocal = orig.d_anyLocal;  
   d_eso = orig.d_eso;
@@ -242,7 +242,7 @@ void DNSPacket::setCompress(bool compress)
 
 bool DNSPacket::couldBeCached()
 {
-  return d_ednsping.empty() && !d_wantsnsid && qclass==QClass::IN && !d_havetsig;
+  return !d_wantsnsid && qclass==QClass::IN && !d_havetsig;
 }
 
 unsigned int DNSPacket::getMinTTL()
@@ -306,11 +306,6 @@ void DNSPacket::wrapup()
     }
   }
 
-  if(!d_ednsping.empty()) {
-    opts.push_back(make_pair(4, d_ednsping));
-  }
-  
-  
   if(!d_rrs.empty() || !opts.empty() || d_haveednssubnet || d_haveednssection) {
     try {
       uint8_t maxScopeMask=0;
@@ -402,7 +397,6 @@ DNSPacket *DNSPacket::replyPacket() const
   r->qtype = qtype;
   r->qclass = qclass;
   r->d_maxreplylen = d_maxreplylen;
-  r->d_ednsping = d_ednsping;
   r->d_wantsnsid = d_wantsnsid;
   r->d_dnssecOk = d_dnssecOk;
   r->d_eso = d_eso;
@@ -447,7 +441,6 @@ int DNSPacket::noparse(const char *mesg, size_t length)
     return -1;
   }
   d_wantsnsid=false;
-  d_ednsping.clear();
   d_maxreplylen=512;
   memcpy((void *)&d,(const void *)d_rawpacket.c_str(),12);
   return 0;
@@ -543,7 +536,6 @@ try
 
   d_wantsnsid=false;
   d_dnssecOk=false;
-  d_ednsping.clear();
   d_havetsig = mdp.getTSIGPos();
   d_haveednssubnet = false;
   d_haveednssection = false;
@@ -562,13 +554,10 @@ try
     for(vector<pair<uint16_t, string> >::const_iterator iter = edo.d_options.begin();
         iter != edo.d_options.end(); 
         ++iter) {
-      if(iter->first == 3) {// 'EDNS NSID'
-        d_wantsnsid=1;
-      }
-      else if(iter->first == 5) {// 'EDNS PING'
-        d_ednsping = iter->second;
+      if(iter->first == EDNSOptionCode::NSID) {
+        d_wantsnsid=true;
       }
-      else if(s_doEDNSSubnetProcessing && (iter->first == 8)) { // 'EDNS SUBNET'
+      else if(s_doEDNSSubnetProcessing && (iter->first == EDNSOptionCode::ECS)) { // 'EDNS SUBNET'
         if(getEDNSSubnetOptsFromString(iter->second, &d_eso)) {
           //cerr<<"Parsed, source: "<<d_eso.source.toString()<<", scope: "<<d_eso.scope.toString()<<", family = "<<d_eso.scope.getNetwork().sin4.sin_family<<endl;
           d_haveednssubnet=true;
index 5a717e307f217d31b3d86f674aef27f26c08c779..27b428bf02ddb0672492c2c686dd71459fd642f4 100644 (file)
@@ -175,7 +175,6 @@ private:
 
   vector<DNSZoneRecord> d_rrs; // 8
   string d_rawpacket; // this is where everything lives 8
-  string d_ednsping;
   EDNSSubnetOpts d_eso;
 
   int d_maxreplylen;
index 126432f12c638b0ed3ca94f18ea2fa0eb6d12cad..ba44b61e8da961174abffb112f784c5d5a159650 100644 (file)
@@ -260,6 +260,7 @@ void MOADNSParser::init(bool query, const char *packet, unsigned int len)
 
     struct dnsrecordheader ah;
     vector<unsigned char> record;
+    bool seenTSIG = false;
     validPacket=true;
     d_answers.reserve((unsigned int)(d_header.ancount + d_header.nscount + d_header.arcount));
     for(n=0;n < (unsigned int)(d_header.ancount + d_header.nscount + d_header.arcount); ++n) {
@@ -271,16 +272,16 @@ void MOADNSParser::init(bool query, const char *packet, unsigned int len)
         dr.d_place=DNSResourceRecord::AUTHORITY;
       else 
         dr.d_place=DNSResourceRecord::ADDITIONAL;
-      
+
       unsigned int recordStartPos=pr.d_pos;
 
       DNSName name=pr.getName();
-      
+
       pr.getDnsrecordheader(ah);
       dr.d_ttl=ah.d_ttl;
       dr.d_type=ah.d_type;
       dr.d_class=ah.d_class;
-      
+
       dr.d_name=name;
       dr.d_clen=ah.d_clen;
 
@@ -295,20 +296,29 @@ void MOADNSParser::init(bool query, const char *packet, unsigned int len)
 
       d_answers.push_back(make_pair(dr, pr.d_pos));
 
+      /* XXX: XPF records should be allowed after TSIG as soon as the actual XPF option code has been assigned:
+         if (dr.d_place == DNSResourceRecord::ADDITIONAL && seenTSIG && dr.d_type != QType::XPF)
+      */
+      if (dr.d_place == DNSResourceRecord::ADDITIONAL && seenTSIG) {
+        /* only XPF records are allowed after a TSIG */
+        throw MOADNSException("Packet ("+d_qname.toString()+"|#"+std::to_string(d_qtype)+") has an unexpected record ("+std::to_string(dr.d_type)+") after a TSIG one.");
+      }
+
       if(dr.d_type == QType::TSIG && dr.d_class == QClass::ANY) {
-        if(dr.d_place != DNSResourceRecord::ADDITIONAL || n != (unsigned int)(d_header.ancount + d_header.nscount + d_header.arcount) - 1) {
-          throw MOADNSException("Packet ("+d_qname.toString()+"|#"+std::to_string(d_qtype)+") has a TSIG record in an invalid position.");
+        if(seenTSIG || dr.d_place != DNSResourceRecord::ADDITIONAL) {
+          throw MOADNSException("Packet ("+d_qname.toLogString()+"|#"+std::to_string(d_qtype)+") has a TSIG record in an invalid position.");
         }
+        seenTSIG = true;
         d_tsigPos = recordStartPos + sizeof(struct dnsheader);
       }
     }
 
-#if 0    
+#if 0
     if(pr.d_pos!=contentlen) {
       throw MOADNSException("Packet ("+d_qname+"|#"+std::to_string(d_qtype)+") has trailing garbage ("+ std::to_string(pr.d_pos) + " < " + 
                             std::to_string(contentlen) + ")");
     }
-#endif 
+#endif
   }
   catch(std::out_of_range &re) {
     if(validPacket && d_header.tc) { // don't sweat it over truncated packets, but do adjust an, ns and arcount
index 78a73f03bebc3b23b89d8fc7c72ab6f57e725232..9ddc942ab568e28357900003d16423dd468be675 100644 (file)
@@ -37,6 +37,7 @@
 #include "dnswriter.hh"
 #include "dnsname.hh"
 #include "pdnsexception.hh"
+#include "iputils.hh"
 
 /** DNS records have three representations:
     1) in the packet
@@ -98,6 +99,20 @@ public:
     xfrBlob(val, 16);
   }
 
+  void xfrCAWithoutPort(uint8_t version, ComboAddress &val) {
+    string blob;
+    if (version == 4) xfrBlob(blob, 4);
+    else if (version == 6) xfrBlob(blob, 16);
+    else throw runtime_error("invalid IP protocol");
+    val = makeComboAddressFromRaw(version, blob);
+  }
+
+  void xfrCAPort(ComboAddress &val) {
+    uint16_t port;
+    xfr16BitInt(port);
+    val.sin4.sin_port = port;
+  }
+
   void xfrTime(uint32_t& val)
   {
     xfr32BitInt(val);
@@ -153,6 +168,9 @@ public:
   uint16_t d_pos;
 
   bool eof() { return true; };
+  const string getRemaining() const {
+    return "";
+  };
 
 private:
   uint16_t d_startrecordpos; // needed for getBlob later on
index bee70a86a09f71f77b98bb00e2f1ecbd9c20ff4f..114c47253739dc1b947fc4d2b1832bd7310f0796 100644 (file)
@@ -44,18 +44,21 @@ DNSProxy::DNSProxy(const string &remote)
 
   vector<string> addresses;
   stringtok(addresses, remote, " ,\t");
-  ComboAddress remaddr(addresses[0], 53);
-  
-  if((d_sock=socket(remaddr.sin4.sin_family, SOCK_DGRAM,0))<0)
+  d_remote = ComboAddress(addresses[0], 53);
+
+  if((d_sock=socket(d_remote.sin4.sin_family, SOCK_DGRAM,0))<0) {
     throw PDNSException(string("socket: ")+strerror(errno));
+  }
+
   ComboAddress local;
-  if(remaddr.sin4.sin_family==AF_INET)
+  if(d_remote.sin4.sin_family==AF_INET) {
     local = ComboAddress("0.0.0.0");
-  else
+  }
+  else {
     local = ComboAddress("::");
+  }
     
-  int n=0;
+  unsigned int n=0;
   for(;n<10;n++) {
     local.sin4.sin_port = htons(10000+dns_random(50000));
     
@@ -68,11 +71,12 @@ DNSProxy::DNSProxy(const string &remote)
     throw PDNSException(string("binding dnsproxy socket: ")+strerror(errno));
   }
 
-  if(connect(d_sock, (sockaddr *)&remaddr, remaddr.getSocklen())<0) 
-    throw PDNSException("Unable to UDP connect to remote nameserver "+remaddr.toStringWithPort()+": "+stringerror());
+  if(connect(d_sock, (sockaddr *)&d_remote, d_remote.getSocklen())<0) {
+    throw PDNSException("Unable to UDP connect to remote nameserver "+d_remote.toStringWithPort()+": "+stringerror());
+  }
 
   d_xor=dns_random(0xffff);
-  L<<Logger::Error<<"DNS Proxy launched, local port "<<ntohs(local.sin4.sin_port)<<", remote "<<remaddr.toStringWithPort()<<endl;
+  L<<Logger::Error<<"DNS Proxy launched, local port "<<ntohs(local.sin4.sin_port)<<", remote "<<d_remote.toStringWithPort()<<endl;
 } 
 
 void DNSProxy::go()
@@ -179,9 +183,11 @@ void DNSProxy::mainloop(void)
     struct msghdr msgh;
     struct iovec iov;
     char cbuf[256];
+    ComboAddress fromaddr;
 
     for(;;) {
-      len=recv(d_sock, buffer, sizeof(buffer),0); // answer from our backend
+      socklen_t fromaddrSize = sizeof(fromaddr);
+      len=recvfrom(d_sock, buffer, sizeof(buffer),0, (struct sockaddr*) &fromaddr, &fromaddrSize); // answer from our backend
       if(len<(ssize_t)sizeof(dnsheader)) {
         if(len<0)
           L<<Logger::Error<<"Error receiving packet from recursor backend: "<<stringerror()<<endl;
@@ -192,6 +198,10 @@ void DNSProxy::mainloop(void)
         
         continue;
       }
+      if (fromaddr != d_remote) {
+        L<<Logger::Error<<"Got answer from unexpected host "<<fromaddr.toStringWithPort()<<" instead of our recursor backend "<<d_remote.toStringWithPort()<<endl;
+        continue;
+      }
       (*d_resanswers)++;
       (*d_udpanswers)++;
       dnsheader d;
index f927c86a7a86cb1c20a458ab7e02e9bafbffa1f8..527eabadafe63f60394b27a857768c91d6d41a23 100644 (file)
@@ -81,6 +81,7 @@ private:
   typedef map<int,ConntrackEntry> map_t;
 
   // Data
+  ComboAddress d_remote;
   AtomicCounter* d_resanswers;
   AtomicCounter* d_udpanswers;
   AtomicCounter* d_resquestions;
index 418cead8df9adfca7d10b9c10620b32ee05a472c..39e662a56f36fdd92580d128c7d884e2d0179ecc 100644 (file)
@@ -104,6 +104,7 @@ AAAARecordContent::AAAARecordContent(const ComboAddress& ca)
 }
 
 
+
 ComboAddress ARecordContent::getCA(int port) const
 {
   ComboAddress ret;
@@ -268,7 +269,7 @@ SOARecordContent::SOARecordContent(const DNSName& mname, const DNSName& rname, c
   d_st=st;
 }
 
-boilerplate_conv(SOA, QType::SOA, 
+boilerplate_conv(SOA, QType::SOA,
                  conv.xfrName(d_mname, true);
                  conv.xfrName(d_rname, true);
                  conv.xfr32BitInt(d_st.serial);
index 55afedbf2d40f91fd44fa60ea96ec303b67df8e7..fe19b55c7a6e0a345a882a176ddc4c0df0c29777 100644 (file)
@@ -28,6 +28,7 @@
 #include <set>
 #include <bitset>
 #include "namespaces.hh"
+#include "iputils.hh"
 
 #define includeboilerplate(RNAME)   RNAME##RecordContent(const DNSRecord& dr, PacketReader& pr); \
   RNAME##RecordContent(const string& zoneData);                                                  \
@@ -746,7 +747,7 @@ template<class Convertor>                                         \
 void RNAME##RecordContent::xfrPacket(Convertor& conv, bool noDot) \
 {                                                                 \
   CONV;                                                           \
-  if (conv.eof() == false) throw MOADNSException("All data was not consumed"); \
+  if (conv.eof() == false) throw MOADNSException("When parsing " #RNAME " trailing data was not parsed: '" + conv.getRemaining() + "'"); \
 }                                                                 \
 
 struct EDNSOpts
index 4c5f218c99a17b890c2815c4e7fdffda05566770..64868b2d066b067db932c0d0a55760afe14458e2 100644 (file)
@@ -553,9 +553,6 @@ Orig    9           21      29     36         47        57       66    72
   pruneQids();
 }
 
-
-bool g_rdSelector;
-
 static void generateOptRR(const std::string& optRData, string& res)
 {
   const uint8_t name = 0;
@@ -603,12 +600,14 @@ static void addECSOption(char* packet, const size_t& packetSize, uint16_t* len,
   }
 }
 
+static bool g_rdSelector;
+static uint16_t g_pcapDnsPort;
 
-bool sendPacketFromPR(PcapPacketReader& pr, const ComboAddress& remote, int stamp)
+static bool sendPacketFromPR(PcapPacketReader& pr, const ComboAddress& remote, int stamp)
 {
   dnsheader* dh=(dnsheader*)pr.d_payload;
   bool sent=false;
-  if((ntohs(pr.d_udp->uh_dport)!=53 && ntohs(pr.d_udp->uh_sport)!=53) || dh->rd != g_rdSelector || (unsigned int)pr.d_len <= sizeof(dnsheader))
+  if((ntohs(pr.d_udp->uh_dport)!=g_pcapDnsPort && ntohs(pr.d_udp->uh_sport)!=g_pcapDnsPort) || dh->rd != g_rdSelector || (unsigned int)pr.d_len <= sizeof(dnsheader))
     return sent;
 
   QuestionData qd;
@@ -704,6 +703,7 @@ try
     ("help,h", "produce help message")
     ("version", "show version number")
     ("packet-limit", po::value<uint32_t>()->default_value(0), "stop after this many packets")
+    ("pcap-dns-port", po::value<uint16_t>()->default_value(53), "look at packets from or to this port in the PCAP (defaults to 53)")
     ("quiet", po::value<bool>()->default_value(true), "don't be too noisy")
     ("recursive", po::value<bool>()->default_value(true), "look at recursion desired packets, or not (defaults true)")
     ("speedup", po::value<float>()->default_value(1), "replay at this speedup")
@@ -750,6 +750,7 @@ try
   uint32_t packetLimit = g_vm["packet-limit"].as<uint32_t>();
 
   g_rdSelector = g_vm["recursive"].as<bool>();
+  g_pcapDnsPort = g_vm["pcap-dns-port"].as<uint16_t>();
 
   g_quiet = g_vm["quiet"].as<bool>();
 
index 68cd19e000991633f04cf16b9feaca40267a97f0..d37a0465cb397bed06543b1acb9b153a674f1d46 100644 (file)
@@ -696,14 +696,14 @@ void addTSIG(DNSPacketWriter& pw, TSIGRecordContent& trc, const DNSName& tsigkey
 {
   TSIGHashEnum algo;
   if (!getTSIGHashEnum(trc.d_algoName, algo)) {
-    throw PDNSException(string("Unsupported TSIG HMAC algorithm ") + trc.d_algoName.toString());
+    throw PDNSException(string("Unsupported TSIG HMAC algorithm ") + trc.d_algoName.toLogString());
   }
 
   string toSign = makeTSIGPayload(tsigprevious, reinterpret_cast<const char*>(pw.getContent().data()), pw.getContent().size(), tsigkeyname, trc, timersonly);
 
   if (algo == TSIG_GSS) {
     if (!gss_add_signature(tsigkeyname, toSign, trc.d_mac)) {
-      throw PDNSException(string("Could not add TSIG signature with algorithm 'gss-tsig' and key name '")+tsigkeyname.toString()+string("'"));
+      throw PDNSException(string("Could not add TSIG signature with algorithm 'gss-tsig' and key name '")+tsigkeyname.toLogString()+string("'"));
     }
   } else {
     trc.d_mac = calculateHMAC(tsigsecret, toSign, algo);
@@ -723,16 +723,16 @@ bool validateTSIG(const std::string& packet, size_t sigPos, const TSIGTriplet& t
 
   TSIGHashEnum algo;
   if (!getTSIGHashEnum(trc.d_algoName, algo)) {
-    throw std::runtime_error("Unsupported TSIG HMAC algorithm " + trc.d_algoName.toString());
+    throw std::runtime_error("Unsupported TSIG HMAC algorithm " + trc.d_algoName.toLogString());
   }
 
   TSIGHashEnum expectedAlgo;
   if (!getTSIGHashEnum(tt.algo, expectedAlgo)) {
-    throw std::runtime_error("Unsupported TSIG HMAC algorithm expected " + tt.algo.toString());
+    throw std::runtime_error("Unsupported TSIG HMAC algorithm expected " + tt.algo.toLogString());
   }
 
   if (algo != expectedAlgo) {
-    throw std::runtime_error("Signature with TSIG key '"+tt.name.toString()+"' does not match the expected algorithm (" + tt.algo.toString() + " / " + trc.d_algoName.toString() + ")");
+    throw std::runtime_error("Signature with TSIG key '"+tt.name.toLogString()+"' does not match the expected algorithm (" + tt.algo.toLogString() + " / " + trc.d_algoName.toLogString() + ")");
   }
 
   string tsigMsg;
@@ -741,13 +741,13 @@ bool validateTSIG(const std::string& packet, size_t sigPos, const TSIGTriplet& t
   if (algo == TSIG_GSS) {
     GssContext gssctx(tt.name);
     if (!gss_verify_signature(tt.name, tsigMsg, theirMAC)) {
-      throw std::runtime_error("Signature with TSIG key '"+tt.name.toString()+"' failed to validate");
+      throw std::runtime_error("Signature with TSIG key '"+tt.name.toLogString()+"' failed to validate");
     }
   } else {
     string ourMac = calculateHMAC(tt.secret, tsigMsg, algo);
 
     if(!constantTimeStringEquals(ourMac, theirMAC)) {
-      throw std::runtime_error("Signature with TSIG key '"+tt.name.toString()+"' failed to validate");
+      throw std::runtime_error("Signature with TSIG key '"+tt.name.toLogString()+"' failed to validate");
     }
   }
 
index 61b17ae571fb07b4cffbc0143071740909b658f5..3e50a584f37d88557bc6bdf39a243368e4a8f703 100644 (file)
@@ -298,11 +298,8 @@ public:
 class DNSPacket;
 uint32_t localtime_format_YYYYMMDDSS(time_t t, uint32_t seq);
 // for SOA-EDIT
-uint32_t calculateEditSOA(const DNSZoneRecord& rr, const string& kind);
-uint32_t calculateEditSOA(const SOAData& sd, const string& kind);
-bool editSOA(DNSSECKeeper& dk, const DNSName& qname, DNSPacket* dp);
-bool editSOARecord(DNSZoneRecord& rr, const string& kind);
+uint32_t calculateEditSOA(uint32_t old_serial, DNSSECKeeper& dk, const DNSName& zonename);
+uint32_t calculateEditSOA(uint32_t old_serial, const string& kind, const DNSName& zonename);
 // for SOA-EDIT-DNSUPDATE/API
-uint32_t calculateIncreaseSOA(SOAData sd, const string& increaseKind, const string& editKind);
-bool increaseSOARecord(DNSResourceRecord& rr, const string& increaseKind, const string& editKind);
-bool increaseSOARecord(DNSZoneRecord& rr, const string& increaseKind, const string& editKind);
+bool increaseSOARecord(DNSResourceRecord& dr, const string& increaseKind, const string& editKind);
+bool makeIncreasedSOARecord(SOAData& sd, const string& increaseKind, const string& editKind, DNSResourceRecord& rrout);
index 49cf6955cac9ee49dac7bff778d7a02eac396deb..64b1e35eaf14723670e77b6525195226b98e04fc 100644 (file)
@@ -28,6 +28,7 @@
 #include "dns.hh"
 #include "dnsname.hh"
 #include "namespaces.hh"
+#include "iputils.hh"
 #include <arpa/inet.h>
 
 
@@ -100,6 +101,25 @@ public:
   {
     xfrBlob(val,16);
   }
+
+  void xfrCAWithoutPort(uint8_t version, ComboAddress &val)
+  {
+    if (version == 4) xfrIP(val.sin4.sin_addr.s_addr);
+    else if (version == 6) {
+      string blob;
+      blob.assign((const char*)val.sin6.sin6_addr.s6_addr, 16);
+      xfrBlob(blob, 16);
+    }
+    else throw runtime_error("invalid IP protocol");
+  }
+
+  void xfrCAPort(ComboAddress &val)
+  {
+    uint16_t port;
+    port = val.sin4.sin_port;
+    xfr16BitInt(port);
+  }
+
   void xfrTime(const uint32_t& val)
   {
     xfr32BitInt(val);
@@ -132,6 +152,9 @@ public:
   }
   bool eof() { return true; } // we don't know how long the record should be
 
+  const string getRemaining() const {
+    return "";
+  }
 private:
   uint16_t lookupName(const DNSName& name, uint16_t* matchlen);
   vector<uint16_t> d_namepositions;
index ea0279a3e069d6bee96018b4c0bf6852afddf053..2fdc9a57d0ca7ff19621fa2c125ceab5d95e89ea 100644 (file)
@@ -26,7 +26,7 @@
 
 struct EDNSOptionCode
 {
-  enum EDNSOptionCodeEnum {NSID=3, DAU=4, DHU=6, N3U=7, ECS=8, EXPIRE=9, COOKIE=10, TCPKEEPALIVE=11, PADDING=12, CHAIN=13};
+  enum EDNSOptionCodeEnum {NSID=3, DAU=5, DHU=6, N3U=7, ECS=8, EXPIRE=9, COOKIE=10, TCPKEEPALIVE=11, PADDING=12, CHAIN=13, KEYTAG=14};
 };
 
 /* extract a specific EDNS0 option from a pointer on the beginning rdLen of the OPT RR */
index d6facc02958f314afa2bf0bcc25e26fc317638a7..b2d512182a2837c168fe02a7c7061951295b9660 100644 (file)
@@ -146,7 +146,11 @@ bool HarvestTimestamp(struct msghdr* msgh, struct timeval* tv)
 bool HarvestDestinationAddress(const struct msghdr* msgh, ComboAddress* destination)
 {
   memset(destination, 0, sizeof(*destination));
+#ifdef __NetBSD__
+  struct cmsghdr* cmsg;
+#else
   const struct cmsghdr* cmsg;
+#endif
   for (cmsg = CMSG_FIRSTHDR(msgh); cmsg != NULL; cmsg = CMSG_NXTHDR(const_cast<struct msghdr*>(msgh), const_cast<struct cmsghdr*>(cmsg))) {
 #if defined(IP_PKTINFO)
      if ((cmsg->cmsg_level == IPPROTO_IP) && (cmsg->cmsg_type == IP_PKTINFO)) {
index 8ab4f13c3473311d8e82625e461e26eabc95165e..b80aa800f344417d348c2ed39255d7e75c0d72ec 100644 (file)
 #include <sys/endian.h>
 #endif
 
+#if defined(__NetBSD__) && defined(IP_PKTINFO) && !defined(IP_SENDSRCADDR)
+// The IP_PKTINFO option in NetBSD was incompatible with Linux until a
+// change that also introduced IP_SENDSRCADDR for FreeBSD compatibility.
+#undef IP_PKTINFO
+#endif
+
 union ComboAddress {
   struct sockaddr_in sin4;
   struct sockaddr_in6 sin6;
@@ -182,6 +188,8 @@ union ComboAddress {
     sin4.sin_family=AF_INET;
     sin4.sin_addr.s_addr=0;
     sin4.sin_port=0;
+    sin6.sin6_scope_id = 0;
+    sin6.sin6_flowinfo = 0;
   }
 
   ComboAddress(const struct sockaddr *sa, socklen_t salen) {
@@ -261,10 +269,11 @@ union ComboAddress {
   string toString() const
   {
     char host[1024];
-    if(sin4.sin_family && !getnameinfo((struct sockaddr*) this, getSocklen(), host, sizeof(host),0, 0, NI_NUMERICHOST))
+    int retval = 0;
+    if(sin4.sin_family && !(retval = getnameinfo((struct sockaddr*) this, getSocklen(), host, sizeof(host),0, 0, NI_NUMERICHOST)))
       return host;
     else
-      return "invalid";
+      return "invalid "+string(gai_strerror(retval));
   }
 
   string toStringWithPort() const
@@ -303,6 +312,30 @@ inline ComboAddress makeComboAddress(const string& str)
   return address;
 }
 
+inline ComboAddress makeComboAddressFromRaw(uint8_t version, const char* raw, size_t len)
+{
+  ComboAddress address;
+
+  if (version == 4) {
+    address.sin4.sin_family = AF_INET;
+    if (len != sizeof(address.sin4.sin_addr)) throw NetmaskException("invalid raw address length");
+    memcpy(&address.sin4.sin_addr, raw, sizeof(address.sin4.sin_addr));
+  }
+  else if (version == 6) {
+    address.sin6.sin6_family = AF_INET6;
+    if (len != sizeof(address.sin6.sin6_addr)) throw NetmaskException("invalid raw address length");
+    memcpy(&address.sin6.sin6_addr, raw, sizeof(address.sin6.sin6_addr));
+  }
+  else throw NetmaskException("invalid address family");
+
+  return address;
+}
+
+inline ComboAddress makeComboAddressFromRaw(uint8_t version, const string &str)
+{
+  return makeComboAddressFromRaw(version, str.c_str(), str.size());
+}
+
 /** This class represents a netmask and can be queried to see if a certain
     IP address is matched by this mask */
 class Netmask
@@ -992,6 +1025,7 @@ int SSetsockopt(int sockfd, int level, int opname, int value);
 #elif defined(IP_RECVDSTADDR)
   #define GEN_IP_PKTINFO IP_RECVDSTADDR 
 #endif
+
 bool IsAnyAddress(const ComboAddress& addr);
 bool HarvestDestinationAddress(const struct msghdr* msgh, ComboAddress* destination);
 bool HarvestTimestamp(struct msghdr* msgh, struct timeval* tv);
index 74982b518f779e2e13d24d345196c037cc4a2a07..54bb8781adedc0bfe8f76980b160e18096d85428 100644 (file)
@@ -51,7 +51,7 @@ vector<pair<vector<DNSRecord>, vector<DNSRecord> > > processIXFRRecords(const Co
 
     auto sr = getRR<SOARecordContent>(records[pos]);
     if (!sr) {
-      throw std::runtime_error("Error getting the content of the first SOA record of this IXFR sequence for zone '"+zone.toString()+"' from master '"+master.toStringWithPort()+"'");
+      throw std::runtime_error("Error getting the content of the first SOA record of this IXFR sequence for zone '"+zone.toLogString()+"' from master '"+master.toStringWithPort()+"'");
     }
 
     // cerr<<"Serial is "<<sr->d_st.serial<<", final serial is "<<masterSOA->d_st.serial<<endl;
@@ -71,12 +71,12 @@ vector<pair<vector<DNSRecord>, vector<DNSRecord> > > processIXFRRecords(const Co
     }
 
     if (pos >= records.size()) {
-      throw std::runtime_error("No SOA record to finish the removals part of the IXFR sequence of zone '" + zone.toString() + "' from " + master.toStringWithPort());
+      throw std::runtime_error("No SOA record to finish the removals part of the IXFR sequence of zone '" + zone.toLogString() + "' from " + master.toStringWithPort());
     }
 
     sr = getRR<SOARecordContent>(records[pos]);
     if (!sr) {
-      throw std::runtime_error("Invalid SOA record to finish the removals part of the IXFR sequence of zone '" + zone.toString() + "' from " + master.toStringWithPort());
+      throw std::runtime_error("Invalid SOA record to finish the removals part of the IXFR sequence of zone '" + zone.toLogString() + "' from " + master.toStringWithPort());
     }
 
     // this is the serial of the zone after the removals
@@ -91,22 +91,22 @@ vector<pair<vector<DNSRecord>, vector<DNSRecord> > > processIXFRRecords(const Co
     }
 
     if (pos >= records.size()) {
-      throw std::runtime_error("No SOA record to finish the additions part of the IXFR sequence of zone '" + zone.toString() + "' from " + master.toStringWithPort());
+      throw std::runtime_error("No SOA record to finish the additions part of the IXFR sequence of zone '" + zone.toLogString() + "' from " + master.toStringWithPort());
     }
 
     sr = getRR<SOARecordContent>(records[pos]);
     if (!sr) {
-      throw std::runtime_error("Invalid SOA record to finish the additions part of the IXFR sequence of zone '" + zone.toString() + "' from " + master.toStringWithPort());
+      throw std::runtime_error("Invalid SOA record to finish the additions part of the IXFR sequence of zone '" + zone.toLogString() + "' from " + master.toStringWithPort());
     }
 
     if (sr->d_st.serial != newSerial) {
-      throw std::runtime_error("Invalid serial (" + std::to_string(sr->d_st.serial) + ", expecting " + std::to_string(newSerial) + ") in the SOA record finishing the additions part of the IXFR sequence of zone '" + zone.toString() + "' from " + master.toStringWithPort());
+      throw std::runtime_error("Invalid serial (" + std::to_string(sr->d_st.serial) + ", expecting " + std::to_string(newSerial) + ") in the SOA record finishing the additions part of the IXFR sequence of zone '" + zone.toLogString() + "' from " + master.toStringWithPort());
     }
 
     if (newSerial == masterSOA->d_st.serial) {
       // this was the last sequence
       if (pos != (records.size() - 1)) {
-        throw std::runtime_error("Trailing records after the last IXFR sequence of zone '" + zone.toString() + "' from " + master.toStringWithPort());
+        throw std::runtime_error("Trailing records after the last IXFR sequence of zone '" + zone.toLogString() + "' from " + master.toStringWithPort());
       }
     }
 
@@ -138,7 +138,7 @@ vector<pair<vector<DNSRecord>, vector<DNSRecord> > > getIXFRDeltas(const ComboAd
     try {
       trc.d_algoName = getTSIGAlgoName(the);
     } catch(PDNSException& pe) {
-      throw std::runtime_error("TSIG algorithm '"+tt.algo.toString()+"' is unknown.");
+      throw std::runtime_error("TSIG algorithm '"+tt.algo.toLogString()+"' is unknown.");
     }
     trc.d_time = time((time_t*)NULL);
     trc.d_fudge = 300;
@@ -179,7 +179,7 @@ vector<pair<vector<DNSRecord>, vector<DNSRecord> > > getIXFRDeltas(const ComboAd
       break;
 
     if (maxReceivedBytes > 0 && (maxReceivedBytes - receivedBytes) < (size_t) len)
-      throw std::runtime_error("Reached the maximum number of received bytes in an IXFR delta for zone '"+zone.toString()+"' from master "+master.toStringWithPort());
+      throw std::runtime_error("Reached the maximum number of received bytes in an IXFR delta for zone '"+zone.toLogString()+"' from master "+master.toStringWithPort());
 
     char reply[len]; 
     readn2(s.getHandle(), reply, len);
@@ -187,7 +187,7 @@ vector<pair<vector<DNSRecord>, vector<DNSRecord> > > getIXFRDeltas(const ComboAd
 
     MOADNSParser mdp(false, string(reply, len));
     if(mdp.d_header.rcode) 
-      throw std::runtime_error("Got an error trying to IXFR zone '"+zone.toString()+"' from master '"+master.toStringWithPort()+"': "+RCode::to_s(mdp.d_header.rcode));
+      throw std::runtime_error("Got an error trying to IXFR zone '"+zone.toLogString()+"' from master '"+master.toStringWithPort()+"': "+RCode::to_s(mdp.d_header.rcode));
 
     //    cout<<"Got a response, rcode: "<<mdp.d_header.rcode<<", got "<<mdp.d_answers.size()<<" answers"<<endl;
 
@@ -200,12 +200,12 @@ vector<pair<vector<DNSRecord>, vector<DNSRecord> > > getIXFRDeltas(const ComboAd
       if(!masterSOA) {
         // we have not seen the first SOA record yet
         if (r.first.d_type != QType::SOA) {
-          throw std::runtime_error("The first record of the IXFR answer for zone '"+zone.toString()+"' from master '"+master.toStringWithPort()+"' is not a SOA ("+QType(r.first.d_type).getName()+")");
+          throw std::runtime_error("The first record of the IXFR answer for zone '"+zone.toLogString()+"' from master '"+master.toStringWithPort()+"' is not a SOA ("+QType(r.first.d_type).getName()+")");
         }
 
        auto sr = getRR<SOARecordContent>(r.first);
        if (!sr) {
-          throw std::runtime_error("Error getting the content of the first SOA record of the IXFR answer for zone '"+zone.toString()+"' from master '"+master.toStringWithPort()+"'");
+          throw std::runtime_error("Error getting the content of the first SOA record of the IXFR answer for zone '"+zone.toLogString()+"' from master '"+master.toStringWithPort()+"'");
         }
 
         if(sr->d_st.serial == std::dynamic_pointer_cast<SOARecordContent>(oursr.d_content)->d_st.serial) {
@@ -222,7 +222,7 @@ vector<pair<vector<DNSRecord>, vector<DNSRecord> > > getIXFRDeltas(const ComboAd
         if(r.first.d_type == QType::OPT)
           continue;
 
-        throw std::runtime_error("Unexpected record (" +QType(r.first.d_type).getName()+") in non-answer section ("+std::to_string(r.first.d_place)+")in IXFR response for zone '"+zone.toString()+"' from master '"+master.toStringWithPort());
+        throw std::runtime_error("Unexpected record (" +QType(r.first.d_type).getName()+") in non-answer section ("+std::to_string(r.first.d_place)+")in IXFR response for zone '"+zone.toLogString()+"' from master '"+master.toStringWithPort());
       }
 
       r.first.d_name.makeUsRelative(zone);
index b3510c501fe1880c86bf9594f8007ffc8c383669..2fb66c8c78bd1dcdd3112ce23f2f66b2a642876f 100644 (file)
@@ -28,7 +28,7 @@
 #include <unistd.h>
 #include "misc.hh"
 #include <sys/types.h>
-#if defined(__FreeBSD__) || defined(__FreeBSD_kernel__)
+#if defined(__FreeBSD__) || defined(__FreeBSD_kernel__) || defined(__NetBSD__)
 #include <sys/event.h>
 #endif
 #include <sys/time.h>
index 88e05802e035a46a53e85c96c1988e4be6b93590..8dc5d4b360e3db1d1dca0a59c65c1ee24689cd19 100644 (file)
 #ifdef __FreeBSD__
 #  include <pthread_np.h>
 #endif
+#ifdef __NetBSD__
+#  include <pthread.h>
+#  include <sched.h>
+#endif
 
 bool g_singleThreaded;
 
@@ -866,7 +870,7 @@ void addCMsgSrcAddr(struct msghdr* msgh, void* cmsgbuf, const ComboAddress* sour
     pkt->ipi6_ifindex = itfIndex;
   }
   else {
-#ifdef IP_PKTINFO
+#if defined(IP_PKTINFO)
     struct in_pktinfo *pkt;
 
     msgh->msg_control = cmsgbuf;
@@ -881,8 +885,7 @@ void addCMsgSrcAddr(struct msghdr* msgh, void* cmsgbuf, const ComboAddress* sour
     memset(pkt, 0, sizeof(*pkt));
     pkt->ipi_spec_dst = source->sin4.sin_addr;
     pkt->ipi_ifindex = itfIndex;
-#endif
-#ifdef IP_SENDSRCADDR
+#elif defined(IP_SENDSRCADDR)
     struct in_addr *in;
 
     msgh->msg_control = cmsgbuf;
@@ -1329,9 +1332,20 @@ bool isSettingThreadCPUAffinitySupported()
 int mapThreadToCPUList(pthread_t tid, const std::set<int>& cpus)
 {
 #ifdef HAVE_PTHREAD_SETAFFINITY_NP
-#  ifdef __FreeBSD__
-#    define cpu_set_t cpuset_t
-#  endif
+#  ifdef __NetBSD__
+  cpuset_t *cpuset;
+  cpuset = cpuset_create();
+  for (const auto cpuID : cpus) {
+    cpuset_set(cpuID, cpuset);
+  }
+
+  return pthread_setaffinity_np(tid,
+                                cpuset_size(cpuset),
+                                cpuset);
+#  else
+#    ifdef __FreeBSD__
+#      define cpu_set_t cpuset_t
+#    endif
   cpu_set_t cpuset;
   CPU_ZERO(&cpuset);
   for (const auto cpuID : cpus) {
@@ -1341,6 +1355,7 @@ int mapThreadToCPUList(pthread_t tid, const std::set<int>& cpus)
   return pthread_setaffinity_np(tid,
                                 sizeof(cpuset),
                                 &cpuset);
+#  endif
 #endif /* HAVE_PTHREAD_SETAFFINITY_NP */
   return ENOSYS;
 }
index 2014d5bcae6566109a492bab28ae9404253a7237..313d18f290930e963475bd8090d4ba2152f39b4f 100644 (file)
@@ -110,7 +110,7 @@ public:
       This limit applies solely to the stack, the heap is not limited in any way. If threads need to allocate a lot of data,
       the use of new/delete is suggested. 
    */
-  MTasker(size_t stacksize=8192) : d_tid(0), d_maxtid(0), d_stacksize(stacksize), d_waitstatus(Error)
+  MTasker(size_t stacksize=16*8192) : d_tid(0), d_maxtid(0), d_stacksize(stacksize), d_waitstatus(Error)
   {
     initMainStackBounds();
   }
index 2d4451f3088bc40a12d32eb9d282b8ff64977417..6f18b63d0e31d4ca9daa2df80a78fe0473ab7f69 100644 (file)
@@ -1058,8 +1058,6 @@ DNSPacket *PacketHandler::doQuestion(DNSPacket *p)
   DNSZoneRecord rr;
   SOAData sd;
 
-  // string subdomain="";
-  string soa;
   int retargetcount=0;
   set<DNSName> authSet;
 
@@ -1253,6 +1251,7 @@ DNSPacket *PacketHandler::doQuestion(DNSPacket *p)
     if(p->qtype.getCode() == QType::SOA && sd.qname==p->qdomain) {
       rr.dr.d_name=sd.qname;
       rr.dr.d_type=QType::SOA;
+      sd.serial = calculateEditSOA(sd.serial, d_dk, sd.qname);
       rr.dr.d_content=makeSOAContent(sd);
       rr.dr.d_ttl=sd.ttl;
       rr.domain_id=sd.domain_id;
@@ -1322,9 +1321,10 @@ DNSPacket *PacketHandler::doQuestion(DNSPacket *p)
 
     /* Add in SOA if required */
     if(target==sd.qname) {
+        rr.dr.d_name = sd.qname;
         rr.dr.d_type = QType::SOA;
+        sd.serial = calculateEditSOA(sd.serial, d_dk, sd.qname);
         rr.dr.d_content = makeSOAContent(sd);
-        rr.dr.d_name = sd.qname;
         rr.dr.d_ttl = sd.ttl;
         rr.domain_id = sd.domain_id;
         rr.auth = true;
@@ -1435,8 +1435,6 @@ DNSPacket *PacketHandler::doQuestion(DNSPacket *p)
       return 0;
     }
 
-    editSOA(d_dk, sd.qname, r);
-    
     for(const auto& loopRR: r->getRRS()) {
       if(loopRR.scopeMask) {
         noCache=true;
index 48e481fdcf90f868d51abe7b0e211c43bb1038a7..c2b916be69e94938622e1cb7fa1dbb9e63b76c63 100644 (file)
@@ -94,6 +94,8 @@
 
 #include "namespaces.hh"
 
+#include "xpf.hh"
+
 typedef map<ComboAddress, uint32_t, ComboAddress::addressOnlyLessThan> tcpClientCounts_t;
 
 static thread_local std::shared_ptr<RecursorLua4> t_pdl;
@@ -136,6 +138,7 @@ static vector<ComboAddress> g_localQueryAddresses4, g_localQueryAddresses6;
 static AtomicCounter counter;
 static std::shared_ptr<SyncRes::domainmap_t> g_initialDomainMap; // new threads needs this to be setup
 static std::shared_ptr<NetmaskGroup> g_initialAllowFrom; // new thread needs to be setup with this
+static NetmaskGroup g_XPFAcl;
 static size_t g_tcpMaxQueriesPerConn;
 static uint64_t g_latencyStatSize;
 static uint32_t g_disthashseed;
@@ -145,6 +148,7 @@ static unsigned int g_maxMThreads;
 static unsigned int g_numWorkerThreads;
 static int g_tcpTimeout;
 static uint16_t g_udpTruncationThreshold;
+static uint16_t g_xpfRRCode{0};
 static std::atomic<bool> statsWanted;
 static std::atomic<bool> g_quiet;
 static bool g_logCommonErrors;
@@ -167,6 +171,7 @@ uint16_t g_outgoingEDNSBufsize;
 bool g_logRPZChanges{false};
 
 #define LOCAL_NETS "127.0.0.0/8, 10.0.0.0/8, 100.64.0.0/10, 169.254.0.0/16, 192.168.0.0/16, 172.16.0.0/12, ::1/128, fc00::/7, fe80::/10"
+#define LOCAL_NETS_INVERSE "!127.0.0.0/8, !10.0.0.0/8, !100.64.0.0/10, !169.254.0.0/16, !192.168.0.0/16, !172.16.0.0/12, !::1/128, !fc00::/7, !fe80::/10"
 // Bad Nets taken from both:
 // http://www.iana.org/assignments/iana-ipv4-special-registry/iana-ipv4-special-registry.xhtml
 // and
@@ -180,10 +185,15 @@ struct DNSComboWriter {
   DNSComboWriter(const char* data, uint16_t len, const struct timeval& now) : d_mdp(true, data, len), d_now(now),
                                                                                                         d_tcp(false), d_socket(-1)
   {}
-  MOADNSParser d_mdp;
-  void setRemote(const ComboAddress* sa)
+
+  void setRemote(const ComboAddress& sa)
   {
-    d_remote=*sa;
+    d_remote=sa;
+  }
+
+  void setSource(const ComboAddress& sa)
+  {
+    d_source=sa;
   }
 
   void setLocal(const ComboAddress& sa)
@@ -191,6 +201,10 @@ struct DNSComboWriter {
     d_local=sa;
   }
 
+  void setDestination(const ComboAddress& sa)
+  {
+    d_destination=sa;
+  }
 
   void setSocket(int sock)
   {
@@ -199,11 +213,26 @@ struct DNSComboWriter {
 
   string getRemote() const
   {
-    return d_remote.toString();
+    if (d_source == d_remote) {
+      return d_source.toStringWithPort();
+    }
+    return d_source.toStringWithPort() + " (proxied by " + d_remote.toStringWithPort() + ")";
   }
 
+  MOADNSParser d_mdp;
   struct timeval d_now;
-  ComboAddress d_remote, d_local;
+  /* Remote client, might differ from d_source
+     in case of XPF, in which case d_source holds
+     the IP of the client and d_remote of the proxy
+  */
+  ComboAddress d_remote;
+  ComboAddress d_source;
+  /* Destination address, might differ from
+     d_destination in case of XPF, in which case
+     d_destination holds the IP of the proxy and
+     d_local holds our own. */
+  ComboAddress d_local;
+  ComboAddress d_destination;
 #ifdef HAVE_PROTOBUF
   boost::uuids::uuid d_uuid;
   string d_requestorId;
@@ -294,7 +323,15 @@ static void handleGenUDPQueryResponse(int fd, FDMultiplexer::funcparam_t& var)
 {
   PacketID pident=*any_cast<PacketID>(&var);
   char resp[512];
-  ssize_t ret=recv(fd, resp, sizeof(resp), 0);
+  ComboAddress fromaddr;
+  socklen_t addrlen=sizeof(fromaddr);
+
+  ssize_t ret=recvfrom(fd, resp, sizeof(resp), 0, (sockaddr *)&fromaddr, &addrlen);
+  if (fromaddr != pident.remote) {
+    L<<Logger::Notice<<"Response received from the wrong remote host ("<<fromaddr.toStringWithPort()<<" instead of "<<pident.remote.toStringWithPort()<<"), discarding"<<endl;
+
+  }
+
   t_fdm->removeReadFD(fd);
   if(ret >= 0) {
     string data(resp, (size_t) ret);
@@ -318,6 +355,7 @@ string GenUDPQueryResponse(const ComboAddress& dest, const string& query)
 
   PacketID pident;
   pident.sock=&s;
+  pident.remote=dest;
   pident.type=0;
   t_fdm->addReadFD(s.getHandle(), handleGenUDPQueryResponse, pident);
 
@@ -641,7 +679,7 @@ static void updateResponseStats(int res, const ComboAddress& remote, unsigned in
 static string makeLoginfo(DNSComboWriter* dc)
 try
 {
-  return "("+dc->d_mdp.d_qname.toLogString()+"/"+DNSRecordContent::NumberToType(dc->d_mdp.d_qtype)+" from "+(dc->d_remote.toString())+")";
+  return "("+dc->d_mdp.d_qname.toLogString()+"/"+DNSRecordContent::NumberToType(dc->d_mdp.d_qtype)+" from "+(dc->getRemote())+")";
 }
 catch(...)
 {
@@ -763,9 +801,9 @@ static void startDoResolve(void *p)
     RecProtoBufMessage pbMessage(RecProtoBufMessage::Response);
 #ifdef HAVE_PROTOBUF
     if (luaconfsLocal->protobufServer) {
-      Netmask requestorNM(dc->d_remote, dc->d_remote.sin4.sin_family == AF_INET ? luaconfsLocal->protobufMaskV4 : luaconfsLocal->protobufMaskV6);
+      Netmask requestorNM(dc->d_source, dc->d_source.sin4.sin_family == AF_INET ? luaconfsLocal->protobufMaskV4 : luaconfsLocal->protobufMaskV6);
       const ComboAddress& requestor = requestorNM.getMaskedNetwork();
-      pbMessage.update(dc->d_uuid, &requestor, &dc->d_local, dc->d_tcp, dc->d_mdp.d_header.id);
+      pbMessage.update(dc->d_uuid, &requestor, &dc->d_destination, dc->d_tcp, dc->d_mdp.d_header.id);
       pbMessage.setEDNSSubnet(dc->d_ednssubnet.source, dc->d_ednssubnet.source.isIpv4() ? luaconfsLocal->protobufMaskV4 : luaconfsLocal->protobufMaskV6);
       pbMessage.setQuestion(dc->d_mdp.d_qname, dc->d_mdp.d_qtype, dc->d_mdp.d_qclass);
     }
@@ -789,7 +827,6 @@ static void startDoResolve(void *p)
     if(t_pdl) {
       sr.setLuaEngine(t_pdl);
     }
-    sr.d_requestor=dc->d_remote; // ECS needs this too
     if(g_dnssecmode != DNSSECMode::Off) {
       sr.setDoDNSSEC(true);
 
@@ -808,12 +845,7 @@ static void startDoResolve(void *p)
     sr.setInitialRequestId(dc->d_uuid);
 #endif
 
-    if (g_useIncomingECS) {
-      sr.setIncomingECSFound(dc->d_ecsFound);
-      if (dc->d_ecsFound) {
-        sr.setIncomingECS(dc->d_ednssubnet);
-      }
-    }
+    sr.setQuerySource(dc->d_remote, g_useIncomingECS && !dc->d_ednssubnet.source.empty() ? boost::optional<const EDNSSubnetOpts&>(dc->d_ednssubnet) : boost::none);
 
     bool tracedQuery=false; // we could consider letting Lua know about this too
     bool variableAnswer = false;
@@ -823,7 +855,7 @@ static void startDoResolve(void *p)
     int res = RCode::NoError;
     DNSFilterEngine::Policy appliedPolicy;
     DNSRecord spoofed;
-    RecursorLua4::DNSQuestion dq(dc->d_remote, dc->d_local, dc->d_mdp.d_qname, dc->d_mdp.d_qtype, dc->d_tcp, variableAnswer, wantsRPZ);
+    RecursorLua4::DNSQuestion dq(dc->d_source, dc->d_destination, dc->d_mdp.d_qname, dc->d_mdp.d_qtype, dc->d_tcp, variableAnswer, wantsRPZ);
     dq.ednsFlags = &edo.d_Z;
     dq.ednsOptions = &dc->d_ednsOpts;
     dq.tag = dc->d_tag;
@@ -870,7 +902,7 @@ static void startDoResolve(void *p)
 
     // Check if the query has a policy attached to it
     if (wantsRPZ) {
-      appliedPolicy = luaconfsLocal->dfe.getQueryPolicy(dc->d_mdp.d_qname, dc->d_remote, sr.d_discardedPolicies);
+      appliedPolicy = luaconfsLocal->dfe.getQueryPolicy(dc->d_mdp.d_qname, dc->d_source, sr.d_discardedPolicies);
     }
 
     // if there is a RecursorLua active, and it 'took' the query in preResolve, we don't launch beginResolve
@@ -1059,14 +1091,14 @@ static void startDoResolve(void *p)
       if(!shouldNotValidate && sr.isDNSSECValidationRequested()) {
         try {
           if(sr.doLog()) {
-            L<<Logger::Warning<<"Starting validation of answer to "<<dc->d_mdp.d_qname<<"|"<<QType(dc->d_mdp.d_qtype).getName()<<" for "<<dc->d_remote.toStringWithPort()<<endl;
+            L<<Logger::Warning<<"Starting validation of answer to "<<dc->d_mdp.d_qname<<"|"<<QType(dc->d_mdp.d_qtype).getName()<<" for "<<dc->getRemote()<<endl;
           }
 
           auto state = sr.getValidationState();
 
           if(state == Secure) {
             if(sr.doLog()) {
-              L<<Logger::Warning<<"Answer to "<<dc->d_mdp.d_qname<<"|"<<QType(dc->d_mdp.d_qtype).getName()<<" for "<<dc->d_remote.toStringWithPort()<<" validates correctly"<<endl;
+              L<<Logger::Warning<<"Answer to "<<dc->d_mdp.d_qname<<"|"<<QType(dc->d_mdp.d_qtype).getName()<<" for "<<dc->getRemote()<<" validates correctly"<<endl;
             }
             
             // Is the query source interested in the value of the ad-bit?
@@ -1075,14 +1107,14 @@ static void startDoResolve(void *p)
           }
           else if(state == Insecure) {
             if(sr.doLog()) {
-              L<<Logger::Warning<<"Answer to "<<dc->d_mdp.d_qname<<"|"<<QType(dc->d_mdp.d_qtype).getName()<<" for "<<dc->d_remote.toStringWithPort()<<" validates as Insecure"<<endl;
+              L<<Logger::Warning<<"Answer to "<<dc->d_mdp.d_qname<<"|"<<QType(dc->d_mdp.d_qtype).getName()<<" for "<<dc->getRemote()<<" validates as Insecure"<<endl;
             }
             
             pw.getHeader()->ad=0;
           }
           else if(state == Bogus) {
             if(g_dnssecLogBogus || sr.doLog() || g_dnssecmode == DNSSECMode::ValidateForLog) {
-              L<<Logger::Warning<<"Answer to "<<dc->d_mdp.d_qname<<"|"<<QType(dc->d_mdp.d_qtype).getName()<<" for "<<dc->d_remote.toStringWithPort()<<" validates as Bogus"<<endl;
+              L<<Logger::Warning<<"Answer to "<<dc->d_mdp.d_qname<<"|"<<QType(dc->d_mdp.d_qtype).getName()<<" for "<<dc->getRemote()<<" validates as Bogus"<<endl;
             }
             
             // Does the query or validation mode sending out a SERVFAIL on validation errors?
@@ -1110,7 +1142,7 @@ static void startDoResolve(void *p)
 
       if(ret.size()) {
         orderAndShuffle(ret);
-       if(auto sl = luaconfsLocal->sortlist.getOrderCmp(dc->d_remote)) {
+       if(auto sl = luaconfsLocal->sortlist.getOrderCmp(dc->d_source)) {
          stable_sort(ret.begin(), ret.end(), *sl);
          variableAnswer=true;
        }
@@ -1162,7 +1194,7 @@ static void startDoResolve(void *p)
     }
 
     g_rs.submitResponse(dc->d_mdp.d_qtype, packet.size(), !dc->d_tcp);
-    updateResponseStats(res, dc->d_remote, packet.size(), &dc->d_mdp.d_qname, dc->d_mdp.d_qtype);
+    updateResponseStats(res, dc->d_source, packet.size(), &dc->d_mdp.d_qname, dc->d_mdp.d_qtype);
 #ifdef HAVE_PROTOBUF
     if (luaconfsLocal->protobufServer && (!luaconfsLocal->protobufTaggedOnly || (appliedPolicy.d_name && !appliedPolicy.d_name->empty()) || !dc->d_policyTags.empty())) {
       pbMessage.setBytes(packet.size());
@@ -1189,7 +1221,7 @@ static void startDoResolve(void *p)
        addCMsgSrcAddr(&msgh, cbuf, &dc->d_local, 0);
       }
       if(sendmsg(dc->d_socket, &msgh, 0) < 0 && g_logCommonErrors) 
-        L<<Logger::Warning<<"Sending UDP reply to client "<<dc->d_remote.toStringWithPort()<<" failed with: "<<strerror(errno)<<endl;
+        L<<Logger::Warning<<"Sending UDP reply to client "<<dc->getRemote()<<" failed with: "<<strerror(errno)<<endl;
       if(!SyncRes::s_nopacketcache && !variableAnswer && !sr.wasVariable() ) {
         t_packetCache->insertResponsePacket(dc->d_tag, dc->d_qhash, dc->d_mdp.d_qname, dc->d_mdp.d_qtype, dc->d_mdp.d_qclass,
                                             string((const char*)&*packet.begin(), packet.size()),
@@ -1358,48 +1390,75 @@ static void makeControlChannelSocket(int processNum=-1)
   }
 }
 
-static bool getQNameAndSubnet(const std::string& question, DNSName* dnsname, uint16_t* qtype, uint16_t* qclass, EDNSSubnetOpts* ednssubnet, std::map<uint16_t, EDNSOptionView>* options)
+static void getQNameAndSubnet(const std::string& question, DNSName* dnsname, uint16_t* qtype, uint16_t* qclass,
+                              bool& foundECS, EDNSSubnetOpts* ednssubnet, std::map<uint16_t, EDNSOptionView>* options,
+                              bool& foundXPF, ComboAddress* xpfSource, ComboAddress* xpfDest)
 {
-  bool found = false;
-  const struct dnsheader* dh = (struct dnsheader*)question.c_str();
+  const bool lookForXPF = xpfSource != nullptr && g_xpfRRCode != 0;
+  const bool lookForECS = ednssubnet != nullptr;
+  const struct dnsheader* dh = reinterpret_cast<const struct dnsheader*>(question.c_str());
   size_t questionLen = question.length();
   unsigned int consumed=0;
   *dnsname=DNSName(question.c_str(), questionLen, sizeof(dnsheader), false, qtype, qclass, &consumed);
 
   size_t pos= sizeof(dnsheader)+consumed+4;
-  /* at least OPT root label (1), type (2), class (2) and ttl (4) + OPT RR rdlen (2)
-     = 11 */
-  if(ntohs(dh->arcount) == 1 && questionLen > pos + 11) { // this code can extract one (1) EDNS Subnet option
+  const size_t headerSize = /* root */ 1 + sizeof(dnsrecordheader);
+  const uint16_t arcount = ntohs(dh->arcount);
+
+  for (uint16_t arpos = 0; arpos < arcount && questionLen > (pos + headerSize) && ((lookForECS && !foundECS) || (lookForXPF && !foundXPF)); arpos++) {
+    if (question.at(pos) != 0) {
+      /* not an OPT or a XPF, bye. */
+      return;
+    }
+
+    pos += 1;
+    const dnsrecordheader* drh = reinterpret_cast<const dnsrecordheader*>(&question.at(pos));
+    pos += sizeof(dnsrecordheader);
+
+    if (pos >= questionLen) {
+      return;
+    }
+
     /* OPT root label (1) followed by type (2) */
-    if(question.at(pos)==0 && question.at(pos+1)==0 && question.at(pos+2)==QType::OPT) {
+    if(lookForECS && ntohs(drh->d_type) == QType::OPT) {
       if (!options) {
         char* ecsStart = nullptr;
         size_t ecsLen = 0;
-        int res = getEDNSOption((char*)question.c_str()+pos+9, questionLen - pos - 9, EDNSOptionCode::ECS, &ecsStart, &ecsLen);
+        /* we need to pass the record len */
+        int res = getEDNSOption(const_cast<char*>(reinterpret_cast<const char*>(&question.at(pos - sizeof(drh->d_clen)))), questionLen - pos + sizeof(drh->d_clen), EDNSOptionCode::ECS, &ecsStart, &ecsLen);
         if (res == 0 && ecsLen > 4) {
           EDNSSubnetOpts eso;
           if(getEDNSSubnetOptsFromString(ecsStart + 4, ecsLen - 4, &eso)) {
             *ednssubnet=eso;
-            found = true;
+            foundECS = true;
           }
         }
       }
       else {
-        int res = getEDNSOptions((char*)question.c_str()+pos+9, questionLen - pos - 9, *options);
+        /* we need to pass the record len */
+        int res = getEDNSOptions(reinterpret_cast<const char*>(&question.at(pos -sizeof(drh->d_clen))), questionLen - pos + (sizeof(drh->d_clen)), *options);
         if (res == 0) {
           const auto& it = options->find(EDNSOptionCode::ECS);
           if (it != options->end() && it->second.content != nullptr && it->second.size > 0) {
             EDNSSubnetOpts eso;
             if(getEDNSSubnetOptsFromString(it->second.content, it->second.size, &eso)) {
               *ednssubnet=eso;
-              found = true;
+              foundECS = true;
             }
           }
         }
       }
     }
+    else if (lookForXPF && ntohs(drh->d_type) == g_xpfRRCode && ntohs(drh->d_class) == QClass::IN && drh->d_ttl == 0) {
+      if ((questionLen - pos) < ntohs(drh->d_clen)) {
+        return;
+      }
+
+      foundXPF = parseXPFPayload(reinterpret_cast<const char*>(&question.at(pos)), ntohs(drh->d_clen), *xpfSource, xpfDest);
+    }
+
+    pos += ntohs(drh->d_clen);
   }
-  return found;
 }
 
 static void handleRunningTCPQuestion(int fd, FDMultiplexer::funcparam_t& var)
@@ -1429,7 +1488,7 @@ static void handleRunningTCPQuestion(int fd, FDMultiplexer::funcparam_t& var)
     }
     if(!bytes || bytes < 0) {
       if(g_logCommonErrors)
-        L<<Logger::Error<<"TCP client "<< conn->d_remote.toString() <<" disconnected after first byte"<<endl;
+        L<<Logger::Error<<"TCP client "<< conn->d_remote.toStringWithPort() <<" disconnected after first byte"<<endl;
       t_fdm->removeReadFD(fd);
       return;
     }
@@ -1437,7 +1496,7 @@ static void handleRunningTCPQuestion(int fd, FDMultiplexer::funcparam_t& var)
   else if(conn->state==TCPConnection::GETQUESTION) {
     ssize_t bytes=recv(conn->getFD(), conn->data + conn->bytesread, conn->qlen - conn->bytesread, 0);
     if(!bytes || bytes < 0 || bytes > std::numeric_limits<std::uint16_t>::max()) {
-      L<<Logger::Error<<"TCP client "<< conn->d_remote.toString() <<" disconnected while reading question body"<<endl;
+      L<<Logger::Error<<"TCP client "<< conn->d_remote.toStringWithPort() <<" disconnected while reading question body"<<endl;
       t_fdm->removeReadFD(fd);
       return;
     }
@@ -1452,23 +1511,26 @@ static void handleRunningTCPQuestion(int fd, FDMultiplexer::funcparam_t& var)
       catch(MOADNSException &mde) {
         g_stats.clientParseError++;
         if(g_logCommonErrors)
-          L<<Logger::Error<<"Unable to parse packet from TCP client "<< conn->d_remote.toString() <<endl;
+          L<<Logger::Error<<"Unable to parse packet from TCP client "<< conn->d_remote.toStringWithPort() <<endl;
         return;
       }
       dc->d_tcpConnection = conn; // carry the torch
       dc->setSocket(conn->getFD()); // this is the only time a copy is made of the actual fd
       dc->d_tcp=true;
-      dc->setRemote(&conn->d_remote);
+      dc->setRemote(conn->d_remote);
+      dc->setSource(conn->d_remote);
       ComboAddress dest;
       memset(&dest, 0, sizeof(dest));
       dest.sin4.sin_family = conn->d_remote.sin4.sin_family;
       socklen_t len = dest.getSocklen();
       getsockname(conn->getFD(), (sockaddr*)&dest, &len); // if this fails, we're ok with it
       dc->setLocal(dest);
+      dc->setDestination(dest);
       DNSName qname;
       uint16_t qtype=0;
       uint16_t qclass=0;
       bool needECS = false;
+      bool needXPF = g_XPFAcl.match(conn->d_remote);
       string requestorId;
       string deviceId;
 #ifdef HAVE_PROTOBUF
@@ -1478,16 +1540,20 @@ static void handleRunningTCPQuestion(int fd, FDMultiplexer::funcparam_t& var)
       }
 #endif
 
-      if(needECS || (t_pdl && t_pdl->d_gettag)) {
+      if(needECS || needXPF || (t_pdl && t_pdl->d_gettag)) {
 
         try {
           std::map<uint16_t, EDNSOptionView> ednsOptions;
+          bool xpfFound = false;
           dc->d_ecsParsed = true;
-          dc->d_ecsFound = getQNameAndSubnet(std::string(conn->data, conn->qlen), &qname, &qtype, &qclass, &dc->d_ednssubnet, g_gettagNeedsEDNSOptions ? &ednsOptions : nullptr);
+          dc->d_ecsFound = false;
+          getQNameAndSubnet(std::string(conn->data, conn->qlen), &qname, &qtype, &qclass,
+                            dc->d_ecsFound, &dc->d_ednssubnet, g_gettagNeedsEDNSOptions ? &ednsOptions : nullptr,
+                            xpfFound, needXPF ? &dc->d_source : nullptr, needXPF ? &dc->d_destination : nullptr);
 
           if(t_pdl && t_pdl->d_gettag) {
             try {
-              dc->d_tag = t_pdl->gettag(conn->d_remote, dc->d_ednssubnet.source, dest, qname, qtype, &dc->d_policyTags, dc->d_data, ednsOptions, true, requestorId, deviceId);
+              dc->d_tag = t_pdl->gettag(dc->d_source, dc->d_ednssubnet.source, dc->d_destination, qname, qtype, &dc->d_policyTags, dc->d_data, ednsOptions, true, requestorId, deviceId);
             }
             catch(std::exception& e)  {
               if(g_logCommonErrors)
@@ -1513,7 +1579,7 @@ static void handleRunningTCPQuestion(int fd, FDMultiplexer::funcparam_t& var)
           const struct dnsheader* dh = (const struct dnsheader*) conn->data;
 
           if (!luaconfsLocal->protobufTaggedOnly) {
-            protobufLogQuery(luaconfsLocal->protobufServer, luaconfsLocal->protobufMaskV4, luaconfsLocal->protobufMaskV6, dc->d_uuid, conn->d_remote, dest, dc->d_ednssubnet.source, true, dh->id, conn->qlen, qname, qtype, qclass, dc->d_policyTags, dc->d_requestorId, dc->d_deviceId);
+            protobufLogQuery(luaconfsLocal->protobufServer, luaconfsLocal->protobufMaskV4, luaconfsLocal->protobufMaskV6, dc->d_uuid, dc->d_source, dc->d_destination, dc->d_ednssubnet.source, true, dh->id, conn->qlen, qname, qtype, qclass, dc->d_policyTags, dc->d_requestorId, dc->d_deviceId);
           }
         }
         catch(std::exception& e) {
@@ -1523,15 +1589,15 @@ static void handleRunningTCPQuestion(int fd, FDMultiplexer::funcparam_t& var)
       }
 #endif
       if(dc->d_mdp.d_header.qr) {
-        delete dc;
         g_stats.ignoredCount++;
-        L<<Logger::Error<<"Ignoring answer from TCP client "<< conn->d_remote.toString() <<" on server socket!"<<endl;
+        L<<Logger::Error<<"Ignoring answer from TCP client "<< dc->getRemote() <<" on server socket!"<<endl;
+        delete dc;
         return;
       }
       if(dc->d_mdp.d_header.opcode) {
-        delete dc;
         g_stats.ignoredCount++;
-        L<<Logger::Error<<"Ignoring non-query opcode from TCP client "<< conn->d_remote.toString() <<" on server socket!"<<endl;
+        L<<Logger::Error<<"Ignoring non-query opcode from TCP client "<< dc->getRemote() <<" on server socket!"<<endl;
+        delete dc;
         return;
       }
       else {
@@ -1620,8 +1686,11 @@ static string* doProcessUDPQuestion(const std::string& question, const ComboAddr
   unsigned int ctag=0;
   uint32_t qhash = 0;
   bool needECS = false;
+  bool needXPF = g_XPFAcl.match(fromaddr);
   std::vector<std::string> policyTags;
   LuaContext::LuaObject data;
+  ComboAddress source = fromaddr;
+  ComboAddress destination = destaddr;
   string requestorId;
   string deviceId;
 #ifdef HAVE_PROTOBUF
@@ -1655,16 +1724,23 @@ static string* doProcessUDPQuestion(const std::string& question, const ComboAddr
     */
 #endif
 
-    if(needECS || (t_pdl && t_pdl->d_gettag)) {
+    if(needECS || needXPF || (t_pdl && t_pdl->d_gettag)) {
       try {
         std::map<uint16_t, EDNSOptionView> ednsOptions;
-        ecsFound = getQNameAndSubnet(question, &qname, &qtype, &qclass, &ednssubnet, g_gettagNeedsEDNSOptions ? &ednsOptions : nullptr);
+        bool xpfFound = false;
+
+        ecsFound = false;
+
+        getQNameAndSubnet(question, &qname, &qtype, &qclass,
+                          ecsFound, &ednssubnet, g_gettagNeedsEDNSOptions ? &ednsOptions : nullptr,
+                          xpfFound, needXPF ? &source : nullptr, needXPF ? &destination : nullptr);
+
         qnameParsed = true;
         ecsParsed = true;
 
         if(t_pdl && t_pdl->d_gettag) {
           try {
-            ctag=t_pdl->gettag(fromaddr, ednssubnet.source, destaddr, qname, qtype, &policyTags, data, ednsOptions, false, requestorId, deviceId);
+            ctag=t_pdl->gettag(source, ednssubnet.source, destination, qname, qtype, &policyTags, data, ednsOptions, false, requestorId, deviceId);
           }
           catch(std::exception& e)  {
             if(g_logCommonErrors)
@@ -1684,7 +1760,7 @@ static string* doProcessUDPQuestion(const std::string& question, const ComboAddr
 #ifdef HAVE_PROTOBUF
     if(luaconfsLocal->protobufServer) {
       if (!luaconfsLocal->protobufTaggedOnly || !policyTags.empty()) {
-        protobufLogQuery(luaconfsLocal->protobufServer, luaconfsLocal->protobufMaskV4, luaconfsLocal->protobufMaskV6, uniqueId, fromaddr, destaddr, ednssubnet.source, false, dh->id, question.size(), qname, qtype, qclass, policyTags, requestorId, deviceId);
+        protobufLogQuery(luaconfsLocal->protobufServer, luaconfsLocal->protobufMaskV4, luaconfsLocal->protobufMaskV6, uniqueId, source, destination, ednssubnet.source, false, dh->id, question.size(), qname, qtype, qclass, policyTags, requestorId, deviceId);
       }
     }
 #endif /* HAVE_PROTOBUF */
@@ -1699,9 +1775,9 @@ static string* doProcessUDPQuestion(const std::string& question, const ComboAddr
     if (cacheHit) {
 #ifdef HAVE_PROTOBUF
       if(luaconfsLocal->protobufServer && (!luaconfsLocal->protobufTaggedOnly || !pbMessage.getAppliedPolicy().empty() || !pbMessage.getPolicyTags().empty())) {
-        Netmask requestorNM(fromaddr, fromaddr.sin4.sin_family == AF_INET ? luaconfsLocal->protobufMaskV4 : luaconfsLocal->protobufMaskV6);
+        Netmask requestorNM(source, source.sin4.sin_family == AF_INET ? luaconfsLocal->protobufMaskV4 : luaconfsLocal->protobufMaskV6);
         const ComboAddress& requestor = requestorNM.getMaskedNetwork();
-        pbMessage.update(uniqueId, &requestor, &destaddr, false, dh->id);
+        pbMessage.update(uniqueId, &requestor, &destination, false, dh->id);
         pbMessage.setEDNSSubnet(ednssubnet.source, ednssubnet.source.isIpv4() ? luaconfsLocal->protobufMaskV4 : luaconfsLocal->protobufMaskV6);
         pbMessage.setQueryTime(g_now.tv_sec, g_now.tv_usec);
         pbMessage.setRequestorId(requestorId);
@@ -1710,7 +1786,7 @@ static string* doProcessUDPQuestion(const std::string& question, const ComboAddr
       }
 #endif /* HAVE_PROTOBUF */
       if(!g_quiet)
-        L<<Logger::Notice<<t_id<< " question answered from packet cache tag="<<ctag<<" from "<<fromaddr.toString()<<endl;
+        L<<Logger::Notice<<t_id<< " question answered from packet cache tag="<<ctag<<" from "<<source.toStringWithPort()<<(source != fromaddr ? " (via "+fromaddr.toStringWithPort()+")" : "")<<endl;
 
       g_stats.packetCacheHits++;
       SyncRes::s_queries++;
@@ -1725,12 +1801,12 @@ static string* doProcessUDPQuestion(const std::string& question, const ComboAddr
        addCMsgSrcAddr(&msgh, cbuf, &destaddr, 0);
       }
       if(sendmsg(fd, &msgh, 0) < 0 && g_logCommonErrors)
-        L<<Logger::Warning<<"Sending UDP reply to client "<<fromaddr.toStringWithPort()<<" failed with: "<<strerror(errno)<<endl;
+        L<<Logger::Warning<<"Sending UDP reply to client "<<source.toStringWithPort()<<(source != fromaddr ? " (via "+fromaddr.toStringWithPort()+")" : "")<<" failed with: "<<strerror(errno)<<endl;
 
       if(response.length() >= sizeof(struct dnsheader)) {
         struct dnsheader tmpdh;
         memcpy(&tmpdh, response.c_str(), sizeof(tmpdh));
-        updateResponseStats(tmpdh.rcode, fromaddr, response.length(), 0, 0);
+        updateResponseStats(tmpdh.rcode, source, response.length(), 0, 0);
       }
       g_stats.avgLatencyUsec=(1-1.0/g_latencyStatSize)*g_stats.avgLatencyUsec + 0.0; // we assume 0 usec
       g_stats.avgLatencyOursUsec=(1-1.0/g_latencyStatSize)*g_stats.avgLatencyOursUsec + 0.0; // we assume 0 usec
@@ -1743,9 +1819,9 @@ static string* doProcessUDPQuestion(const std::string& question, const ComboAddr
   }
 
   if(t_pdl) {
-    if(t_pdl->ipfilter(fromaddr, destaddr, *dh)) {
+    if(t_pdl->ipfilter(source, destination, *dh)) {
       if(!g_quiet)
-       L<<Logger::Notice<<t_id<<" ["<<MT->getTid()<<"/"<<MT->numProcesses()<<"] DROPPED question from "<<fromaddr.toStringWithPort()<<" based on policy"<<endl;
+       L<<Logger::Notice<<t_id<<" ["<<MT->getTid()<<"/"<<MT->numProcesses()<<"] DROPPED question from "<<source.toStringWithPort()<<(source != fromaddr ? " (via "+fromaddr.toStringWithPort()+")" : "")<<" based on policy"<<endl;
       g_stats.policyDrops++;
       return 0;
     }
@@ -1753,7 +1829,7 @@ static string* doProcessUDPQuestion(const std::string& question, const ComboAddr
 
   if(MT->numProcesses() > g_maxMThreads) {
     if(!g_quiet)
-      L<<Logger::Notice<<t_id<<" ["<<MT->getTid()<<"/"<<MT->numProcesses()<<"] DROPPED question from "<<fromaddr.toStringWithPort()<<", over capacity"<<endl;
+      L<<Logger::Notice<<t_id<<" ["<<MT->getTid()<<"/"<<MT->numProcesses()<<"] DROPPED question from "<<source.toStringWithPort()<<(source != fromaddr ? " (via "+fromaddr.toStringWithPort()+")" : "")<<", over capacity"<<endl;
 
     g_stats.overCapacityDrops++;
     return 0;
@@ -1764,8 +1840,10 @@ static string* doProcessUDPQuestion(const std::string& question, const ComboAddr
   dc->d_tag=ctag;
   dc->d_qhash=qhash;
   dc->d_query = question;
-  dc->setRemote(&fromaddr);
+  dc->setRemote(fromaddr);
+  dc->setSource(source);
   dc->setLocal(destaddr);
+  dc->setDestination(destination);
   dc->d_tcp=false;
   dc->d_policyTags = policyTags;
   dc->d_data = data;
@@ -1922,7 +2000,7 @@ static void makeTCPServerSockets(unsigned int threadId)
     }
 
 #ifdef TCP_DEFER_ACCEPT
-    if(setsockopt(fd, SOL_TCP, TCP_DEFER_ACCEPT, &tmp, sizeof tmp) >= 0) {
+    if(setsockopt(fd, IPPROTO_TCP, TCP_DEFER_ACCEPT, &tmp, sizeof tmp) >= 0) {
       if(i==locals.begin())
         L<<Logger::Error<<"Enabled TCP data-ready filter for (slight) DoS protection"<<endl;
     }
@@ -2991,6 +3069,13 @@ static int serviceMain(int argc, char*argv[])
     }
   }
 
+  SyncRes::parseEDNSSubnetWhitelist(::arg()["edns-subnet-whitelist"]);
+  SyncRes::parseEDNSSubnetAddFor(::arg()["ecs-add-for"]);
+  g_useIncomingECS = ::arg().mustDo("use-incoming-edns-subnet");
+
+  g_XPFAcl.toMasks(::arg()["xpf-allow-from"]);
+  g_xpfRRCode = ::arg().asNum("xpf-rr-code");
+
   g_networkTimeoutMsec = ::arg().asNum("network-timeout");
 
   g_initialDomainMap = parseAuthAndForwards();
@@ -3035,9 +3120,6 @@ static int serviceMain(int argc, char*argv[])
     makeTCPServerSockets(0);
   }
 
-  SyncRes::parseEDNSSubnetWhitelist(::arg()["edns-subnet-whitelist"]);
-  g_useIncomingECS = ::arg().mustDo("use-incoming-edns-subnet");
-
   int forks;
   for(forks = 0; forks < ::arg().asNum("processes") - 1; ++forks) {
     if(!fork()) // we are child
@@ -3246,7 +3328,7 @@ try
       for(expired_t::iterator i=expired.begin() ; i != expired.end(); ++i) {
         shared_ptr<TCPConnection> conn=any_cast<shared_ptr<TCPConnection> >(i->second);
         if(g_logCommonErrors)
-          L<<Logger::Warning<<"Timeout from remote TCP client "<< conn->d_remote.toString() <<endl;
+          L<<Logger::Warning<<"Timeout from remote TCP client "<< conn->d_remote.toStringWithPort() <<endl;
         t_fdm->removeReadFD(i->first);
       }
     }
@@ -3396,6 +3478,7 @@ int main(int argc, char **argv)
     ::arg().set("ecs-ipv4-bits", "Number of bits of IPv4 address to pass for EDNS Client Subnet")="24";
     ::arg().set("ecs-ipv6-bits", "Number of bits of IPv6 address to pass for EDNS Client Subnet")="56";
     ::arg().set("edns-subnet-whitelist", "List of netmasks and domains that we should enable EDNS subnet for")="";
+    ::arg().set("ecs-add-for", "List of client netmasks for which EDNS Client Subnet will be added")="0.0.0.0/0, ::/0, " LOCAL_NETS_INVERSE;
     ::arg().set("ecs-scope-zero-address", "Address to send to whitelisted authoritative servers for incoming queries with ECS prefix-length source of 0")="";
     ::arg().setSwitch( "use-incoming-edns-subnet", "Pass along received EDNS Client Subnet information")="no";
     ::arg().setSwitch( "pdns-distributes-queries", "If PowerDNS itself should distribute queries over threads")="yes";
@@ -3425,6 +3508,9 @@ int main(int argc, char **argv)
 
     ::arg().setSwitch("log-rpz-changes", "Log additions and removals to RPZ zones at Info level")="no";
 
+    ::arg().set("xpf-allow-from","XPF information is only processed from these subnets")="";
+    ::arg().set("xpf-rr-code","XPF option code to use")="0";
+
     ::arg().setCmd("help","Provide a helpful message");
     ::arg().setCmd("version","Print version string");
     ::arg().setCmd("config","Output blank configuration");
@@ -3438,6 +3524,11 @@ int main(int argc, char **argv)
     }
     cleanSlashes(configname);
 
+    if(!::arg().getCommands().empty()) {
+      cerr<<"Fatal: non-option on the command line, perhaps a '--setting=123' statement missed the '='?"<<endl;
+      exit(99);
+    }
+
     if(::arg().mustDo("config")) {
       cout<<::arg().configstring()<<endl;
       exit(0);
index 1d677f445befcc3b69c2b85891101ecb978747c9..f4e37ab624d435ebafb89853e112c2e94d85b78c 100644 (file)
@@ -121,7 +121,7 @@ void loadMainConfig(const std::string& configdir)
 
   // Keep this line below all ::arg().set() statements
   if (! ::arg().laxFile(configname.c_str()))
-    cerr<<"Warning: unable to read configuration file '"<<configname<<"'."<<endl;
+    cerr<<"Warning: unable to read configuration file '"<<configname<<"': "<<strerror(errno)<<endl;
 
   seedRandom(::arg()["entropy-source"]);
 
@@ -586,53 +586,16 @@ int increaseSerial(const DNSName& zone, DNSSECKeeper &dk)
     cerr<<"Serial increase of presigned zone '"<<zone<<"' is not allowed."<<endl;
     return -1;
   }
-  
+
   string soaEditKind;
   dk.getSoaEdit(zone, soaEditKind);
 
-  sd.db->lookup(QType(QType::SOA), zone);
-  vector<DNSResourceRecord> rrs;
   DNSResourceRecord rr;
-  DNSZoneRecord szr;
-  while (sd.db->get(rr)) {
-    if (rr.qtype.getCode() == QType::SOA) {
-      rrs.push_back(rr);
-      szr.dr=DNSRecord(rr) ;
-    }
-  } 
-
-  if (rrs.size() > 1) {
-    cerr<<rrs.size()<<" SOA records found for "<<zone<<"!"<<endl;
-    return -1;
-  }
-  if (rrs.size() < 1) {
-     cerr<<zone<<" not found!"<<endl;
-  }
-  
-  if (soaEditKind.empty()) {
-    sd.serial++;
-  }
-  else if(pdns_iequals(soaEditKind,"INCREMENT-WEEKS")) {
-    sd.serial++;
-  }
-  else if(pdns_iequals(soaEditKind,"INCEPTION-INCREMENT")) {
-    uint32_t today_serial = localtime_format_YYYYMMDDSS(time(NULL), 1);
-
-    if (sd.serial < today_serial) {
-      sd.serial = today_serial;
-    }
-    else {
-      sd.serial++;
-    }
-  }
-  else {
-    sd.serial = calculateEditSOA(szr, soaEditKind) + 1;
-  }
-  rrs[0].content = serializeSOAData(sd);
+  makeIncreasedSOARecord(sd, "SOA-EDIT-INCREASE", soaEditKind, rr);
 
   sd.db->startTransaction(zone, -1);
 
-  if (! sd.db->replaceRRSet(sd.domain_id, zone, rr.qtype, rrs)) {
+  if (!sd.db->replaceRRSet(sd.domain_id, zone, rr.qtype, vector<DNSResourceRecord>(1, rr))) {
    sd.db->abortTransaction();
    cerr<<"Backend did not replace SOA record. Backend might not support this operation."<<endl;
    return -1;
@@ -650,8 +613,8 @@ int increaseSerial(const DNSName& zone, DNSSECKeeper &dk)
     } else
       ordername=zone;
     if(g_verbose)
-      cerr<<"'"<<rrs[0].qname<<"' -> '"<< ordername <<"'"<<endl;
-    sd.db->updateDNSSECOrderNameAndAuth(sd.domain_id, rrs[0].qname, ordername, true);
+      cerr<<"'"<<rr.qname<<"' -> '"<< ordername <<"'"<<endl;
+    sd.db->updateDNSSECOrderNameAndAuth(sd.domain_id, rr.qname, ordername, true);
   }
 
   sd.db->commitTransaction();
@@ -1045,7 +1008,7 @@ int createZone(const DNSName &zone, const DNSName& nsname) {
   ).str();
   SOAData sd;
   fillSOAData(soa, sd);  // fills out default values for us
-  rr.content = DNSRecordContent::mastermake(rr.qtype.getCode(), 1, serializeSOAData(sd))->getZoneRepresentation(true);
+  rr.content = makeSOAContent(sd)->getZoneRepresentation(true);
   rr.domain_id = di.id;
   di.backend->startTransaction(zone, di.id);
   di.backend->feedRecord(rr, DNSName());
index c31579f76fa31e0c8454c32a83da681d1d864e02..2797955defc1d473db66f603a411bc33cf50a23e 100644 (file)
 #include "misc.hh"
 #include "utility.hh"
 #include <boost/algorithm/string.hpp>
+#include <boost/algorithm/string/classification.hpp>
+
 #include <iostream>
 #include "base32.hh"
 #include "base64.hh"
 #include "namespaces.hh"
 
-RecordTextReader::RecordTextReader(const string& str, const string& zone) : d_string(str), d_zone(zone), d_pos(0), d_end(str.size())
+RecordTextReader::RecordTextReader(const string& str, const string& zone) : d_string(str), d_zone(zone), d_pos(0)
 {
+   /* remove whitespace */
+   boost::trim_if(d_string, boost::algorithm::is_space());
+   d_end = d_string.size();
 }
 
 void RecordTextReader::xfr48BitInt(uint64_t &val)
@@ -175,6 +180,28 @@ void RecordTextReader::xfrIP6(std::string &val)
   d_pos += len;
 }
 
+void RecordTextReader::xfrCAWithoutPort(uint8_t version, ComboAddress &val)
+{
+  if (version == 4) {
+    uint32_t ip;
+    xfrIP(ip);
+    val = makeComboAddressFromRaw(4, string((const char*) &ip, 4));
+  }
+  else if (version == 6) {
+    string ip;
+    xfrIP6(ip);
+    val = makeComboAddressFromRaw(6, ip);
+  }
+  else throw RecordTextException("invalid address family");
+}
+
+void RecordTextReader::xfrCAPort(ComboAddress &val)
+{
+  uint16_t port;
+  xfr16BitInt(port);
+  val.sin4.sin_port = port;
+}
+
 bool RecordTextReader::eof()
 {
   return d_pos==d_end;
@@ -501,6 +528,21 @@ void RecordTextWriter::xfrIP6(const std::string& val)
   d_string += std::string(addrbuf);
 }
 
+void RecordTextWriter::xfrCAWithoutPort(uint8_t version, ComboAddress &val)
+{
+  string ip = val.toString();
+
+  if(!d_string.empty())
+    d_string.append(1,' ');
+
+  d_string += ip;
+}
+
+void RecordTextWriter::xfrCAPort(ComboAddress &val)
+{
+  xfr16BitInt(val.sin4.sin_port);
+}
+
 void RecordTextWriter::xfrTime(const uint32_t& val)
 {
   if(!d_string.empty())
index 5f61034e919a7db7d6678b548cf2b8a2014c0213..efb409397266a7c31fd760e7a78510b6401418ea 100644 (file)
@@ -27,6 +27,7 @@
 
 #include "namespaces.hh"
 #include "dnsname.hh"
+#include "iputils.hh"
 
 class RecordTextException : public runtime_error
 {
@@ -48,6 +49,8 @@ public:
   void xfrType(uint16_t& val);
   void xfrIP(uint32_t& val);
   void xfrIP6(std::string& val);
+  void xfrCAWithoutPort(uint8_t version, ComboAddress &val);
+  void xfrCAPort(ComboAddress &val);
   void xfrTime(uint32_t& val);
 
   void xfrName(DNSName& val, bool compress=false, bool noDot=false);
@@ -59,6 +62,10 @@ public:
   void xfrBlobNoSpaces(string& val, int len=-1);
   void xfrBlob(string& val, int len=-1);
 
+  const string getRemaining() const {
+    return d_string.substr(d_pos);
+  }
+
   bool eof();
 private:
   string d_string;
@@ -78,6 +85,8 @@ public:
   void xfr8BitInt(const uint8_t& val);
   void xfrIP(const uint32_t& val);
   void xfrIP6(const std::string& val);
+  void xfrCAWithoutPort(uint8_t version, ComboAddress &val);
+  void xfrCAPort(ComboAddress &val);
   void xfrTime(const uint32_t& val);
   void xfrBase32HexBlob(const string& val);
 
@@ -89,6 +98,10 @@ public:
   void xfrBlob(const string& val, int len=-1);
   void xfrHexBlob(const string& val, bool keepReading=false);
   bool eof() { return true; };
+
+  const string getRemaining() const {
+     return "";
+  }
 private:
   string& d_string;
   bool d_nodot;
index 8c2070ae22111a8c98bd5e17249e43a015a1d0b9..4770fd6f341b360a8d194ed9b04861d6e11b8a85 100644 (file)
@@ -164,6 +164,7 @@ pdns_recursor_SOURCES = \
        webserver.cc webserver.hh \
        ws-api.cc ws-api.hh \
        ws-recursor.cc ws-recursor.hh \
+       xpf.cc xpf.hh \
        zoneparser-tng.cc zoneparser-tng.hh
 
 if !HAVE_LUA_HPP
@@ -248,11 +249,13 @@ testrunner_SOURCES = \
        test-signers.cc \
        test-syncres_cc.cc \
        test-tsig.cc \
+       test-xpf_cc.cc \
        testrunner.cc \
        tsigverifier.cc tsigverifier.hh \
        unix_utility.cc \
        validate.cc validate.hh \
        validate-recursor.cc validate-recursor.hh \
+       xpf.cc xpf.hh \
        zoneparser-tng.cc zoneparser-tng.hh
 
 testrunner_LDFLAGS = \
index 396572b4f25aa9eff7cfc6a8ca443925eb8ed5da..1e06f02fc11372cad064c0a17ca8c91750ede118 100644 (file)
@@ -1,6 +1,75 @@
 Changelogs for 4.1.x
 ====================
 
+.. changelog::
+  :version: 4.1.1
+  :released: 22nd of January 2018
+
+  This is the second release in the 4.1 train.
+
+  This release fixes PowerDNS Security Advisory :doc:`2018-01 <../security-advisories/powerdns-advisory-2018-01>`.
+
+  The full release notes can be read `on the blog <https://blog.powerdns.com/2018/01/22/powerdns-recursor-4-1-1/>`_.
+
+  This is a release on the stable branch, containing a fix for the
+  abovementioned security issue and several bug fixes from the
+  development branch.
+
+  .. change::
+    :tags: DNSSEC, Bug Fixes
+    :pullreq: 6215
+
+    Correctly handle ancestor delegation NSEC{,3} for children. Fixes
+    the DNSSEC validation issue found in Knot Resolver, where a NSEC{3}
+    ancestor delegation is wrongly use to prove the non-existence of a
+    RR below the delegation.
+    We already had the correct check for the exact owner name, but not
+    for RRs below the delegation.
+    (Security Advisory :doc:`2018-01 <../security-advisories/powerdns-advisory-2018-01>`)
+
+  .. change::
+    :tags: Internals, Bug Fixes
+    :pullreq: 6209
+    :tickets: 6212
+
+    Fix to make ``primeHints`` threadsafe, otherwise there's a small
+    chance on startup that the root-server IPs will be incorrect.
+
+  .. change::
+    :tags: Internals, Improvements
+    :pullreq: 6085
+    :tickets: 6198
+
+    Don't process records for another class than IN. We don't use
+    records of another class than IN, but we used to store some of them
+    in the cache which is useless. Just skip them.
+
+  .. change::
+    :tags: DNSSEC, Bug Fixes
+    :pullreq: 6092
+    :tickets: 6199
+
+    Fix the computation of the closest encloser for positive
+    answers. When the positive answer is expanded from a wildcard with
+    NSEC3, the closest encloser is not always parent of the qname,
+    depending on the number of labels in the initial wildcard.
+
+  .. change::
+    :tags: DNSSEC, Bug Fixes
+    :pullreq: 6095
+    :tickets: 6200
+
+    Pass the correct buffer size to ``arecvfrom()``. The incorrect size
+    could possibly cause DNSSEC failures.
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 6137
+    :tickets: 6201
+
+    Don't validate signature for "glue" CNAME, since anything else than
+    the initial CNAME can't be considered authoritative.
+
 .. changelog::
   :version: 4.1.0
   :released: 4th of December 2017
index 8bb211a5343ae12262ec8b2223694fdfa41e5d52..b41c2d49192503b09a97b2974b31018fc0e4319c 100644 (file)
@@ -72,7 +72,7 @@ zoneSizeHint
 ^^^^^^^^^^^^
 An indication of the number of expected entries in the zone, speeding up the loading of huge zones by reserving space in advance.
 
-Extra Settings for rzpMaster
+Extra settings for rpzMaster
 ----------------------------
 In addition to the settings above the settings for :func:`rpzMaster` may contain:
 
diff --git a/pdns/recursordist/docs/security-advisories/powerdns-advisory-2018-01.rst b/pdns/recursordist/docs/security-advisories/powerdns-advisory-2018-01.rst
new file mode 100644 (file)
index 0000000..838ca3f
--- /dev/null
@@ -0,0 +1,28 @@
+PowerDNS Security Advisory 2018-01: Insufficient validation of DNSSEC signatures
+================================================================================
+
+-  CVE: CVE-2018-1000003
+-  Date: January 22nd 2018
+-  Credit: CZ.NIC
+-  Affects: PowerDNS Recursor 4.1.0
+-  Not affected: PowerDNS Recursor < 4.1.0, 4.1.1
+-  Severity: Low
+-  Impact: Denial of existence spoofing
+-  Exploit: This problem can be triggered by an attacker in position of
+   man-in-the-middle
+-  Risk of system compromise: No
+-  Solution: Upgrade to a non-affected version
+
+An issue has been found in the DNSSEC validation component of PowerDNS Recursor,
+allowing an ancestor delegation NSEC or NSEC3 record to be used to wrongfully
+prove the non-existence of a RR below the owner name of that record. This would
+allow an attacker in position of man-in-the-middle to send a NXDOMAIN answer
+for a name that does exist. This issue has been assigned CVE-2018-1000003.
+
+PowerDNS Recursor 4.1.0 is affected.
+
+For those unable to upgrade to a new version, a minimal patch is
+`available <https://downloads.powerdns.com/patches/2018-01>`__
+
+We would like to thank CZ.NIC for finding and subsequently reporting this
+issue! Please also see https://lists.nic.cz/pipermail/knot-dns-users/2018-January/001309.html
index 8940d8eb65b173928da1aa6b47619bee0a95640e..ab3047b06bcbd6bdf38f3d193b28b39b677d3337 100644 (file)
@@ -322,6 +322,22 @@ This setting can be used to expand or reduce the limitations.
 
 Queries to addresses for zones as configured in any of the settings `forward-zones`_, `forward-zones-file`_ or `forward-zones-recurse`_ are performed regardless of these limitations.
 
+.. _setting-ecs-add-for:
+
+``ecs-add-for``
+--------------------------
+.. versionadded:: 4.2.0
+
+-  Comma separated list of netmasks
+-  Default: 0.0.0.0/0, ::, !127.0.0.0/8, !10.0.0.0/8, !100.64.0.0/10, !169.254.0.0/16, !192.168.0.0/16, !172.16.0.0/12, !::1/128, !fc00::/7, !fe80::/10
+
+List of requestor netmasks for which the requestor IP Address should be used as the :rfc:`EDNS Client Subnet <7871>` for outgoing queries. Outgoing queries for requestors that do not match this list will use the `ecs-scope-zero-address`_ instead.
+Valid incoming ECS values from `use-incoming-edns-subnet`_ are not replaced.
+
+Regardless of the value of this setting, ECS values are only sent for outgoing queries matching the conditions in the `edns-subnet-whitelist`_ setting. This setting only controls the actual value being sent.
+
+This defaults to not using the requestor address inside RFC1918 and similar "private" IP address spaces.
+
 .. _setting-ecs-ipv4-bits:
 
 ``ecs-ipv4-bits``
@@ -378,8 +394,10 @@ Lower this if you experience timeouts.
 -  Default: (none)
 
 List of netmasks and domains that :rfc:`EDNS Client Subnet <7871>` should be enabled for in outgoing queries.
-For example, an EDNS Client Subnet option containing the address of the initial requestor will be added to an outgoing query sent to server 192.0.2.1 for domain X if 192.0.2.1 matches one of the supplied netmasks, or if X matches one of the supplied domains.
-The initial requestor address will be truncated to 24 bits for IPv4 and to 56 bits for IPv6, as recommended in the privacy section of RFC 7871.
+
+For example, an EDNS Client Subnet option containing the address of the initial requestor (but see `ecs-add-for`_) will be added to an outgoing query sent to server 192.0.2.1 for domain X if 192.0.2.1 matches one of the supplied netmasks, or if X matches one of the supplied domains.
+The initial requestor address will be truncated to 24 bits for IPv4 (see `ecs-ipv4-bits`_) and to 56 bits for IPv6 (see `ecs-ipv6-bits`_), as recommended in the privacy section of RFC 7871.
+
 By default, this option is empty, meaning no EDNS Client Subnet information is sent.
 
 .. _setting-entropy-source:
@@ -1144,3 +1162,36 @@ TCP port where the webserver should listen on.
 -  Default: yes
 
 If a PID file should be written to `socket-dir`_
+
+.. _setting-xpf-allow-from:
+
+``xpf-allow-from``
+------------------
+.. versionadded:: 4.2.0
+
+-  IP ranges, separated by commas
+-  Default: empty
+
+.. note::
+  This is an experimental implementation of `draft-bellis-dnsop-xpf <https://datatracker.ietf.org/doc/draft-bellis-dnsop-xpf/>`_.
+
+The server will trust XPF records found in queries sent from those netmasks (both IPv4 and IPv6),
+and will adjust queries' source and destination accordingly. This is especially useful when the recursor
+is placed behind a proxy like `dnsdist <https://dnsdist.org>`_.
+Note that the ref:`setting-allow-from` setting is still applied to the original source address, and thus access restriction
+should be done on the proxy.
+
+.. _setting-xpf-rr-code:
+
+``xpf-rr-code``
+-------------------
+.. versionadded:: 4.2.0
+
+-  Integer
+-  Default: 0
+
+.. note::
+  This is an experimental implementation of `draft-bellis-dnsop-xpf <https://datatracker.ietf.org/doc/draft-bellis-dnsop-xpf/>`_.
+
+This option sets the resource record code to use for XPF records, as long as an official code has not been assigned to it.
+0 means that XPF is disabled.
index aab2fed0bb0f559a810de7107ac195274576764b..ad68e1e57b4476d93b0646213d7e6045351aaeae 100644 (file)
@@ -4,6 +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.
 
+4.1.x to 4.2.0 or master
+------------------------
+
+Two new settings have been added:
+
+- :ref:`setting-xpf-allow-from` can contain a list of IP addresses ranges from which `XPF (X-Proxied-For) <https://datatracker.ietf.org/doc/draft-bellis-dnsop-xpf/>`_ records will be trusted.
+- :ref:`setting-xpf-rr-code` should list the number of the XPF record to use (in lieu of an assigned code).
+
 4.0.x to 4.1.0
 --------------
 
index 28531923eea4184cf00285ca0c8de1c9e8611d3b..573ee1e0816ebee10f8a72a237d2fa028838670f 100644 (file)
@@ -24,6 +24,7 @@
 #include "negcache.hh"
 #include "misc.hh"
 #include "cachecleaner.hh"
+#include "utility.hh"
 
 /*!
  * Set ne to the NegCacheEntry for the last label in qname and return true if there
@@ -182,16 +183,18 @@ void NegCache::prune(unsigned int maxEntries) {
  */
 uint64_t NegCache::dumpToFile(FILE* fp) {
   uint64_t ret(0);
-  time_t now = time(0);
+  struct timeval now;
+  Utility::gettimeofday(&now, nullptr);
+
   negcache_sequence_t& sidx = d_negcache.get<1>();
   for(const NegCacheEntry& ne : sidx) {
     ret++;
-    fprintf(fp, "%s %d IN %s VIA %s ; (%s)\n", ne.d_name.toString().c_str(), (unsigned int) (ne.d_ttd - now), ne.d_qtype.getName().c_str(), ne.d_auth.toString().c_str(), vStates[ne.d_validationState]);
+    fprintf(fp, "%s %" PRId64 " IN %s VIA %s ; (%s)\n", ne.d_name.toString().c_str(), static_cast<int64_t>(ne.d_ttd - now.tv_sec), ne.d_qtype.getName().c_str(), ne.d_auth.toString().c_str(), vStates[ne.d_validationState]);
     for (const auto& rec : ne.DNSSECRecords.records) {
-      fprintf(fp, "%s %" PRId64 " IN %s %s ; (%s)\n", ne.d_name.toString().c_str(), static_cast<int64_t>(ne.d_ttd - now), DNSRecordContent::NumberToType(rec.d_type).c_str(), rec.d_content->getZoneRepresentation().c_str(), vStates[ne.d_validationState]);
+      fprintf(fp, "%s %" PRId64 " IN %s %s ; (%s)\n", ne.d_name.toString().c_str(), static_cast<int64_t>(ne.d_ttd - now.tv_sec), DNSRecordContent::NumberToType(rec.d_type).c_str(), rec.d_content->getZoneRepresentation().c_str(), vStates[ne.d_validationState]);
     }
     for (const auto& sig : ne.DNSSECRecords.signatures) {
-      fprintf(fp, "%s %" PRId64 " IN RRSIG %s ;\n", ne.d_name.toString().c_str(), static_cast<int64_t>(ne.d_ttd - now), sig.d_content->getZoneRepresentation().c_str());
+      fprintf(fp, "%s %" PRId64 " IN RRSIG %s ;\n", ne.d_name.toString().c_str(), static_cast<int64_t>(ne.d_ttd - now.tv_sec), sig.d_content->getZoneRepresentation().c_str());
     }
   }
   return ret;
index 2a4cab01edb88bf1781b45900da607567f03e145..fe12a962cca48c3d4e30caa85fbc2abae51fe042 100644 (file)
@@ -14,9 +14,6 @@
 #include "ednssubnet.hh"
 #include "iputils.hh"
 
-/* extract a specific EDNS0 option from a pointer on the beginning rdLen of the OPT RR */
-int getEDNSOption(char* optRR, size_t len, uint16_t wantedOption, char ** optionValue, size_t * optionValueSize);
-
 BOOST_AUTO_TEST_SUITE(ednsoptions_cc)
 
 static void getRawQueryWithECSAndCookie(const DNSName& name, const Netmask& ecs, const std::string& clientCookie, const std::string& serverCookie, std::vector<uint8_t>& query)
index 67425b55f83252e3f30c31a1a93aca7ce0a9c631..08272004ed3b8982c29273d3d6eb6a396bd64a7f 100644 (file)
@@ -66,7 +66,7 @@ void primeHints(void)
   arr.d_ttl=aaaarr.d_ttl=nsrr.d_ttl=time(nullptr)+3600000;
 
   for(char c='a';c<='m';++c) {
-    static char templ[40];
+    char templ[40];
     strncpy(templ,"a.root-servers.net.", sizeof(templ) - 1);
     templ[sizeof(templ)-1] = '\0';
     *templ=c;
@@ -75,18 +75,18 @@ void primeHints(void)
     arr.d_content=std::make_shared<ARecordContent>(ComboAddress(rootIps4[c-'a']));
     vector<DNSRecord> aset;
     aset.push_back(arr);
-    t_RC->replace(time(0), DNSName(templ), QType(QType::A), aset, vector<std::shared_ptr<RRSIGRecordContent>>(), vector<std::shared_ptr<DNSRecord>>(), true); // auth, nuke it all
+    t_RC->replace(time(nullptr), DNSName(templ), QType(QType::A), aset, vector<std::shared_ptr<RRSIGRecordContent>>(), vector<std::shared_ptr<DNSRecord>>(), true); // auth, nuke it all
     if (rootIps6[c-'a'] != NULL) {
       aaaarr.d_content=std::make_shared<AAAARecordContent>(ComboAddress(rootIps6[c-'a']));
 
       vector<DNSRecord> aaaaset;
       aaaaset.push_back(aaaarr);
-      t_RC->replace(time(0), DNSName(templ), QType(QType::AAAA), aaaaset, vector<std::shared_ptr<RRSIGRecordContent>>(), vector<std::shared_ptr<DNSRecord>>(), true);
+      t_RC->replace(time(nullptr), DNSName(templ), QType(QType::AAAA), aaaaset, vector<std::shared_ptr<RRSIGRecordContent>>(), vector<std::shared_ptr<DNSRecord>>(), true);
     }
 
     nsset.push_back(nsrr);
   }
-  t_RC->replace(time(0), g_rootdnsname, QType(QType::NS), nsset, vector<std::shared_ptr<RRSIGRecordContent>>(), vector<std::shared_ptr<DNSRecord>>(), false); // and stuff in the cache
+  t_RC->replace(time(nullptr), g_rootdnsname, QType(QType::NS), nsset, vector<std::shared_ptr<RRSIGRecordContent>>(), vector<std::shared_ptr<DNSRecord>>(), false); // and stuff in the cache
 }
 
 LuaConfigItems::LuaConfigItems()
@@ -133,10 +133,14 @@ static void init(bool debug=false)
   SyncRes::s_rootNXTrust = true;
   SyncRes::s_minimumTTL = 0;
   SyncRes::s_serverID = "PowerDNS Unit Tests Server ID";
-  SyncRes::clearEDNSSubnets();
+  SyncRes::clearEDNSLocalSubnets();
+  SyncRes::addEDNSLocalSubnet("0.0.0.0/0");
+  SyncRes::addEDNSLocalSubnet("::/0");
+  SyncRes::clearEDNSRemoteSubnets();
   SyncRes::clearEDNSDomains();
   SyncRes::clearDelegationOnly();
   SyncRes::clearDontQuery();
+  SyncRes::setECSScopeZeroAddress(Netmask("127.0.0.1/32"));
 
   SyncRes::clearNSSpeeds();
   BOOST_CHECK_EQUAL(SyncRes::getNSSpeedsSize(), 0);
@@ -264,7 +268,7 @@ static bool addRRSIG(const testkeysset_t& keys, std::vector<DNSRecord>& records,
 
   const auto it = keys.find(signer);
   if (it == keys.cend()) {
-    throw std::runtime_error("No DNSKEY found for " + signer.toString() + ", unable to compute the requested RRSIG");
+    throw std::runtime_error("No DNSKEY found for " + signer.toLogString() + ", unable to compute the requested RRSIG");
   }
 
   size_t recordsCount = records.size();
@@ -300,7 +304,7 @@ static void addDNSKEY(const testkeysset_t& keys, const DNSName& signer, uint32_t
 {
   const auto it = keys.find(signer);
   if (it == keys.cend()) {
-    throw std::runtime_error("No DNSKEY found for " + signer.toString());
+    throw std::runtime_error("No DNSKEY found for " + signer.toLogString());
   }
 
   DNSRecord rec;
@@ -746,9 +750,10 @@ BOOST_AUTO_TEST_CASE(test_all_nss_down) {
   BOOST_CHECK_EQUAL(ret.size(), 0);
   BOOST_CHECK_EQUAL(downServers.size(), 4);
 
+  time_t now = sr->getNow().tv_sec;
   for (const auto& server : downServers) {
     BOOST_CHECK_EQUAL(SyncRes::getServerFailsCount(server), 1);
-    BOOST_CHECK(SyncRes::isThrottled(time(nullptr), server, target, QType::A));
+    BOOST_CHECK(SyncRes::isThrottled(now, server, target, QType::A));
   }
 }
 
@@ -793,9 +798,10 @@ BOOST_AUTO_TEST_CASE(test_all_nss_network_error) {
   BOOST_CHECK_EQUAL(ret.size(), 0);
   BOOST_CHECK_EQUAL(downServers.size(), 4);
 
+  time_t now = sr->getNow().tv_sec;
   for (const auto& server : downServers) {
     BOOST_CHECK_EQUAL(SyncRes::getServerFailsCount(server), 1);
-    BOOST_CHECK(SyncRes::isThrottled(time(nullptr), server, target, QType::A));
+    BOOST_CHECK(SyncRes::isThrottled(now, server, target, QType::A));
   }
 }
 
@@ -903,9 +909,10 @@ BOOST_AUTO_TEST_CASE(test_os_limit_errors) {
   BOOST_CHECK_EQUAL(downServers.size(), 3);
 
   /* Error is reported as "OS limit error" (-2) so the servers should _NOT_ be marked down */
+  time_t now = sr->getNow().tv_sec;
   for (const auto& server : downServers) {
     BOOST_CHECK_EQUAL(SyncRes::getServerFailsCount(server), 0);
-    BOOST_CHECK(!SyncRes::isThrottled(time(nullptr), server, target, QType::A));
+    BOOST_CHECK(!SyncRes::isThrottled(now, server, target, QType::A));
   }
 }
 
@@ -1037,8 +1044,7 @@ BOOST_AUTO_TEST_CASE(test_edns_submask_by_domain) {
 
   EDNSSubnetOpts incomingECS;
   incomingECS.source = Netmask("192.0.2.128/32");
-  sr->setIncomingECSFound(true);
-  sr->setIncomingECS(incomingECS);
+  sr->setQuerySource(ComboAddress(), boost::optional<const EDNSSubnetOpts&>(incomingECS));
 
   sr->setAsyncCallback([target](const ComboAddress& ip, const DNSName& domain, int type, bool doTCP, bool sendRDQuery, int EDNS0Level, struct timeval* now, boost::optional<Netmask>& srcmask, boost::optional<const ResolveContext&> context, std::shared_ptr<RemoteLogger> outgoingLogger, LWResult* res) {
 
@@ -1059,12 +1065,11 @@ BOOST_AUTO_TEST_CASE(test_edns_submask_by_addr) {
   primeHints();
 
   const DNSName target("powerdns.com.");
-  SyncRes::addEDNSSubnet(Netmask("192.0.2.1/32"));
+  SyncRes::addEDNSRemoteSubnet("192.0.2.1/32");
 
   EDNSSubnetOpts incomingECS;
   incomingECS.source = Netmask("2001:DB8::FF/128");
-  sr->setIncomingECSFound(true);
-  sr->setIncomingECS(incomingECS);
+  sr->setQuerySource(ComboAddress(), boost::optional<const EDNSSubnetOpts&>(incomingECS));
 
   sr->setAsyncCallback([target](const ComboAddress& ip, const DNSName& domain, int type, bool doTCP, bool sendRDQuery, int EDNS0Level, struct timeval* now, boost::optional<Netmask>& srcmask, boost::optional<const ResolveContext&> context, std::shared_ptr<RemoteLogger> outgoingLogger, LWResult* res) {
 
@@ -1096,6 +1101,178 @@ BOOST_AUTO_TEST_CASE(test_edns_submask_by_addr) {
   BOOST_CHECK_EQUAL(ret[0].d_name, target);
 }
 
+BOOST_AUTO_TEST_CASE(test_ecs_use_requestor) {
+  std::unique_ptr<SyncRes> sr;
+  initSR(sr);
+
+  primeHints();
+
+  const DNSName target("powerdns.com.");
+  SyncRes::addEDNSRemoteSubnet("192.0.2.1/32");
+  // No incoming ECS data
+  sr->setQuerySource(ComboAddress("192.0.2.127"), boost::none);
+
+  sr->setAsyncCallback([target](const ComboAddress& ip, const DNSName& domain, int type, bool doTCP, bool sendRDQuery, int EDNS0Level, struct timeval* now, boost::optional<Netmask>& srcmask, boost::optional<const ResolveContext&> context, std::shared_ptr<RemoteLogger> outgoingLogger, LWResult* res) {
+
+      if (isRootServer(ip)) {
+        BOOST_REQUIRE(!srcmask);
+
+        setLWResult(res, 0, false, false, true);
+        addRecordToLW(res, domain, QType::NS, "a.gtld-servers.net.", DNSResourceRecord::AUTHORITY, 172800);
+        addRecordToLW(res, "a.gtld-servers.net.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600);
+        return 1;
+      } else if (ip == ComboAddress("192.0.2.1:53")) {
+
+        BOOST_REQUIRE(srcmask);
+        BOOST_CHECK_EQUAL(srcmask->toString(), "192.0.2.0/24");
+
+        setLWResult(res, 0, true, false, false);
+        addRecordToLW(res, domain, QType::A, "192.0.2.2");
+        return 1;
+      }
+
+      return 0;
+    });
+
+  vector<DNSRecord> ret;
+  int res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
+  BOOST_CHECK_EQUAL(res, RCode::NoError);
+  BOOST_REQUIRE_EQUAL(ret.size(), 1);
+  BOOST_CHECK(ret[0].d_type == QType::A);
+  BOOST_CHECK_EQUAL(ret[0].d_name, target);
+}
+
+BOOST_AUTO_TEST_CASE(test_ecs_use_scope_zero) {
+  std::unique_ptr<SyncRes> sr;
+  initSR(sr);
+
+  primeHints();
+
+  const DNSName target("powerdns.com.");
+  SyncRes::addEDNSRemoteSubnet("192.0.2.1/32");
+  SyncRes::clearEDNSLocalSubnets();
+  SyncRes::addEDNSLocalSubnet("192.0.2.254/32");
+  // No incoming ECS data, Requestor IP not in ecs-add-for
+  sr->setQuerySource(ComboAddress("192.0.2.127"), boost::none);
+
+  sr->setAsyncCallback([target](const ComboAddress& ip, const DNSName& domain, int type, bool doTCP, bool sendRDQuery, int EDNS0Level, struct timeval* now, boost::optional<Netmask>& srcmask, boost::optional<const ResolveContext&> context, std::shared_ptr<RemoteLogger> outgoingLogger, LWResult* res) {
+
+      if (isRootServer(ip)) {
+        BOOST_REQUIRE(!srcmask);
+
+        setLWResult(res, 0, false, false, true);
+        addRecordToLW(res, domain, QType::NS, "a.gtld-servers.net.", DNSResourceRecord::AUTHORITY, 172800);
+        addRecordToLW(res, "a.gtld-servers.net.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600);
+        return 1;
+      } else if (ip == ComboAddress("192.0.2.1:53")) {
+
+        BOOST_REQUIRE(srcmask);
+        BOOST_CHECK_EQUAL(srcmask->toString(), "127.0.0.1/32");
+
+        setLWResult(res, 0, true, false, false);
+        addRecordToLW(res, domain, QType::A, "192.0.2.2");
+        return 1;
+      }
+
+      return 0;
+    });
+
+  vector<DNSRecord> ret;
+  int res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
+  BOOST_CHECK_EQUAL(res, RCode::NoError);
+  BOOST_REQUIRE_EQUAL(ret.size(), 1);
+  BOOST_CHECK(ret[0].d_type == QType::A);
+  BOOST_CHECK_EQUAL(ret[0].d_name, target);
+}
+
+BOOST_AUTO_TEST_CASE(test_ecs_honor_incoming_mask) {
+  std::unique_ptr<SyncRes> sr;
+  initSR(sr);
+
+  primeHints();
+
+  const DNSName target("powerdns.com.");
+  SyncRes::addEDNSRemoteSubnet("192.0.2.1/32");
+  SyncRes::clearEDNSLocalSubnets();
+  SyncRes::addEDNSLocalSubnet("192.0.2.254/32");
+  EDNSSubnetOpts incomingECS;
+  incomingECS.source = Netmask("192.0.0.0/16");
+  sr->setQuerySource(ComboAddress("192.0.2.127"), boost::optional<const EDNSSubnetOpts&>(incomingECS));
+
+  sr->setAsyncCallback([target](const ComboAddress& ip, const DNSName& domain, int type, bool doTCP, bool sendRDQuery, int EDNS0Level, struct timeval* now, boost::optional<Netmask>& srcmask, boost::optional<const ResolveContext&> context, std::shared_ptr<RemoteLogger> outgoingLogger, LWResult* res) {
+
+      if (isRootServer(ip)) {
+        BOOST_REQUIRE(!srcmask);
+
+        setLWResult(res, 0, false, false, true);
+        addRecordToLW(res, domain, QType::NS, "a.gtld-servers.net.", DNSResourceRecord::AUTHORITY, 172800);
+        addRecordToLW(res, "a.gtld-servers.net.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600);
+        return 1;
+      } else if (ip == ComboAddress("192.0.2.1:53")) {
+
+        BOOST_REQUIRE(srcmask);
+        BOOST_CHECK_EQUAL(srcmask->toString(), "192.0.0.0/16");
+
+        setLWResult(res, 0, true, false, false);
+        addRecordToLW(res, domain, QType::A, "192.0.2.2");
+        return 1;
+      }
+
+      return 0;
+    });
+
+  vector<DNSRecord> ret;
+  int res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
+  BOOST_CHECK_EQUAL(res, RCode::NoError);
+  BOOST_REQUIRE_EQUAL(ret.size(), 1);
+  BOOST_CHECK(ret[0].d_type == QType::A);
+  BOOST_CHECK_EQUAL(ret[0].d_name, target);
+}
+
+BOOST_AUTO_TEST_CASE(test_ecs_honor_incoming_mask_zero) {
+  std::unique_ptr<SyncRes> sr;
+  initSR(sr);
+
+  primeHints();
+
+  const DNSName target("powerdns.com.");
+  SyncRes::addEDNSRemoteSubnet("192.0.2.1/32");
+  SyncRes::clearEDNSLocalSubnets();
+  SyncRes::addEDNSLocalSubnet("192.0.2.254/32");
+  EDNSSubnetOpts incomingECS;
+  incomingECS.source = Netmask("0.0.0.0/0");
+  sr->setQuerySource(ComboAddress("192.0.2.127"), boost::optional<const EDNSSubnetOpts&>(incomingECS));
+
+  sr->setAsyncCallback([target](const ComboAddress& ip, const DNSName& domain, int type, bool doTCP, bool sendRDQuery, int EDNS0Level, struct timeval* now, boost::optional<Netmask>& srcmask, boost::optional<const ResolveContext&> context, std::shared_ptr<RemoteLogger> outgoingLogger, LWResult* res) {
+
+      if (isRootServer(ip)) {
+        BOOST_REQUIRE(!srcmask);
+
+        setLWResult(res, 0, false, false, true);
+        addRecordToLW(res, domain, QType::NS, "a.gtld-servers.net.", DNSResourceRecord::AUTHORITY, 172800);
+        addRecordToLW(res, "a.gtld-servers.net.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600);
+        return 1;
+      } else if (ip == ComboAddress("192.0.2.1:53")) {
+
+        BOOST_REQUIRE(srcmask);
+        BOOST_CHECK_EQUAL(srcmask->toString(), "127.0.0.1/32");
+
+        setLWResult(res, 0, true, false, false);
+        addRecordToLW(res, domain, QType::A, "192.0.2.2");
+        return 1;
+      }
+
+      return 0;
+    });
+
+  vector<DNSRecord> ret;
+  int res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
+  BOOST_CHECK_EQUAL(res, RCode::NoError);
+  BOOST_REQUIRE_EQUAL(ret.size(), 1);
+  BOOST_CHECK(ret[0].d_type == QType::A);
+  BOOST_CHECK_EQUAL(ret[0].d_name, target);
+}
+
 BOOST_AUTO_TEST_CASE(test_following_cname) {
   std::unique_ptr<SyncRes> sr;
   initSR(sr);
@@ -1498,7 +1675,8 @@ BOOST_AUTO_TEST_CASE(test_throttled_server) {
     });
 
   /* mark ns as down */
-  SyncRes::doThrottle(time(nullptr), ns, SyncRes::s_serverdownthrottletime, 10000);
+  time_t now = sr->getNow().tv_sec;
+  SyncRes::doThrottle(now, ns, SyncRes::s_serverdownthrottletime, 10000);
 
   vector<DNSRecord> ret;
   int res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
@@ -1518,14 +1696,15 @@ BOOST_AUTO_TEST_CASE(test_throttled_server_count) {
 
   const size_t blocks = 10;
   /* mark ns as down for 'blocks' queries */
-  SyncRes::doThrottle(time(nullptr), ns, SyncRes::s_serverdownthrottletime, blocks);
+  time_t now = sr->getNow().tv_sec;
+  SyncRes::doThrottle(now, ns, SyncRes::s_serverdownthrottletime, blocks);
 
   for (size_t idx = 0; idx < blocks; idx++) {
-    BOOST_CHECK(SyncRes::isThrottled(time(nullptr), ns));
+    BOOST_CHECK(SyncRes::isThrottled(now, ns));
   }
 
   /* we have been throttled 'blocks' times, we should not be throttled anymore */
-  BOOST_CHECK(!SyncRes::isThrottled(time(nullptr), ns));
+  BOOST_CHECK(!SyncRes::isThrottled(now, ns));
 }
 
 BOOST_AUTO_TEST_CASE(test_throttled_server_time) {
@@ -1538,14 +1717,13 @@ BOOST_AUTO_TEST_CASE(test_throttled_server_time) {
 
   const size_t seconds = 1;
   /* mark ns as down for 'seconds' seconds */
-  SyncRes::doThrottle(time(nullptr), ns, seconds, 10000);
-
-  BOOST_CHECK(SyncRes::isThrottled(time(nullptr), ns));
+  time_t now = sr->getNow().tv_sec;
+  SyncRes::doThrottle(now, ns, seconds, 10000);
 
-  sleep(seconds + 1);
+  BOOST_CHECK(SyncRes::isThrottled(now, ns));
 
   /* we should not be throttled anymore */
-  BOOST_CHECK(!SyncRes::isThrottled(time(nullptr), ns));
+  BOOST_CHECK(!SyncRes::isThrottled(now + 2, ns));
 }
 
 BOOST_AUTO_TEST_CASE(test_dont_query_server) {
@@ -1784,8 +1962,7 @@ BOOST_AUTO_TEST_CASE(test_skip_negcache_for_variable_response) {
 
   EDNSSubnetOpts incomingECS;
   incomingECS.source = Netmask("192.0.2.128/32");
-  sr->setIncomingECSFound(true);
-  sr->setIncomingECS(incomingECS);
+  sr->setQuerySource(ComboAddress(), boost::optional<const EDNSSubnetOpts&>(incomingECS));
 
   sr->setAsyncCallback([target,cnameTarget](const ComboAddress& ip, const DNSName& domain, int type, bool doTCP, bool sendRDQuery, int EDNS0Level, struct timeval* now, boost::optional<Netmask>& srcmask, boost::optional<const ResolveContext&> context, std::shared_ptr<RemoteLogger> outgoingLogger, LWResult* res) {
 
@@ -1875,8 +2052,7 @@ BOOST_AUTO_TEST_CASE(test_ns_speed) {
       return 0;
     });
 
-  struct timeval now;
-  gettimeofday(&now, 0);
+  struct timeval now = sr->getNow();
 
   /* make pdns-public-ns2.powerdns.com. the fastest NS, with its IPv6 address faster than the IPV4 one,
      then pdns-public-ns1.powerdns.com. on IPv4 */
@@ -4279,6 +4455,144 @@ BOOST_AUTO_TEST_CASE(test_dnssec_bogus_bad_algo) {
   BOOST_CHECK_EQUAL(queriesCount, 2);
 }
 
+BOOST_AUTO_TEST_CASE(test_dnssec_bogus_unsigned_ds) {
+  std::unique_ptr<SyncRes> sr;
+  initSR(sr, true);
+
+  setDNSSECValidation(sr, DNSSECMode::ValidateAll);
+
+  primeHints();
+  const DNSName target("com.");
+  const ComboAddress targetAddr("192.0.2.42");
+  testkeysset_t keys;
+
+  auto luaconfsCopy = g_luaconfs.getCopy();
+  luaconfsCopy.dsAnchors.clear();
+  generateKeyMaterial(g_rootdnsname, DNSSECKeeper::RSASHA512, DNSSECKeeper::SHA384, keys, luaconfsCopy.dsAnchors);
+  generateKeyMaterial(DNSName("com."), DNSSECKeeper::ECDSA256, DNSSECKeeper::SHA256, keys);
+
+  g_luaconfs.setState(luaconfsCopy);
+
+  size_t queriesCount = 0;
+
+  sr->setAsyncCallback([target,targetAddr,&queriesCount,keys](const ComboAddress& ip, const DNSName& domain, int type, bool doTCP, bool sendRDQuery, int EDNS0Level, struct timeval* now, boost::optional<Netmask>& srcmask, boost::optional<const ResolveContext&> context, std::shared_ptr<RemoteLogger> outgoingLogger, LWResult* res) {
+      queriesCount++;
+
+      DNSName auth = domain;
+
+      if (type == QType::DS || type == QType::DNSKEY) {
+        if (genericDSAndDNSKEYHandler(res, domain, auth, type, keys) == 0) {
+          return 0;
+        }
+
+        if (type == QType::DS && domain == target) {
+          /* remove the last record, which is the DS's RRSIG */
+          res->d_records.pop_back();
+        }
+
+        return 1;
+      }
+
+      if (isRootServer(ip)) {
+        setLWResult(res, 0, false, false, true);
+        addRecordToLW(res, "com.", QType::NS, "a.gtld-servers.com.", DNSResourceRecord::AUTHORITY, 3600);
+        /* Include the DS but omit the RRSIG*/
+        addDS(DNSName("com."), 300, res->d_records, keys);
+        addRecordToLW(res, "a.gtld-servers.com.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600);
+        return 1;
+      }
+
+      if (ip == ComboAddress("192.0.2.1:53")) {
+        setLWResult(res, RCode::NoError, true, false, true);
+        addRecordToLW(res, domain, QType::A, targetAddr.toString(), DNSResourceRecord::ANSWER, 3600);
+        addRRSIG(keys, res->d_records, auth, 300);
+        return 1;
+      }
+
+      return 0;
+    });
+
+  vector<DNSRecord> ret;
+  int res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
+  BOOST_CHECK_EQUAL(res, RCode::NoError);
+  BOOST_CHECK_EQUAL(sr->getValidationState(), Bogus);
+  BOOST_REQUIRE_EQUAL(ret.size(), 2);
+  BOOST_CHECK_EQUAL(queriesCount, 4);
+
+  /* again, to test the cache */
+  ret.clear();
+  res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
+  BOOST_CHECK_EQUAL(res, RCode::NoError);
+  BOOST_CHECK_EQUAL(sr->getValidationState(), Bogus);
+  BOOST_REQUIRE_EQUAL(ret.size(), 2);
+  BOOST_CHECK_EQUAL(queriesCount, 4);
+
+  /* now we ask directly for the DS */
+  ret.clear();
+  res = sr->beginResolve(DNSName("com."), QType(QType::DS), QClass::IN, ret);
+  BOOST_CHECK_EQUAL(res, RCode::NoError);
+  BOOST_CHECK_EQUAL(sr->getValidationState(), Bogus);
+  BOOST_REQUIRE_EQUAL(ret.size(), 1);
+  BOOST_CHECK_EQUAL(queriesCount, 4);
+}
+
+BOOST_AUTO_TEST_CASE(test_dnssec_bogus_unsigned_ds_direct) {
+  std::unique_ptr<SyncRes> sr;
+  initSR(sr, true);
+
+  setDNSSECValidation(sr, DNSSECMode::ValidateAll);
+
+  primeHints();
+  const DNSName target("com.");
+  testkeysset_t keys;
+
+  auto luaconfsCopy = g_luaconfs.getCopy();
+  luaconfsCopy.dsAnchors.clear();
+  generateKeyMaterial(g_rootdnsname, DNSSECKeeper::RSASHA512, DNSSECKeeper::SHA384, keys, luaconfsCopy.dsAnchors);
+  generateKeyMaterial(DNSName("com."), DNSSECKeeper::ECDSA256, DNSSECKeeper::SHA256, keys);
+
+  g_luaconfs.setState(luaconfsCopy);
+
+  size_t queriesCount = 0;
+
+  sr->setAsyncCallback([target,&queriesCount,keys](const ComboAddress& ip, const DNSName& domain, int type, bool doTCP, bool sendRDQuery, int EDNS0Level, struct timeval* now, boost::optional<Netmask>& srcmask, boost::optional<const ResolveContext&> context, std::shared_ptr<RemoteLogger> outgoingLogger, LWResult* res) {
+      queriesCount++;
+
+      DNSName auth = domain;
+
+      if (type == QType::DS || type == QType::DNSKEY) {
+        if (genericDSAndDNSKEYHandler(res, domain, auth, type, keys) == 0) {
+          return 0;
+        }
+
+        if (type == QType::DS && domain == target) {
+          /* remove the last record, which is the DS's RRSIG */
+          res->d_records.pop_back();
+        }
+
+        return 1;
+      }
+
+      if (isRootServer(ip)) {
+        setLWResult(res, 0, false, false, true);
+        addRecordToLW(res, "com.", QType::NS, "a.gtld-servers.com.", DNSResourceRecord::AUTHORITY, 3600);
+        /* Include the DS but omit the RRSIG*/
+        addDS(DNSName("com."), 300, res->d_records, keys);
+        addRecordToLW(res, "a.gtld-servers.com.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600);
+        return 1;
+      }
+
+      return 0;
+    });
+
+  vector<DNSRecord> ret;
+  int res = sr->beginResolve(DNSName("com."), QType(QType::DS), QClass::IN, ret);
+  BOOST_CHECK_EQUAL(res, RCode::NoError);
+  BOOST_CHECK_EQUAL(sr->getValidationState(), Bogus);
+  BOOST_REQUIRE_EQUAL(ret.size(), 1);
+  BOOST_CHECK_EQUAL(queriesCount, 1);
+}
+
 BOOST_AUTO_TEST_CASE(test_dnssec_secure_various_algos) {
   std::unique_ptr<SyncRes> sr;
   initSR(sr, true);
@@ -7022,6 +7336,127 @@ BOOST_AUTO_TEST_CASE(test_dnssec_secure_to_insecure_cname) {
   BOOST_CHECK_EQUAL(queriesCount, 11);
 }
 
+BOOST_AUTO_TEST_CASE(test_dnssec_secure_to_insecure_cname_glue) {
+  std::unique_ptr<SyncRes> sr;
+  initSR(sr, true);
+
+  setDNSSECValidation(sr, DNSSECMode::ValidateAll);
+
+  primeHints();
+  const DNSName target("powerdns.com.");
+  const DNSName targetCName1("cname.sub.powerdns.com.");
+  const DNSName targetCName2("cname2.sub.powerdns.com.");
+  const ComboAddress targetCName2Addr("192.0.2.42");
+  testkeysset_t keys;
+
+  auto luaconfsCopy = g_luaconfs.getCopy();
+  luaconfsCopy.dsAnchors.clear();
+  generateKeyMaterial(g_rootdnsname, DNSSECKeeper::ECDSA256, DNSSECKeeper::SHA256, keys, luaconfsCopy.dsAnchors);
+  generateKeyMaterial(DNSName("com."), DNSSECKeeper::ECDSA256, DNSSECKeeper::SHA256, keys);
+  generateKeyMaterial(DNSName("powerdns.com."), DNSSECKeeper::ECDSA256, DNSSECKeeper::SHA256, keys);
+  g_luaconfs.setState(luaconfsCopy);
+
+  size_t queriesCount = 0;
+
+  sr->setAsyncCallback([target,targetCName1,targetCName2,targetCName2Addr,&queriesCount,keys](const ComboAddress& ip, const DNSName& domain, int type, bool doTCP, bool sendRDQuery, int EDNS0Level, struct timeval* now, boost::optional<Netmask>& srcmask, boost::optional<const ResolveContext&> context, std::shared_ptr<RemoteLogger> outgoingLogger, LWResult* res) {
+      queriesCount++;
+
+      if (type == QType::DS || type == QType::DNSKEY) {
+        if (domain == DNSName("sub.powerdns.com")) {
+          setLWResult(res, 0, false, false, true);
+          addRecordToLW(res, domain, QType::SOA, "pdns-public-ns1.powerdns.com. pieter\\.lexis.powerdns.com. 2017032301 10800 3600 604800 3600", DNSResourceRecord::AUTHORITY, 3600);
+          addNSECRecordToLW(domain, DNSName("z.power-dns.com."), { QType::NS }, 600, res->d_records);
+          addRRSIG(keys, res->d_records, DNSName("com."), 300);
+          return 1;
+        }
+        else {
+          return genericDSAndDNSKEYHandler(res, domain, domain, type, keys);
+        }
+      }
+      else {
+        if (isRootServer(ip)) {
+          setLWResult(res, 0, false, false, true);
+          addRecordToLW(res, "com.", QType::NS, "a.gtld-servers.com.", DNSResourceRecord::AUTHORITY, 3600);
+          addDS(DNSName("com."), 300, res->d_records, keys);
+          addRRSIG(keys, res->d_records, DNSName("."), 300);
+          addRecordToLW(res, "a.gtld-servers.com.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600);
+          return 1;
+        }
+        else if (ip == ComboAddress("192.0.2.1:53")) {
+          setLWResult(res, 0, false, false, true);
+          if (domain == DNSName("com.")) {
+            setLWResult(res, 0, true, false, true);
+            addRecordToLW(res, domain, QType::NS, "a.gtld-servers.com.");
+            addRRSIG(keys, res->d_records, DNSName("com."), 300);
+            addRecordToLW(res, "a.gtld-servers.com.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600);
+            addRRSIG(keys, res->d_records, DNSName("com."), 300);
+          }
+          else {
+            addRecordToLW(res, domain, QType::NS, "ns1.powerdns.com.", DNSResourceRecord::AUTHORITY, 3600);
+            if (domain == DNSName("powerdns.com.")) {
+              addDS(DNSName("powerdns.com."), 300, res->d_records, keys);
+            }
+            else if (domain == DNSName("sub.powerdns.com")) {
+              addNSECRecordToLW(domain, DNSName("z.power-dns.com."), { QType::NS }, 600, res->d_records);
+            }
+            addRRSIG(keys, res->d_records, DNSName("com."), 300);
+            addRecordToLW(res, "ns1.powerdns.com.", QType::A, "192.0.2.2", DNSResourceRecord::ADDITIONAL, 3600);
+          }
+
+          return 1;
+        }
+        else if (ip == ComboAddress("192.0.2.2:53")) {
+          setLWResult(res, 0, true, false, true);
+
+          if (type == QType::NS) {
+            addRecordToLW(res, domain, QType::NS, "ns1.powerdns.com.");
+            if (domain == DNSName("powerdns.com.")) {
+              addRRSIG(keys, res->d_records, domain, 300);
+            }
+            addRecordToLW(res, "ns1.powerdns.com.", QType::A, "192.0.2.2", DNSResourceRecord::ADDITIONAL, 3600);
+            if (domain == DNSName("powerdns.com.")) {
+              addRRSIG(keys, res->d_records, domain, 300);
+            }
+          }
+          else {
+            if (domain == DNSName("powerdns.com.")) {
+              addRecordToLW(res, domain, QType::CNAME, targetCName1.toString());
+              addRRSIG(keys, res->d_records, domain, 300);
+              /* add the CNAME target as a glue, with no RRSIG since the sub zone is insecure */
+              addRecordToLW(res, targetCName1, QType::CNAME, targetCName2.toString());
+              addRecordToLW(res, targetCName2, QType::A, targetCName2Addr.toString());
+            }
+            else if (domain == targetCName1) {
+              addRecordToLW(res, domain, QType::CNAME, targetCName2.toString());
+            }
+            else if (domain == targetCName2) {
+              addRecordToLW(res, domain, QType::A, targetCName2Addr.toString());
+            }
+          }
+
+          return 1;
+        }
+      }
+
+      return 0;
+    });
+
+  vector<DNSRecord> ret;
+  int res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
+  BOOST_CHECK_EQUAL(res, RCode::NoError);
+  BOOST_CHECK_EQUAL(sr->getValidationState(), Insecure);
+  BOOST_REQUIRE_EQUAL(ret.size(), 4);
+  BOOST_CHECK_EQUAL(queriesCount, 11);
+
+  /* again, to test the cache */
+  ret.clear();
+  res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
+  BOOST_CHECK_EQUAL(res, RCode::NoError);
+  BOOST_CHECK_EQUAL(sr->getValidationState(), Insecure);
+  BOOST_REQUIRE_EQUAL(ret.size(), 4);
+  BOOST_CHECK_EQUAL(queriesCount, 11);
+}
+
 BOOST_AUTO_TEST_CASE(test_dnssec_insecure_to_secure_cname) {
   std::unique_ptr<SyncRes> sr;
   initSR(sr, true);
@@ -8152,6 +8587,10 @@ BOOST_AUTO_TEST_CASE(test_nsec_ancestor_nxqtype_denial) {
      delegation NSEC can only deny the DS */
   BOOST_CHECK_EQUAL(denialState, NODATA);
 
+  /* it can not be used to deny any RRs below that owner name either */
+  denialState = getDenial(denialMap, DNSName("sub.a."), QType::A, false, false);
+  BOOST_CHECK_EQUAL(denialState, NODATA);
+
   denialState = getDenial(denialMap, DNSName("a."), QType::DS, true, true);
   BOOST_CHECK_EQUAL(denialState, NXQTYPE);
 }
@@ -8415,6 +8854,36 @@ BOOST_AUTO_TEST_CASE(test_nsec3_ancestor_nxqtype_denial) {
 
   denialState = getDenial(denialMap, DNSName("a."), QType::DS, true, true);
   BOOST_CHECK_EQUAL(denialState, NXQTYPE);
+
+  /* it can not be used to deny any RRs below that owner name either */
+  /* Add NSEC3 for the next closer */
+  recordContents.clear();
+  signatureContents.clear();
+  records.clear();
+  addNSEC3NarrowRecordToLW(DNSName("sub.a."), DNSName("."), { QType::A, QType::TXT, QType::RRSIG, QType::NSEC3 }, 600, records);
+  recordContents.push_back(records.at(0).d_content);
+  addRRSIG(keys, records, DNSName("."), 300);
+  signatureContents.push_back(getRR<RRSIGRecordContent>(records.at(1)));
+
+  pair.records = recordContents;
+  pair.signatures = signatureContents;
+  denialMap[std::make_pair(records.at(0).d_name, records.at(0).d_type)] = pair;
+
+  /* add wildcard denial */
+  recordContents.clear();
+  signatureContents.clear();
+  records.clear();
+  addNSEC3NarrowRecordToLW(DNSName("*.a."), DNSName("."), { QType::A, QType::TXT, QType::RRSIG, QType::NSEC3 }, 600, records);
+  recordContents.push_back(records.at(0).d_content);
+  addRRSIG(keys, records, DNSName("."), 300);
+  signatureContents.push_back(getRR<RRSIGRecordContent>(records.at(1)));
+
+  pair.records = recordContents;
+  pair.signatures = signatureContents;
+  denialMap[std::make_pair(records.at(0).d_name, records.at(0).d_type)] = pair;
+
+  denialState = getDenial(denialMap, DNSName("sub.a."), QType::A, false, true);
+  BOOST_CHECK_EQUAL(denialState, NODATA);
 }
 
 BOOST_AUTO_TEST_CASE(test_nsec3_denial_too_many_iterations) {
diff --git a/pdns/recursordist/test-xpf_cc.cc b/pdns/recursordist/test-xpf_cc.cc
new file mode 100644 (file)
index 0000000..61d079c
--- /dev/null
@@ -0,0 +1,180 @@
+#define BOOST_TEST_DYN_LINK
+#define BOOST_TEST_NO_MAIN
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+#include <boost/test/unit_test.hpp>
+
+#include "xpf.hh"
+
+BOOST_AUTO_TEST_SUITE(xpf_cc)
+
+BOOST_AUTO_TEST_CASE(test_generateXPFPayload) {
+
+  /* Mixing v4 with v6 should throw */
+  BOOST_CHECK_THROW(generateXPFPayload(false, ComboAddress("192.0.2.1"), ComboAddress("2001:db8::1")), std::runtime_error);
+  BOOST_CHECK_THROW(generateXPFPayload(false, ComboAddress("2001:db8::1"), ComboAddress("192.0.2.1")), std::runtime_error);
+
+  {
+    /* v4 payload over UDP */
+    ComboAddress source("192.0.2.1:53");
+    ComboAddress destination("192.0.2.2:65535");
+
+    auto payload = generateXPFPayload(false, source, destination);
+    BOOST_CHECK_EQUAL(payload.size(), 14);
+    BOOST_CHECK_EQUAL(payload.at(0), 4);
+    BOOST_CHECK_EQUAL(payload.at(1), 17);
+
+    ComboAddress parsedSource;
+    ComboAddress parsedDestination;
+    BOOST_CHECK(parseXPFPayload(payload.c_str(), payload.size(), parsedSource, &parsedDestination));
+    BOOST_CHECK_EQUAL(parsedSource.toStringWithPort(), source.toStringWithPort());
+    BOOST_CHECK_EQUAL(parsedDestination.toStringWithPort(), destination.toStringWithPort());
+  }
+
+  {
+    /* v4 payload over TCP */
+    ComboAddress source("192.0.2.1:53");
+    ComboAddress destination("192.0.2.2:65535");
+
+    auto payload = generateXPFPayload(true, source, destination);
+    BOOST_CHECK_EQUAL(payload.size(), 14);
+    BOOST_CHECK_EQUAL(payload.at(0), 4);
+    BOOST_CHECK_EQUAL(payload.at(1), 6);
+
+    ComboAddress parsedSource;
+    ComboAddress parsedDestination;
+    BOOST_CHECK(parseXPFPayload(payload.c_str(), payload.size(), parsedSource, &parsedDestination));
+    BOOST_CHECK_EQUAL(parsedSource.toStringWithPort(), source.toStringWithPort());
+    BOOST_CHECK_EQUAL(parsedDestination.toStringWithPort(), destination.toStringWithPort());
+  }
+
+  {
+    /* v6 payload over UDP */
+    ComboAddress source("[2001:db8::1]:42");
+    ComboAddress destination("[::1]:65535");
+
+    auto payload = generateXPFPayload(false, source, destination);
+    BOOST_CHECK_EQUAL(payload.size(), 38);
+    BOOST_CHECK_EQUAL(payload.at(0), 6);
+    BOOST_CHECK_EQUAL(payload.at(1), 17);
+
+    ComboAddress parsedSource;
+    ComboAddress parsedDestination;
+    BOOST_CHECK(parseXPFPayload(payload.c_str(), payload.size(), parsedSource, &parsedDestination));
+    BOOST_CHECK_EQUAL(parsedSource.toStringWithPort(), source.toStringWithPort());
+    BOOST_CHECK_EQUAL(parsedDestination.toStringWithPort(), destination.toStringWithPort());
+  }
+
+  {
+    /* v6 payload over TCP */
+    ComboAddress source("[2001:db8::1]:42");
+    ComboAddress destination("[::1]:65535");
+
+    auto payload = generateXPFPayload(true, source, destination);
+    BOOST_CHECK_EQUAL(payload.size(), 38);
+    BOOST_CHECK_EQUAL(payload.at(0), 6);
+    BOOST_CHECK_EQUAL(payload.at(1), 6);
+
+    ComboAddress parsedSource;
+    ComboAddress parsedDestination;
+    BOOST_CHECK(parseXPFPayload(payload.c_str(), payload.size(), parsedSource, &parsedDestination));
+    BOOST_CHECK_EQUAL(parsedSource.toStringWithPort(), source.toStringWithPort());
+    BOOST_CHECK_EQUAL(parsedDestination.toStringWithPort(), destination.toStringWithPort());
+  }
+
+}
+
+BOOST_AUTO_TEST_CASE(test_parseXPFPayload) {
+
+  /* invalid sizes */
+  {
+    ComboAddress source;
+    ComboAddress destination;
+
+    BOOST_CHECK_EQUAL(parseXPFPayload(nullptr, 0, source, &destination), false);
+    BOOST_CHECK_EQUAL(parseXPFPayload(nullptr, 13, source, &destination), false);
+    BOOST_CHECK_EQUAL(parseXPFPayload(nullptr, 15, source, &destination), false);
+    BOOST_CHECK_EQUAL(parseXPFPayload(nullptr, 37, source, &destination), false);
+    BOOST_CHECK_EQUAL(parseXPFPayload(nullptr, 39, source, &destination), false);
+  }
+
+
+  {
+    /* invalid protocol */
+    ComboAddress source("[2001:db8::1]:42");
+    ComboAddress destination("[::1]:65535");
+
+    auto payload = generateXPFPayload(true, source, destination);
+    /* set protocol to 0 */
+    payload.at(1) = 0;
+
+    ComboAddress parsedSource;
+    ComboAddress parsedDestination;
+    BOOST_CHECK_EQUAL(parseXPFPayload(payload.c_str(), payload.size(), parsedSource, &parsedDestination), false);
+  }
+
+  {
+    /* invalid version */
+    ComboAddress source("[2001:db8::1]:42");
+    ComboAddress destination("[::1]:65535");
+
+    auto payload = generateXPFPayload(true, source, destination);
+    /* set version to 0 */
+    payload.at(0) = 0;
+
+    ComboAddress parsedSource;
+    ComboAddress parsedDestination;
+    BOOST_CHECK_EQUAL(parseXPFPayload(payload.c_str(), payload.size(), parsedSource, &parsedDestination), false);
+  }
+
+  {
+    /* payload too short (v6 size with v4 payload) */
+    ComboAddress source("192.0.2.1:53");
+    ComboAddress destination("192.0.2.2:65535");
+
+
+    auto payload = generateXPFPayload(true, source, destination);
+    /* set version to 6 */
+    payload.at(0) = 6;
+
+    ComboAddress parsedSource;
+    ComboAddress parsedDestination;
+    BOOST_CHECK_EQUAL(parseXPFPayload(payload.c_str(), payload.size(), parsedSource, &parsedDestination), false);
+  }
+
+  {
+    /* payload too long (v6 size with v4 payload) */
+    ComboAddress source("[2001:db8::1]:42");
+    ComboAddress destination("[::1]:65535");
+
+
+    auto payload = generateXPFPayload(true, source, destination);
+    /* set version to 4 */
+    payload.at(0) = 4;
+
+    ComboAddress parsedSource;
+    ComboAddress parsedDestination;
+    BOOST_CHECK_EQUAL(parseXPFPayload(payload.c_str(), payload.size(), parsedSource, &parsedDestination), false);
+  }
+
+  {
+    /* v4 payload over UDP */
+    ComboAddress source("192.0.2.1:53");
+    ComboAddress destination("192.0.2.2:65535");
+
+    auto payload = generateXPFPayload(false, source, destination);
+    BOOST_CHECK_EQUAL(payload.size(), 14);
+    BOOST_CHECK_EQUAL(payload.at(0), 4);
+    BOOST_CHECK_EQUAL(payload.at(1), 17);
+
+    ComboAddress parsedSource;
+    BOOST_CHECK(parseXPFPayload(payload.c_str(), payload.size(), parsedSource, nullptr));
+    BOOST_CHECK_EQUAL(parsedSource.toStringWithPort(), source.toStringWithPort());
+  }
+
+}
+
+
+BOOST_AUTO_TEST_SUITE_END()
diff --git a/pdns/recursordist/xpf.cc b/pdns/recursordist/xpf.cc
new file mode 120000 (symlink)
index 0000000..98ab722
--- /dev/null
@@ -0,0 +1 @@
+../xpf.cc
\ No newline at end of file
diff --git a/pdns/recursordist/xpf.hh b/pdns/recursordist/xpf.hh
new file mode 120000 (symlink)
index 0000000..023c8c4
--- /dev/null
@@ -0,0 +1 @@
+../xpf.hh
\ No newline at end of file
index b0ac82748f09bc955afce0e1fe1cba32c544fe1a..eb945e2fcd8abd1428e9dcfb67ff845b13c6424e 100644 (file)
@@ -53,7 +53,7 @@ void primeHints(void)
     arr.d_ttl=aaaarr.d_ttl=nsrr.d_ttl=time(0)+3600000;
     
     for(char c='a';c<='m';++c) {
-      static char templ[40];
+      char templ[40];
       strncpy(templ,"a.root-servers.net.", sizeof(templ) - 1);
       templ[sizeof(templ)-1] = '\0';
       *templ=c;
@@ -370,7 +370,7 @@ void RPZIXFRTracker(const ComboAddress& master, const DNSName& zoneName, boost::
        }
        else {
           totremove++;
-         L<<(g_logRPZChanges ? Logger::Info : Logger::Debug)<<"Had removal of "<<rr.d_name<<" from RPZ zone "<<zoneName.toStringNoDot()<<endl;
+         L<<(g_logRPZChanges ? Logger::Info : Logger::Debug)<<"Had removal of "<<rr.d_name<<" from RPZ zone "<<zoneName<<endl;
          RPZRecordToPolicy(rr, newZone, false, defpol, maxTTL);
        }
       }
@@ -387,7 +387,7 @@ void RPZIXFRTracker(const ComboAddress& master, const DNSName& zoneName, boost::
        }
        else {
           totadd++;
-         L<<(g_logRPZChanges ? Logger::Info : Logger::Debug)<<"Had addition of "<<rr.d_name<<" to RPZ zone "<<zoneName.toStringNoDot()<<endl;
+         L<<(g_logRPZChanges ? Logger::Info : Logger::Debug)<<"Had addition of "<<rr.d_name<<" to RPZ zone "<<zoneName<<endl;
          RPZRecordToPolicy(rr, newZone, true, defpol, maxTTL);
        }
       }
@@ -438,10 +438,10 @@ std::shared_ptr<SyncRes::domainmap_t> parseAuthAndForwards()
            dr.d_place=DNSResourceRecord::ANSWER;
           }
           catch(std::exception &e) {
-            throw PDNSException("Error parsing record '"+rr.qname.toString()+"' of type "+rr.qtype.getName()+" in zone '"+headers.first+"' from file '"+headers.second+"': "+e.what());
+            throw PDNSException("Error parsing record '"+rr.qname.toLogString()+"' of type "+rr.qtype.getName()+" in zone '"+headers.first+"' from file '"+headers.second+"': "+e.what());
           }
           catch(...) {
-            throw PDNSException("Error parsing record '"+rr.qname.toString()+"' of type "+rr.qtype.getName()+" in zone '"+headers.first+"' from file '"+headers.second+"'");
+            throw PDNSException("Error parsing record '"+rr.qname.toLogString()+"' of type "+rr.qtype.getName()+" in zone '"+headers.first+"' from file '"+headers.second+"'");
           }
 
           ad.d_records.insert(dr);
index 07ab13e13319db2dbe699e886057db1d74b373d8..9844cc132a953a75d80ecbae4730d2e3c342ce87 100644 (file)
@@ -14,6 +14,7 @@ bool RemoteLogger::reconnect()
     close(d_socket);
     d_socket = -1;
   }
+  d_connected = false;
   try {
     d_socket = SSocket(d_remote.sin4.sin_family, SOCK_STREAM, 0);
     setNonBlocking(d_socket);
@@ -27,13 +28,21 @@ bool RemoteLogger::reconnect()
 #endif
     return false;
   }
+  d_connected = true;
   return true;
 }
 
+void RemoteLogger::busyReconnectLoop()
+{
+  while (!reconnect()) {
+    sleep(d_reconnectWaitTime);
+  }
+}
+
 void RemoteLogger::worker()
 {
   if (d_asyncConnect) {
-    reconnect();
+    busyReconnectLoop();
   }
 
   while(true) {
@@ -48,6 +57,10 @@ void RemoteLogger::worker()
       d_writeQueue.pop();
     }
 
+    if (!d_connected) {
+      busyReconnectLoop();
+    }
+
     try {
       uint16_t len = static_cast<uint16_t>(data.length());
       sendSizeAndMsgWithTimeout(d_socket, len, data.c_str(), static_cast<int>(d_timeout), nullptr, nullptr, 0, 0, 0);
@@ -58,9 +71,7 @@ void RemoteLogger::worker()
 #else
       vinfolog("Error sending data to remote logger (%s): %s", d_remote.toStringWithPort(), e.what());
 #endif
-      while (!reconnect()) {
-        sleep(d_reconnectWaitTime);
-      }
+      busyReconnectLoop();
     }
   }
 }
@@ -90,6 +101,7 @@ RemoteLogger::~RemoteLogger()
   if (d_socket >= 0) {
     close(d_socket);
     d_socket = -1;
+    d_connected = false;
   }
   d_queueCond.notify_one();
   d_thread.join();
index 33f0bbaef2c6b6c59be82cb4b1b4d94f08ef9b4e..e4d420243a059832678d808d7db0e1b8cb358a8a 100644 (file)
@@ -42,6 +42,7 @@ public:
     return d_remote.toStringWithPort();
   }
 private:
+  void busyReconnectLoop();
   bool reconnect();
   void worker();
 
@@ -55,5 +56,6 @@ private:
   uint8_t d_reconnectWaitTime;
   std::atomic<bool> d_exiting{false};
   bool d_asyncConnect{false};
+  bool d_connected{false};
   std::thread d_thread;
 };
index 51d81160c49af4e11556fa30784d8833b97ee9cc..81eabdfea634537da3459596293707812f230221 100644 (file)
@@ -119,7 +119,7 @@ Resolver::~Resolver()
 }
 
 uint16_t Resolver::sendResolve(const ComboAddress& remote, const ComboAddress& local,
-                               const DNSName &domain, int type, bool dnssecOK,
+                               const DNSName &domain, int type, int *localsock, bool dnssecOK,
                                const DNSName& tsigkeyname, const DNSName& tsigalgorithm,
                                const string& tsigsecret)
 {
@@ -156,36 +156,29 @@ uint16_t Resolver::sendResolve(const ComboAddress& remote, const ComboAddress& l
   } else {
     std::string lstr = local.toString();
     std::map<std::string, int>::iterator lptr;
-    // see if there is a local
 
+    // reuse an existing local socket or make a new one
     if ((lptr = locals.find(lstr)) != locals.end()) {
       sock = lptr->second;
     } else {
       // try to make socket
       sock = makeQuerySocket(local, true);
       if (sock < 0)
-        throw ResolverException("Unable to create socket to "+remote.toStringWithPort()+": "+stringerror());
+        throw ResolverException("Unable to create local socket on "+lstr+" to "+remote.toStringWithPort()+": "+stringerror());
       setNonBlocking( sock );
       locals[lstr] = sock;
     }
   }
 
+  if (localsock != nullptr) {
+    *localsock = sock;
+  }
   if(sendto(sock, &packet[0], packet.size(), 0, (struct sockaddr*)(&remote), remote.getSocklen()) < 0) {
     throw ResolverException("Unable to ask query of "+remote.toStringWithPort()+": "+stringerror());
   }
   return randomid;
 }
 
-uint16_t Resolver::sendResolve(const ComboAddress& remote, const DNSName &domain,
-                               int type, bool dnssecOK,
-                               const DNSName& tsigkeyname, const DNSName& tsigalgorithm,
-                               const string& tsigsecret)
-{
-  ComboAddress local;
-  local.sin4.sin_family = 0;
-  return this->sendResolve(remote, local, domain, type, dnssecOK, tsigkeyname, tsigalgorithm, tsigsecret);
-}
-
 static int parseResult(MOADNSParser& mdp, const DNSName& origQname, uint16_t origQtype, uint16_t id, Resolver::res_t* result)
 {
   result->clear();
@@ -199,7 +192,7 @@ static int parseResult(MOADNSParser& mdp, const DNSName& origQname, uint16_t ori
     if(mdp.d_header.qdcount != 1)
       throw ResolverException("resolver: received answer with wrong number of questions ("+itoa(mdp.d_header.qdcount)+")");
     if(mdp.d_qname != origQname)
-      throw ResolverException(string("resolver: received an answer to another question (")+mdp.d_qname.toString()+"!="+ origQname.toString()+".)");
+      throw ResolverException(string("resolver: received an answer to another question (")+mdp.d_qname.toLogString()+"!="+ origQname.toLogString()+".)");
   }
 
   vector<DNSResourceRecord> ret;
@@ -215,7 +208,7 @@ static int parseResult(MOADNSParser& mdp, const DNSName& origQname, uint16_t ori
   return 0;
 }
 
-bool Resolver::tryGetSOASerial(DNSName *domain, uint32_t *theirSerial, uint32_t *theirInception, uint32_t *theirExpire, uint16_t* id)
+bool Resolver::tryGetSOASerial(DNSName *domain, ComboAddress* remote, uint32_t *theirSerial, uint32_t *theirInception, uint32_t *theirExpire, uint16_t* id)
 {
   auto fds = std::unique_ptr<struct pollfd[]>(new struct pollfd[locals.size()]);
   size_t i = 0, k;
@@ -243,10 +236,10 @@ bool Resolver::tryGetSOASerial(DNSName *domain, uint32_t *theirSerial, uint32_t
   if (sock < 0) return false; // false alarm
 
   int err;
-  ComboAddress fromaddr;
-  socklen_t addrlen=fromaddr.getSocklen();
+  remote->sin6.sin6_family = AF_INET6; // make sure getSocklen() below returns a large enough value
+  socklen_t addrlen=remote->getSocklen();
   char buf[3000];
-  err = recvfrom(sock, buf, sizeof(buf), 0,(struct sockaddr*)(&fromaddr), &addrlen);
+  err = recvfrom(sock, buf, sizeof(buf), 0,(struct sockaddr*)(remote), &addrlen);
   if(err < 0) {
     if(errno == EAGAIN)
       return false;
@@ -259,13 +252,13 @@ bool Resolver::tryGetSOASerial(DNSName *domain, uint32_t *theirSerial, uint32_t
   *domain = mdp.d_qname;
   
   if(domain->empty())
-    throw ResolverException("SOA query to '" + fromaddr.toStringWithPort() + "' produced response without domain name (RCode: " + RCode::to_s(mdp.d_header.rcode) + ")");
+    throw ResolverException("SOA query to '" + remote->toStringWithPort() + "' produced response without domain name (RCode: " + RCode::to_s(mdp.d_header.rcode) + ")");
 
   if(mdp.d_answers.empty())
-    throw ResolverException("Query to '" + fromaddr.toStringWithPort() + "' for SOA of '" + domain->toString() + "' produced no results (RCode: " + RCode::to_s(mdp.d_header.rcode) + ")");
+    throw ResolverException("Query to '" + remote->toStringWithPort() + "' for SOA of '" + domain->toLogString() + "' produced no results (RCode: " + RCode::to_s(mdp.d_header.rcode) + ")");
   
   if(mdp.d_qtype != QType::SOA)
-    throw ResolverException("Query to '" + fromaddr.toStringWithPort() + "' for SOA of '" + domain->toString() + "' returned wrong record type");
+    throw ResolverException("Query to '" + remote->toStringWithPort() + "' for SOA of '" + domain->toLogString() + "' returned wrong record type");
 
   *theirInception = *theirExpire = 0;
   bool gotSOA=false;
@@ -286,7 +279,7 @@ bool Resolver::tryGetSOASerial(DNSName *domain, uint32_t *theirSerial, uint32_t
     }
   }
   if(!gotSOA)
-    throw ResolverException("Query to '" + fromaddr.toString() + "' for SOA of '" + domain->toString() + "' did not return a SOA");
+    throw ResolverException("Query to '" + remote->toString() + "' for SOA of '" + domain->toLogString() + "' did not return a SOA");
   return true;
 }
 
@@ -295,22 +288,8 @@ int Resolver::resolve(const string &ipport, const DNSName &domain, int type, Res
   try {
     ComboAddress to(ipport, 53);
 
-    int id = sendResolve(to, local, domain, type);
-    int sock;
-
-    // choose socket based on local
-    if (local.sin4.sin_family == 0) {
-      // up to us.
-      sock = to.sin4.sin_family == AF_INET ? locals["default4"] : locals["default6"];
-    } else {
-      std::string lstr = local.toString();
-      std::map<std::string, int>::iterator lptr;
-      // see if there is a local
-
-      if ((lptr = locals.find(lstr)) != locals.end()) sock = lptr->second;
-      else throw ResolverException("sendResolve did not create socket for " + lstr);
-    }
-
+    int sock = -1;
+    int id = sendResolve(to, local, domain, type, &sock);
     int err=waitForData(sock, 0, 3000000); 
   
     if(!err) {
@@ -323,10 +302,14 @@ int Resolver::resolve(const string &ipport, const DNSName &domain, int type, Res
     socklen_t addrlen = sizeof(from);
     char buffer[3000];
     int len;
-    
+
     if((len=recvfrom(sock, buffer, sizeof(buffer), 0,(struct sockaddr*)(&from), &addrlen)) < 0) 
       throw ResolverException("recvfrom error waiting for answer: "+stringerror());
-  
+
+    if (from != to) {
+      throw ResolverException("Got answer from the wrong peer while resolving ("+from.toStringWithPort()+" instead of "+to.toStringWithPort()+", discarding");
+    }
+
     MOADNSParser mdp(false, buffer, len);
     return parseResult(mdp, domain, type, id, res);
   }
@@ -348,21 +331,21 @@ void Resolver::getSoaSerial(const string &ipport, const DNSName &domain, uint32_
   int ret = resolve(ipport, domain, QType::SOA, &res);
   
   if(ret || res.empty())
-    throw ResolverException("Query to '" + ipport + "' for SOA of '" + domain.toString() + "' produced no answers");
+    throw ResolverException("Query to '" + ipport + "' for SOA of '" + domain.toLogString() + "' produced no answers");
 
   if(res[0].qtype.getCode() != QType::SOA) 
-    throw ResolverException("Query to '" + ipport + "' for SOA of '" + domain.toString() + "' produced a "+res[0].qtype.getName()+" record");
+    throw ResolverException("Query to '" + ipport + "' for SOA of '" + domain.toLogString() + "' produced a "+res[0].qtype.getName()+" record");
 
   vector<string>parts;
   stringtok(parts, res[0].content);
   if(parts.size()<3)
-    throw ResolverException("Query to '" + ipport + "' for SOA of '" + domain.toString() + "' produced an unparseable response");
+    throw ResolverException("Query to '" + ipport + "' for SOA of '" + domain.toLogString() + "' produced an unparseable response");
 
   try {
     *serial=pdns_stou(parts[2]);
   }
   catch(const std::out_of_range& oor) {
-    throw ResolverException("Query to '" + ipport + "' for SOA of '" + domain.toString() + "' produced an unparseable serial");
+    throw ResolverException("Query to '" + ipport + "' for SOA of '" + domain.toLogString() + "' produced an unparseable serial");
   }
 }
 
index e6c6e7ae5622d8869bba1b86d1320656481a7e00..c9946ccf810ca7319aa3f9c17f820a8ce95d489b 100644 (file)
@@ -65,14 +65,11 @@ public:
   int resolve(const string &ip, const DNSName &domain, int type, res_t* result);
 
   //! only send out a resolution request
-  uint16_t sendResolve(const ComboAddress& remote, const ComboAddress& local, const DNSName &domain, int type, bool dnssecOk=false,
-    const DNSName& tsigkeyname=DNSName(), const DNSName& tsigalgorithm=DNSName(), const string& tsigsecret="");
-
-  uint16_t sendResolve(const ComboAddress& remote, const DNSName &domain, int type, bool dnssecOk=false,
+  uint16_t sendResolve(const ComboAddress& remote, const ComboAddress& local, const DNSName &domain, int type, int *localsock, bool dnssecOk=false,
     const DNSName& tsigkeyname=DNSName(), const DNSName& tsigalgorithm=DNSName(), const string& tsigsecret="");
 
   //! see if we got a SOA response from our sendResolve
-  bool tryGetSOASerial(DNSName *theirDomain, uint32_t* theirSerial, uint32_t* theirInception, uint32_t* theirExpire, uint16_t* id);
+  bool tryGetSOASerial(DNSName *theirDomain, ComboAddress* remote, uint32_t* theirSerial, uint32_t* theirInception, uint32_t* theirExpire, uint16_t* id);
   
   //! convenience function that calls resolve above
   void getSoaSerial(const string &, const DNSName &, uint32_t *);
index b4d0ab963c1a25b19763f82ae4456e1dfa6b5dc4..f262517ea724cb6a0e920d0f949f00fa25b2ee78 100644 (file)
@@ -833,7 +833,7 @@ int PacketHandler::processUpdate(DNSPacket *p) {
     if (rr->d_place == DNSResourceRecord::ANSWER) {
       int res = checkUpdatePrerequisites(rr, &di);
       if (res>0) {
-        L<<Logger::Error<<msgPrefix<<"Failed PreRequisites check, returning "<<res<<endl;
+        L<<Logger::Error<<msgPrefix<<"Failed PreRequisites check, returning "<<RCode::to_s(res)<<endl;
         di.backend->abortTransaction();
         return res;
       }
@@ -880,7 +880,7 @@ int PacketHandler::processUpdate(DNSPacket *p) {
         }
       }
       if (matchRR != foundRR || foundRR != vec->size()) {
-        L<<Logger::Error<<msgPrefix<<"Failed PreRequisites check, returning NXRRSet"<<endl;
+        L<<Logger::Error<<msgPrefix<<"Failed PreRequisites check (RRs differ), returning NXRRSet"<<endl;
         di.backend->abortTransaction();
         return RCode::NXRRSet;
       }
@@ -1015,22 +1015,14 @@ int PacketHandler::processUpdate(DNSPacket *p) {
 }
 
 void PacketHandler::increaseSerial(const string &msgPrefix, const DomainInfo *di, bool haveNSEC3, bool narrow, const NSEC3PARAMRecordContent *ns3pr) {
-  DNSResourceRecord rec, newRec;
-  di->backend->lookup(QType(QType::SOA), di->zone);
-  bool foundSOA=false;
-  while (di->backend->get(rec)) {
-    newRec = rec;
-    foundSOA=true;
-  }
-  if (!foundSOA) {
+  SOAData sd;
+  if (!di->backend->getSOA(di->zone, sd, true)) {
     throw PDNSException("SOA-Serial update failed because there was no SOA. Wowie.");
   }
-  SOAData soa2Update;
-  fillSOAData(rec.content, soa2Update);
-  uint32_t oldSerial = soa2Update.serial;
 
+  uint32_t oldSerial = sd.serial;
   if (oldSerial == 0) { // using Autoserial, leave the serial alone.
-    L<<Logger::Notice<<msgPrefix<<"AutoSerial being used, not updating SOA serial."<<endl;
+    L<<Logger::Notice<<msgPrefix<<"AutoSerial in use in domain \""<<di->zone.toLogString()<<"\", not updating SOA serial."<<endl;
     return;
   }
 
@@ -1044,33 +1036,28 @@ void PacketHandler::increaseSerial(const string &msgPrefix, const DomainInfo *di
       string soaEditSetting;
       d_dk.getSoaEdit(di->zone, soaEditSetting);
       if (soaEditSetting.empty()) {
-        L<<Logger::Error<<msgPrefix<<"Using "<<soaEdit2136<<" for SOA-EDIT-DNSUPDATE increase on DNS update, but SOA-EDIT is not set for domain \""<< di->zone <<"\". Using DEFAULT for SOA-EDIT-DNSUPDATE"<<endl;
+        L<<Logger::Error<<msgPrefix<<"Using "<<soaEdit2136<<" for SOA-EDIT-DNSUPDATE increase on DNS update, but SOA-EDIT is not set for domain \""<< di->zone.toLogString() <<"\". Using DEFAULT for SOA-EDIT-DNSUPDATE"<<endl;
         soaEdit2136 = "DEFAULT";
       } else
         soaEdit = soaEditSetting;
     }
   }
 
-  soa2Update.serial = calculateIncreaseSOA(soa2Update, soaEdit2136, soaEdit);
-
-  newRec.content = serializeSOAData(soa2Update);
-  vector<DNSResourceRecord> rrset;
-  rrset.push_back(newRec);
-  di->backend->replaceRRSet(di->id, newRec.qname, newRec.qtype, rrset);
-  L<<Logger::Notice<<msgPrefix<<"Increasing SOA serial ("<<oldSerial<<" -> "<<soa2Update.serial<<")"<<endl;
-
-  //Correct ordername + auth flag
-  if (haveNSEC3 && narrow)
-    di->backend->updateDNSSECOrderNameAndAuth(di->id, newRec.qname, DNSName(), true);
-  else if (haveNSEC3) {
-    DNSName ordername;
-    if (!narrow)
-      ordername = DNSName(toBase32Hex(hashQNameWithSalt(*ns3pr, newRec.qname)));
-
-    di->backend->updateDNSSECOrderNameAndAuth(di->id, newRec.qname, ordername, true);
-  }
-  else { // NSEC
-    DNSName ordername=newRec.qname.makeRelative(di->zone);
-    di->backend->updateDNSSECOrderNameAndAuth(di->id, newRec.qname, ordername, true);
+  DNSResourceRecord rr;
+  if (makeIncreasedSOARecord(sd, soaEdit2136, soaEdit, rr)) {
+    di->backend->replaceRRSet(di->id, rr.qname, rr.qtype, vector<DNSResourceRecord>(1, rr));
+    L << Logger::Notice << msgPrefix << "Increasing SOA serial (" << oldSerial << " -> " << sd.serial << ")" << endl;
+
+    //Correct ordername + auth flag
+    if (haveNSEC3) {
+      DNSName ordername;
+      if (!narrow)
+        ordername = DNSName(toBase32Hex(hashQNameWithSalt(*ns3pr, rr.qname)));
+
+      di->backend->updateDNSSECOrderNameAndAuth(di->id, rr.qname, ordername, true);
+    } else { // NSEC
+      DNSName ordername = rr.qname.makeRelative(di->zone);
+      di->backend->updateDNSSECOrderNameAndAuth(di->id, rr.qname, ordername, true);
+    }
   }
 }
index b5977d9d0467d0976c420844b25fb002ba1ae43f..4ed461273de2564cbb1cfac9160b558e7f37eb78 100644 (file)
@@ -16,7 +16,7 @@ static Netmask makeNetmaskFromRPZ(const DNSName& name)
    * Terrible right?
    */
   if(parts.size() < 2 || parts.size() > 9)
-    throw PDNSException("Invalid IP address in RPZ: "+name.toString());
+    throw PDNSException("Invalid IP address in RPZ: "+name.toLogString());
 
   bool isV6 = (stoi(parts[0]) > 32);
   bool hadZZ = false;
@@ -29,7 +29,7 @@ static Netmask makeNetmaskFromRPZ(const DNSName& name)
 
     if (pdns_iequals(part,"zz")) {
       if (hadZZ)
-        throw PDNSException("more than one 'zz' label found in RPZ name"+name.toString());
+        throw PDNSException("more than one 'zz' label found in RPZ name"+name.toLogString());
       part = "";
       isV6 = true;
       hadZZ = true;
@@ -37,7 +37,7 @@ static Netmask makeNetmaskFromRPZ(const DNSName& name)
   }
 
   if (isV6 && parts.size() < 9 && !hadZZ)
-    throw PDNSException("No 'zz' label found in an IPv6 RPZ name shorter than 9 elements: "+name.toString());
+    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]);
@@ -109,7 +109,7 @@ void RPZRecordToPolicy(const DNSRecord& dr, std::shared_ptr<DNSFilterEngine::Zon
     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. */
-      L<<Logger::Info<<"Discarding unsupported RPZ entry "<<crcTarget.toString()<<" for "<<dr.d_name<<endl;
+      L<<Logger::Info<<"Discarding unsupported RPZ entry "<<crcTarget<<" for "<<dr.d_name<<endl;
       return;
     }
     else {
@@ -238,7 +238,7 @@ void loadRPZFromFile(const std::string& fname, std::shared_ptr<DNSFilterEngine::
       }
     }
     catch(const PDNSException& pe) {
-      throw PDNSException("Issue parsing '"+drr.qname.toString()+"' '"+drr.content+"' at "+zpt.getLineOfFile()+": "+pe.reason);
+      throw PDNSException("Issue parsing '"+drr.qname.toLogString()+"' '"+drr.content+"' at "+zpt.getLineOfFile()+": "+pe.reason);
     }
   }
 }
index bcd2bbeb0cd582a8b1fa2943beaaddf2951cc85e..70b2e50e27366a4ceaa8e0045ef01d0a37004653 100644 (file)
@@ -2,6 +2,7 @@
 #include "config.h"
 #endif
 #include "dnsparser.hh"
+#include "ednsoptions.hh"
 #include "sstuff.hh"
 #include "misc.hh"
 #include "dnswriter.hh"
@@ -23,7 +24,7 @@ string ttl(uint32_t ttl)
 
 void usage() {
   cerr<<"sdig"<<endl;
-  cerr<<"Syntax: sdig IP-ADDRESS PORT QUESTION QUESTION-TYPE [dnssec] [recurse] [showflags] [hidesoadetails] [hidettl] [tcp] [ednssubnet SUBNET/MASK]"<<endl;
+  cerr<<"Syntax: sdig IP-ADDRESS PORT QUESTION QUESTION-TYPE [dnssec] [recurse] [showflags] [hidesoadetails] [hidettl] [tcp] [ednssubnet SUBNET/MASK] [xpf XPFDATA]"<<endl;
 }
 
 int main(int argc, char** argv)
@@ -35,7 +36,8 @@ try
   bool showflags=false;
   bool hidesoadetails=false;
   boost::optional<Netmask> ednsnm;
-
+  uint16_t xpfcode = 0, xpfversion = 0, xpfproto = 0;
+  char *xpfsrc = NULL, *xpfdst = NULL;
 
   for(int i=1; i<argc; i++) {
     if ((string) argv[i] == "--help") {
@@ -71,8 +73,23 @@ try
       if (strcmp(argv[i], "tcp") == 0)
         tcp=true;
       if (strcmp(argv[i], "ednssubnet") == 0) {
+        if(argc < i+2) {
+          cerr<<"ednssubnet needs an argument"<<endl;
+          exit(EXIT_FAILURE);
+        }
         ednsnm=Netmask(argv[++i]);
       }
+      if (strcmp(argv[i], "xpf") == 0) {
+        if(argc < i+6) {
+          cerr<<"xpf needs five arguments"<<endl;
+          exit(EXIT_FAILURE);
+        }
+        xpfcode = atoi(argv[++i]);
+        xpfversion = atoi(argv[++i]);
+        xpfproto = atoi(argv[++i]);
+        xpfsrc = argv[++i];
+        xpfdst = argv[++i];
+      }
     }
   }
 
@@ -92,13 +109,27 @@ try
     if(ednsnm) {
       EDNSSubnetOpts eo;
       eo.source = *ednsnm;
-      opts.push_back(make_pair(8, makeEDNSSubnetOptsString(eo)));
+      opts.push_back(make_pair(EDNSOptionCode::ECS, makeEDNSSubnetOptsString(eo)));
     }
 
     pw.addOpt(bufsize, 0, dnssec ? EDNSOpts::DNSSECOK : 0, opts);
     pw.commit();
   }
 
+  if(xpfcode)
+  {
+    ComboAddress src(xpfsrc), dst(xpfdst);
+    pw.startRecord(DNSName("."), xpfcode, 0, 1, DNSResourceRecord::ADDITIONAL);
+    // xpf->toPacket(pw);
+    pw.xfr8BitInt(xpfversion);
+    pw.xfr8BitInt(xpfproto);
+    pw.xfrCAWithoutPort(xpfversion, src);
+    pw.xfrCAWithoutPort(xpfversion, dst);
+    pw.xfrCAPort(src);
+    pw.xfrCAPort(dst);
+    pw.commit();
+  }
+
   if(recurse)
   {
     pw.getHeader()->rd=true;
@@ -196,12 +227,7 @@ try
     for(vector<pair<uint16_t, string> >::const_iterator iter = edo.d_options.begin();
         iter != edo.d_options.end(); 
         ++iter) {
-      if(iter->first == 5) {// 'EDNS PING'
-        cerr<<"Have ednsping: '"<<iter->second<<"'\n";
-        //if(iter->second == ping) 
-         // cerr<<"It is correct!"<<endl;
-      }
-      if(iter->first == 8) {// 'EDNS subnet'
+      if(iter->first == EDNSOptionCode::ECS) {// 'EDNS subnet'
        EDNSSubnetOpts reso;
         if(getEDNSSubnetOptsFromString(iter->second, &reso)) {
           cerr<<"EDNS Subnet response: "<<reso.source.toString()<<", scope: "<<reso.scope.toString()<<", family = "<<reso.scope.getNetwork().sin4.sin_family<<endl;
index fa7a0fd69f3cdedf0d2de08f42460993a85bfa52..06a036536bf073fe9f76e07b16d140e5e08b86ec 100644 (file)
@@ -27,7 +27,6 @@
 #include "dnspacket.hh"
 #include "namespaces.hh"
 
-
 uint32_t localtime_format_YYYYMMDDSS(time_t t, uint32_t seq)
 {
   struct tm tm;
@@ -39,124 +38,116 @@ uint32_t localtime_format_YYYYMMDDSS(time_t t, uint32_t seq)
     + seq;
 }
 
-bool editSOA(DNSSECKeeper& dk, const DNSName& qname, DNSPacket* dp)
-{
-  for(auto& rr :  dp->getRRS()) {
-    if(rr.dr.d_type == QType::SOA && rr.dr.d_name == qname) {
-      string kind;
-      dk.getSoaEdit(qname, kind);
-      return editSOARecord(rr, kind);
-    }
-  }
-  return false;
-}
-
-bool editSOARecord(DNSZoneRecord& rr, const string& kind) {
-  if(kind.empty())
-    return false;
-  auto src = getRR<SOARecordContent>(rr.dr);
-  src->d_st.serial=calculateEditSOA(rr, kind);
-
-  return true;
-}
-
-uint32_t calculateEditSOA(const DNSZoneRecord& rr, const string& kind)
+uint32_t calculateEditSOA(uint32_t old_serial, const string& kind, const DNSName& zonename)
 {
-  auto src = getRR<SOARecordContent>(rr.dr);
   if(pdns_iequals(kind,"INCEPTION-INCREMENT")) {
     time_t inception = getStartOfWeek();
     uint32_t inception_serial = localtime_format_YYYYMMDDSS(inception, 1);
     uint32_t dont_increment_after = localtime_format_YYYYMMDDSS(inception + 2*86400, 99);
 
-    if(src->d_st.serial < inception_serial - 1) { /* less than <inceptionday>00 */
+    if(old_serial < inception_serial - 1) { /* less than <inceptionday>00 */
       return inception_serial; /* return <inceptionday>01   (skipping <inceptionday>00 as possible value) */
-    } else if(src->d_st.serial <= dont_increment_after) { /* >= <inceptionday>00 but <= <inceptionday+2>99 */
-      return (src->d_st.serial + 2); /* "<inceptionday>00" and "<inceptionday>01" are reserved for inception increasing, so increment sd.serial by two */
+    } else if (old_serial < inception_serial+1) {
+      /* "<inceptionday>00" and "<inceptionday>01" are reserved for inception increasing, so jump to "<inceptionday>02" */
+      return inception_serial+1;
+    } else if(old_serial <= dont_increment_after) { /* >= <inceptionday>00 but <= <inceptionday+2>99 */
+      return old_serial + 1;
     }
   }
   else if(pdns_iequals(kind,"INCREMENT-WEEKS")) {
     time_t inception = getStartOfWeek();
-    return (src->d_st.serial + (inception / (7*86400)));
+    return (old_serial + (inception / (7*86400)));
   }
   else if(pdns_iequals(kind,"EPOCH")) {
     return time(0);
   }
   else if(pdns_iequals(kind,"INCEPTION-EPOCH")) {
     uint32_t inception = getStartOfWeek();
-    if (src->d_st.serial < inception)
+    if (old_serial < inception)
       return inception;
   } else if(!kind.empty()) {
-    L<<Logger::Warning<<"SOA-EDIT type '"<<kind<<"' for zone "<<rr.dr.d_name<<" is unknown."<<endl;
+    L<<Logger::Warning<<"SOA-EDIT type '"<<kind<<"' for zone "<<zonename<<" is unknown."<<endl;
   }
-  return src->d_st.serial;
+  return old_serial;
 }
 
-uint32_t calculateEditSOA(const SOAData& sd, const string& kind)
-{
-  DNSZoneRecord dzr;
-  dzr.dr.d_name=sd.qname;
-  struct soatimes st;
-  st.serial = sd.serial;
-  dzr.dr.d_content = std::make_shared<SOARecordContent>(sd.nameserver, sd.hostmaster, st);
-  return calculateEditSOA(dzr, kind);
+uint32_t calculateEditSOA(uint32_t old_serial, DNSSECKeeper& dk, const DNSName& zonename) {
+  string kind;
+  dk.getSoaEdit(zonename, kind);
+  if(kind.empty())
+    return old_serial;
+  return calculateEditSOA(old_serial, kind, zonename);
 }
 
-// Used for SOA-EDIT-DNSUPDATE and SOA-EDIT-API.
-uint32_t calculateIncreaseSOA(DNSZoneRecord& dzr, const string& increaseKind, const string& editKind) {
-  auto src = getRR<SOARecordContent>(dzr.dr);
-  // These only work when SOA-EDIT is set, otherwise fall back to default.
-  if (!editKind.empty()) {
-    if (pdns_iequals(increaseKind, "SOA-EDIT-INCREASE")) {
-      uint32_t new_serial = calculateEditSOA(dzr, editKind);
-      if (new_serial <= src->d_st.serial) {
-        new_serial = src->d_st.serial + 1;
-      }
-      return new_serial;
+/** Used for SOA-EDIT-DNSUPDATE and SOA-EDIT-API. */
+static uint32_t calculateIncreaseSOA(uint32_t old_serial, const string& increaseKind, const string& editKind, const DNSName& zonename) {
+  if (pdns_iequals(increaseKind, "SOA-EDIT-INCREASE")) {
+    uint32_t new_serial = old_serial;
+    if (!editKind.empty()) {
+      new_serial = calculateEditSOA(old_serial, editKind, zonename);
     }
-    else if (pdns_iequals(increaseKind, "SOA-EDIT")) {
-      return calculateEditSOA(dzr, editKind);
+    if (new_serial <= old_serial) {
+      new_serial = old_serial + 1;
     }
+    return new_serial;
   }
-
-  if (pdns_iequals(increaseKind, "INCREASE")) {
-    return src->d_st.serial + 1;
+  else if (pdns_iequals(increaseKind, "SOA-EDIT")) {
+    return calculateEditSOA(old_serial, editKind, zonename);
+  }
+  else if (pdns_iequals(increaseKind, "INCREASE")) {
+    return old_serial + 1;
   }
   else if (pdns_iequals(increaseKind, "EPOCH")) {
     return time(0);
   }
-
-  // DEFAULT case
-  time_t now = time(0);
-  struct tm tm;
-  localtime_r(&now, &tm);
-  boost::format fmt("%04d%02d%02d%02d");
-  string newdate = (fmt % (tm.tm_year + 1900) % (tm.tm_mon + 1) % tm.tm_mday % 1).str();
-  uint32_t new_serial = pdns_stou(newdate);
-  if (new_serial <= src->d_st.serial) {
-    new_serial = src->d_st.serial + 1;
+  else if (pdns_iequals(increaseKind, "DEFAULT")) {
+    time_t now = time(0);
+    uint32_t new_serial = localtime_format_YYYYMMDDSS(now, 1);
+    if (new_serial <= old_serial) {
+        new_serial = old_serial + 1;
+    }
+    return new_serial;
+  } else if(!increaseKind.empty()) {
+    L<<Logger::Warning<<"SOA-EDIT-API/DNSUPDATE type '"<<increaseKind<<"' for zone "<<zonename<<" is unknown."<<endl;
   }
-  return new_serial;
-}
-
-// Used for SOA-EDIT-DNSUPDATE and SOA-EDIT-API.
-uint32_t calculateIncreaseSOA(SOAData sd, const string& increaseKind, const string& editKind) {
-  DNSZoneRecord dzr;
-  dzr.dr.d_name=sd.qname;
-  struct soatimes st;
-  st.serial = sd.serial;
-  dzr.dr.d_content = std::make_shared<SOARecordContent>(sd.nameserver, sd.hostmaster, st);
-  return calculateIncreaseSOA(dzr, increaseKind, editKind);
+  return old_serial;
 }
 
-
-
+/** Used for SOA-EDIT-DNSUPDATE and SOA-EDIT-API.
+ * Good if you already *have* a DNSResourceRecord.
+ * Content in rr is suitable for writing into a backend.
+ *
+ * @return true if changes may have been made
+ */
 bool increaseSOARecord(DNSResourceRecord& rr, const string& increaseKind, const string& editKind) {
   if (increaseKind.empty())
     return false;
 
   SOAData sd;
   fillSOAData(rr.content, sd);
-  sd.serial = calculateIncreaseSOA(sd, increaseKind, editKind);
-  rr.content = serializeSOAData(sd);
+
+  sd.serial = calculateIncreaseSOA(sd.serial, increaseKind, editKind, rr.qname);
+  rr.content = makeSOAContent(sd)->getZoneRepresentation(true);
+  return true;
+}
+
+/** Used for SOA-EDIT-DNSUPDATE and SOA-EDIT-API.
+ * Makes a mostly reset DNSResourceRecord for you in @param rrout.
+ * Content in rrout is suitable for writing into a backend.
+ *
+ * @return true if rrout is now valid
+ */
+bool makeIncreasedSOARecord(SOAData& sd, const string& increaseKind, const string& editKind, DNSResourceRecord& rrout) {
+  if (increaseKind.empty())
+    return false;
+
+  sd.serial = calculateIncreaseSOA(sd.serial, increaseKind, editKind, sd.qname);
+  rrout.qname = sd.qname;
+  rrout.content = makeSOAContent(sd)->getZoneRepresentation(true);
+  rrout.qtype = QType::SOA;
+  rrout.domain_id = sd.domain_id;
+  rrout.auth = 1;
+  rrout.ttl = sd.ttl;
+
   return true;
 }
index 8c11d6758b17a3029c621dc5f0c6bd5d88b69616..748522b41a734c2d7ecaa8d215fc2f980c043d19 100644 (file)
@@ -626,12 +626,6 @@ void CommunicatorClass::suck(const DNSName &domain, const string &remote)
   }
 }
 namespace {
-struct QueryInfo
-{
-  struct timeval query_ttd;
-  uint16_t id;
-};
-
 struct DomainNotificationInfo
 {
   DomainInfo di;
@@ -645,7 +639,7 @@ struct DomainNotificationInfo
 
 struct SlaveSenderReceiver
 {
-  typedef pair<DNSName, uint16_t> Identifier;
+  typedef std::tuple<DNSName, ComboAddress, uint16_t> Identifier;
 
   struct Answer {
     uint32_t theirSerial;
@@ -667,31 +661,25 @@ struct SlaveSenderReceiver
   {
     random_shuffle(dni.di.masters.begin(), dni.di.masters.end());
     try {
-      ComboAddress remote(*dni.di.masters.begin());
-      if (dni.localaddr.sin4.sin_family == 0) {
-        return make_pair(dni.di.zone,
-          d_resolver.sendResolve(ComboAddress(*dni.di.masters.begin(), 53),
-            dni.di.zone,
-            QType::SOA,
-            dni.dnssecOk, dni.tsigkeyname, dni.tsigalgname, dni.tsigsecret)
+      ComboAddress remote(*dni.di.masters.begin(), 53);
+      return std::make_tuple(dni.di.zone,
+                             remote,
+                             d_resolver.sendResolve(remote,
+                                                    dni.localaddr,
+                                                    dni.di.zone,
+                                                    QType::SOA,
+                                                    nullptr,
+                                                    dni.dnssecOk, dni.tsigkeyname, dni.tsigalgname, dni.tsigsecret)
         );
-      } else {
-        return make_pair(dni.di.zone,
-          d_resolver.sendResolve(ComboAddress(*dni.di.masters.begin(), 53), dni.localaddr,
-            dni.di.zone,
-            QType::SOA,
-            dni.dnssecOk, dni.tsigkeyname, dni.tsigalgname, dni.tsigsecret)
-        );
-      }
     }
     catch(PDNSException& e) {
-      throw runtime_error("While attempting to query freshness of '"+dni.di.zone.toString()+"': "+e.reason);
+      throw runtime_error("While attempting to query freshness of '"+dni.di.zone.toLogString()+"': "+e.reason);
     }
   }
 
   bool receive(Identifier& id, Answer& a)
   {
-    if(d_resolver.tryGetSOASerial(&id.first, &a.theirSerial, &a.theirInception, &a.theirExpire, &id.second)) {
+    if(d_resolver.tryGetSOASerial(&(std::get<0>(id)), &(std::get<1>(id)), &a.theirSerial, &a.theirInception, &a.theirExpire, &(std::get<2>(id)))) {
       return 1;
     }
     return 0;
@@ -928,11 +916,3 @@ void CommunicatorClass::slaveRefresh(PacketHandler *P)
     }
   }
 }
-
-// stub for PowerDNSLua linking
-int directResolve(const std::string& qname, const QType& qtype, int qclass, vector<DNSResourceRecord>& ret)
-{
-  return -1;
-}
-
-
index 360eadcf084b9874fe4d78c1c0445765001fc6f4..b1b331003a247736f2ca3d056d955ea343be313e 100644 (file)
@@ -40,7 +40,8 @@ thread_local SyncRes::ThreadLocalStorage SyncRes::t_sstorage;
 
 std::unordered_set<DNSName> SyncRes::s_delegationOnly;
 std::unique_ptr<NetmaskGroup> SyncRes::s_dontQuery{nullptr};
-NetmaskGroup SyncRes::s_ednssubnets;
+NetmaskGroup SyncRes::s_ednslocalsubnets;
+NetmaskGroup SyncRes::s_ednsremotesubnets;
 SuffixMatchNode SyncRes::s_ednsdomains;
 EDNSSubnetOpts SyncRes::s_ecsScopeZero;
 string SyncRes::s_serverID;
@@ -687,7 +688,7 @@ vector<ComboAddress> SyncRes::getAddrs(const DNSName &qname, unsigned int depth,
     if(done) {
       if(j==1 && s_doIPv6) { // we got an A record, see if we have some AAAA lying around
        vector<DNSRecord> cset;
-       if(t_RC->get(d_now.tv_sec, qname, QType(QType::AAAA), false, &cset, d_incomingECSFound ? d_incomingECSNetwork : d_requestor) > 0) {
+       if(t_RC->get(d_now.tv_sec, qname, QType(QType::AAAA), false, &cset, d_cacheRemote) > 0) {
          for(auto k=cset.cbegin();k!=cset.cend();++k) {
            if(k->d_ttl > (unsigned int)d_now.tv_sec ) {
              if (auto drc = getRR<AAAARecordContent>(*k)) {
@@ -760,7 +761,7 @@ void SyncRes::getBestNSFromCache(const DNSName &qname, const QType& qtype, vecto
     vector<DNSRecord> ns;
     *flawedNSSet = false;
 
-    if(t_RC->get(d_now.tv_sec, subdomain, QType(QType::NS), false, &ns, d_incomingECSFound ? d_incomingECSNetwork : d_requestor) > 0) {
+    if(t_RC->get(d_now.tv_sec, subdomain, QType(QType::NS), false, &ns, d_cacheRemote) > 0) {
       for(auto k=ns.cbegin();k!=ns.cend(); ++k) {
         if(k->d_ttl > (unsigned int)d_now.tv_sec ) {
           vector<DNSRecord> aset;
@@ -768,7 +769,7 @@ void SyncRes::getBestNSFromCache(const DNSName &qname, const QType& qtype, vecto
           const DNSRecord& dr=*k;
          auto nrr = getRR<NSRecordContent>(dr);
           if(nrr && (!nrr->getNS().isPartOf(subdomain) || t_RC->get(d_now.tv_sec, nrr->getNS(), s_doIPv6 ? QType(QType::ADDR) : QType(QType::A),
-                                                                    false, doLog() ? &aset : 0, d_incomingECSFound ? d_incomingECSNetwork : d_requestor) > 5)) {
+                                                                    false, doLog() ? &aset : 0, d_cacheRemote) > 5)) {
             bestns.push_back(dr);
             LOG(prefix<<qname<<": NS (with ip, or non-glue) in cache for '"<<subdomain<<"' -> '"<<nrr->getNS()<<"'"<<endl);
             LOG(prefix<<qname<<": within bailiwick: "<< nrr->getNS().isPartOf(subdomain));
@@ -894,7 +895,7 @@ bool SyncRes::doCNAMECacheCheck(const DNSName &qname, const QType &qtype, vector
   vector<std::shared_ptr<RRSIGRecordContent>> signatures;
   vector<std::shared_ptr<DNSRecord>> authorityRecs;
   bool wasAuth;
-  if(t_RC->get(d_now.tv_sec, qname, QType(QType::CNAME), d_requireAuthData, &cset, d_incomingECSFound ? d_incomingECSNetwork : d_requestor, d_doDNSSEC ? &signatures : nullptr, d_doDNSSEC ? &authorityRecs : nullptr, &d_wasVariable, &state, &wasAuth) > 0) {
+  if(t_RC->get(d_now.tv_sec, qname, QType(QType::CNAME), d_requireAuthData, &cset, d_cacheRemote, d_doDNSSEC ? &signatures : nullptr, d_doDNSSEC ? &authorityRecs : nullptr, &d_wasVariable, &state, &wasAuth) > 0) {
 
     for(auto j=cset.cbegin() ; j != cset.cend() ; ++j) {
       if (j->d_class != QClass::IN) {
@@ -920,7 +921,7 @@ bool SyncRes::doCNAMECacheCheck(const DNSName &qname, const QType &qtype, vector
             state = SyncRes::validateRecordsWithSigs(depth, qname, QType(QType::CNAME), qname, cset, signatures);
             if (state != Indeterminate) {
               LOG(prefix<<qname<<": got Indeterminate state from the CNAME cache, new validation result is "<<vStates[state]<<endl);
-              t_RC->updateValidationStatus(d_now.tv_sec, qname, QType(QType::CNAME), d_incomingECSFound ? d_incomingECSNetwork : d_requestor, d_requireAuthData, state);
+              t_RC->updateValidationStatus(d_now.tv_sec, qname, QType(QType::CNAME), d_cacheRemote, d_requireAuthData, state);
             }
           }
         }
@@ -1147,7 +1148,7 @@ bool SyncRes::doCacheCheck(const DNSName &qname, const DNSName& authname, bool w
   vector<std::shared_ptr<DNSRecord>> authorityRecs;
   uint32_t ttl=0;
   bool wasCachedAuth;
-  if(t_RC->get(d_now.tv_sec, sqname, sqt, d_requireAuthData, &cset, d_incomingECSFound ? d_incomingECSNetwork : d_requestor, d_doDNSSEC ? &signatures : nullptr, d_doDNSSEC ? &authorityRecs : nullptr, &d_wasVariable, &cachedState, &wasCachedAuth) > 0) {
+  if(t_RC->get(d_now.tv_sec, sqname, sqt, d_requireAuthData, &cset, d_cacheRemote, d_doDNSSEC ? &signatures : nullptr, d_doDNSSEC ? &authorityRecs : nullptr, &d_wasVariable, &cachedState, &wasCachedAuth) > 0) {
 
     LOG(prefix<<sqname<<": Found cache hit for "<<sqt.getName()<<": ");
 
@@ -1174,7 +1175,7 @@ bool SyncRes::doCacheCheck(const DNSName &qname, const DNSName& authname, bool w
 
       if (cachedState != Indeterminate) {
         LOG(prefix<<qname<<": got Indeterminate state from the cache, validation result is "<<vStates[cachedState]<<endl);
-        t_RC->updateValidationStatus(d_now.tv_sec, sqname, sqt, d_incomingECSFound ? d_incomingECSNetwork : d_requestor, d_requireAuthData, cachedState);
+        t_RC->updateValidationStatus(d_now.tv_sec, sqname, sqt, d_cacheRemote, d_requireAuthData, cachedState);
       }
     }
 
@@ -1275,13 +1276,44 @@ inline vector<DNSName> SyncRes::shuffleInSpeedOrder(NsSet &tnameservers, const s
           LOG(endl<<prefix<<"             ");
         }
       }
-      LOG((i->empty() ? string("<empty>") : i->toString())<<"(" << (boost::format("%0.2f") % (speeds[*i]/1000.0)).str() <<"ms)");
+      LOG(i->toLogString()<<"(" << (boost::format("%0.2f") % (speeds[*i]/1000.0)).str() <<"ms)");
     }
     LOG(endl);
   }
   return rnameservers;
 }
 
+inline vector<ComboAddress> SyncRes::shuffleForwardSpeed(const vector<ComboAddress> &rnameservers, const string &prefix, const bool wasRd)
+{
+  vector<ComboAddress> nameservers = rnameservers;
+  map<ComboAddress, double> speeds;
+
+  for(const auto& val: nameservers) {
+    double speed;
+    DNSName nsName = DNSName(val.toStringWithPort());
+    speed=t_sstorage.nsSpeeds[nsName].get(&d_now);
+    speeds[val]=speed;
+  }
+  random_shuffle(nameservers.begin(),nameservers.end(), dns_random);
+  speedOrderCA so(speeds);
+  stable_sort(nameservers.begin(),nameservers.end(), so);
+
+  if(doLog()) {
+    LOG(prefix<<"Nameservers: ");
+    for(vector<ComboAddress>::const_iterator i=nameservers.cbegin();i!=nameservers.cend();++i) {
+      if(i!=nameservers.cbegin()) {
+        LOG(", ");
+        if(!((i-nameservers.cbegin())%3)) {
+          LOG(endl<<prefix<<"             ");
+        }
+      }
+      LOG((wasRd ? string("+") : string("-")) << i->toStringWithPort() <<"(" << (boost::format("%0.2f") % (speeds[*i]/1000.0)).str() <<"ms)");
+    }
+    LOG(endl);
+  }
+  return nameservers;
+}
+
 static uint32_t getRRSIGTTL(const time_t now, const std::shared_ptr<RRSIGRecordContent>& rrsig)
 {
   uint32_t res = 0;
@@ -1419,13 +1451,13 @@ vector<ComboAddress> SyncRes::retrieveAddressesForNS(const std::string& prefix,
   else {
     LOG(prefix<<qname<<": Domain has hardcoded nameserver");
 
-    result = nameservers[*tns].first;
-    if(result.size() > 1) {
+    if(nameservers[*tns].first.size() > 1) {
       LOG("s");
     }
     LOG(endl);
 
     sendRDQuery = nameservers[*tns].second;
+    result = shuffleForwardSpeed(nameservers[*tns].first, doLog() ? (prefix+qname.toString()+": ") : string(), sendRDQuery);
     pierceDontQuery=true;
   }
   return result;
@@ -1697,8 +1729,8 @@ bool SyncRes::lookForCut(const DNSName& qname, unsigned int depth, const vState
 void SyncRes::computeZoneCuts(const DNSName& begin, const DNSName& end, unsigned int depth)
 {
   if(!begin.isPartOf(end)) {
-    LOG(d_prefix<<" "<<begin.toLogString()<<" is not part of "<<end.toString()<<endl);
-    throw PDNSException(begin.toLogString() + " is not part of " + end.toString());
+    LOG(d_prefix<<" "<<begin.toLogString()<<" is not part of "<<end.toLogString()<<endl);
+    throw PDNSException(begin.toLogString() + " is not part of " + end.toLogString());
   }
 
   if (d_cutStates.count(begin) != 0) {
@@ -2050,7 +2082,7 @@ RCode::rcodes_ SyncRes::updateCacheFromRecords(unsigned int depth, LWResult& lwr
        set the TC bit solely because these RRSIG RRs didn't fit."
     */
     bool isAA = lwr.d_aabit && i->first.place != DNSResourceRecord::ADDITIONAL;
-    if (isAA && isCNAMEAnswer && (i->first.place != DNSResourceRecord::ANSWER || i->first.type != QType::CNAME)) {
+    if (isAA && isCNAMEAnswer && (i->first.place != DNSResourceRecord::ANSWER || i->first.type != QType::CNAME || i->first.name != qname)) {
       /*
         rfc2181 states:
         Note that the answer section of an authoritative answer normally
@@ -2091,6 +2123,8 @@ RCode::rcodes_ SyncRes::updateCacheFromRecords(unsigned int depth, LWResult& lwr
         }
       }
       else {
+        recordState = Indeterminate;
+
         /* in a non authoritative answer, we only care about the DS record (or lack of)  */
         if ((i->first.type == QType::DS || i->first.type == QType::NSEC || i->first.type == QType::NSEC3) && i->first.place == DNSResourceRecord::AUTHORITY) {
           LOG(d_prefix<<"Validating DS record for "<<i->first.name<<endl);
@@ -2098,7 +2132,7 @@ RCode::rcodes_ SyncRes::updateCacheFromRecords(unsigned int depth, LWResult& lwr
         }
       }
 
-      if (initialState == Secure && state != recordState) {
+      if (initialState == Secure && state != recordState && isAA) {
         updateValidationState(state, recordState);
       }
     }
@@ -2255,7 +2289,7 @@ bool SyncRes::processRecords(const std::string& prefix, const DNSName& qname, co
 
           updateValidationState(state, st);
           /* we already stored the record with a different validation status, let's fix it */
-          t_RC->updateValidationStatus(d_now.tv_sec, qname, qtype, d_incomingECSFound ? d_incomingECSNetwork : d_requestor, lwr.d_aabit, st);
+          t_RC->updateValidationStatus(d_now.tv_sec, qname, qtype, d_cacheRemote, lwr.d_aabit, st);
         }
       }
     }
@@ -2350,7 +2384,7 @@ bool SyncRes::processRecords(const std::string& prefix, const DNSName& qname, co
 
 bool SyncRes::doResolveAtThisIP(const std::string& prefix, const DNSName& qname, const QType& qtype, LWResult& lwr, boost::optional<Netmask>& ednsmask, const DNSName& auth, bool const sendRDQuery, const DNSName& nsName, const ComboAddress& remoteIP, bool doTCP, bool* truncated)
 {
-  int resolveret;
+  int resolveret = RCode::NoError;
   s_outqueries++;
   d_outqueries++;
 
@@ -2372,7 +2406,7 @@ bool SyncRes::doResolveAtThisIP(const std::string& prefix, const DNSName& qname,
     LOG(prefix<<qname<<": query handled by Lua"<<endl);
   }
   else {
-    ednsmask=getEDNSSubnetMask(d_requestor, qname, remoteIP);
+    ednsmask=getEDNSSubnetMask(qname, remoteIP);
     if(ednsmask) {
       LOG(prefix<<qname<<": Adding EDNS Client Subnet Mask "<<ednsmask->toString()<<" to query"<<endl);
       s_ecsqueries++;
@@ -2420,7 +2454,7 @@ bool SyncRes::doResolveAtThisIP(const std::string& prefix, const DNSName& qname,
     }
 
     if(resolveret != -2) { // don't account for resource limits, they are our own fault
-      t_sstorage.nsSpeeds[nsName].submit(remoteIP, 1000000, &d_now); // 1 sec
+      t_sstorage.nsSpeeds[nsName.empty()? DNSName(remoteIP.toStringWithPort()) : nsName].submit(remoteIP, 1000000, &d_now); // 1 sec
 
       // code below makes sure we don't filter COM or the root
       if (s_serverdownmaxfails > 0 && (auth != g_rootdnsname) && t_sstorage.fails.incr(remoteIP) >= s_serverdownmaxfails) {
@@ -2720,7 +2754,7 @@ int SyncRes::doResolveAt(NsSet &nameservers, DNSName auth, bool flawedNSSet, con
           */
           //        cout<<"msec: "<<lwr.d_usec/1000.0<<", "<<g_avgLatency/1000.0<<'\n';
 
-          t_sstorage.nsSpeeds[*tns].submit(*remoteIP, lwr.d_usec, &d_now);
+          t_sstorage.nsSpeeds[tns->empty()? DNSName(remoteIP->toStringWithPort()) : *tns].submit(*remoteIP, lwr.d_usec, &d_now);
 
           /* we have received an answer, are we done ? */
           bool done = processAnswer(depth, lwr, qname, qtype, auth, wasForwarded, ednsmask, sendRDQuery, nameservers, ret, luaconfsLocal->dfe, &gotNewServers, &rcode, state);
@@ -2747,11 +2781,25 @@ int SyncRes::doResolveAt(NsSet &nameservers, DNSName auth, bool flawedNSSet, con
   return -1;
 }
 
-void SyncRes::setIncomingECS(boost::optional<const EDNSSubnetOpts&> incomingECS)
+void SyncRes::setQuerySource(const ComboAddress& requestor, boost::optional<const EDNSSubnetOpts&> incomingECS)
 {
-  d_incomingECS = incomingECS;
-  if (incomingECS) {
-    if (d_incomingECS->source.getBits() == 0) {
+  d_requestor = requestor;
+
+  if (incomingECS && incomingECS->source.getBits() > 0) {
+    d_cacheRemote = incomingECS->source.getMaskedNetwork();
+    uint8_t bits = std::min(incomingECS->source.getBits(), (incomingECS->source.isIpv4() ? s_ecsipv4limit : s_ecsipv6limit));
+    ComboAddress trunc = incomingECS->source.getNetwork();
+    trunc.truncate(bits);
+    d_outgoingECSNetwork = boost::optional<Netmask>(Netmask(trunc, bits));
+  } else {
+    d_cacheRemote = d_requestor;
+    if(!incomingECS && s_ednslocalsubnets.match(d_requestor)) {
+      ComboAddress trunc = d_requestor;
+      uint8_t bits = d_requestor.isIPv4() ? 32 : 128;
+      bits = std::min(bits, (trunc.isIPv4() ? s_ecsipv4limit : s_ecsipv6limit));
+      trunc.truncate(bits);
+      d_outgoingECSNetwork = boost::optional<Netmask>(Netmask(trunc, bits));
+    } else if (s_ecsScopeZero.source.getBits() > 0) {
       /* RFC7871 says we MUST NOT send any ECS if the source scope is 0.
          But using an empty ECS in that case would mean inserting
          a non ECS-specific entry into the cache, preventing any further
@@ -2766,45 +2814,21 @@ void SyncRes::setIncomingECS(boost::optional<const EDNSSubnetOpts&> incomingECS)
          indicator of the applicable scope.  Subsequent Stub Resolver queries
          for /0 can then be answered from this cached response.
       */
-      d_incomingECS = s_ecsScopeZero;
-      d_incomingECSNetwork = s_ecsScopeZero.source.getMaskedNetwork();
-    }
-    else {
-      uint8_t bits = std::min(incomingECS->source.getBits(), (incomingECS->source.isIpv4() ? s_ecsipv4limit : s_ecsipv6limit));
-      d_incomingECS->source = Netmask(incomingECS->source.getNetwork(), bits);
-      d_incomingECSNetwork = d_incomingECS->source.getMaskedNetwork();
+      d_outgoingECSNetwork = boost::optional<Netmask>(s_ecsScopeZero.source.getMaskedNetwork());
+      d_cacheRemote = s_ecsScopeZero.source.getNetwork();
+    } else {
+      // ECS disabled because no scope-zero address could be derived.
+      d_outgoingECSNetwork = boost::none;
     }
   }
-  else {
-    d_incomingECSNetwork = ComboAddress();
-  }
 }
 
-boost::optional<Netmask> SyncRes::getEDNSSubnetMask(const ComboAddress& local, const DNSName&dn, const ComboAddress& rem)
+boost::optional<Netmask> SyncRes::getEDNSSubnetMask(const DNSName& dn, const ComboAddress& rem)
 {
-  boost::optional<Netmask> result;
-  ComboAddress trunc;
-  uint8_t bits;
-  if(d_incomingECSFound) {
-    trunc = d_incomingECSNetwork;
-    bits = d_incomingECS->source.getBits();
-  }
-  else if(!local.isIPv4() || local.sin4.sin_addr.s_addr) { // detect unset 'requestor'
-    trunc = local;
-    bits = local.isIPv4() ? 32 : 128;
-    bits = std::min(bits, (trunc.isIPv4() ? s_ecsipv4limit : s_ecsipv6limit));
-  }
-  else {
-    /* nothing usable */
-    return result;
-  }
-
-  if(s_ednsdomains.check(dn) || s_ednssubnets.match(rem)) {
-    trunc.truncate(bits);
-    return boost::optional<Netmask>(Netmask(trunc, bits));
+  if(d_outgoingECSNetwork && (s_ednsdomains.check(dn) || s_ednsremotesubnets.match(rem))) {
+    return d_outgoingECSNetwork;
   }
-
-  return result;
+  return boost::none;
 }
 
 void SyncRes::parseEDNSSubnetWhitelist(const std::string& wlist)
@@ -2813,7 +2837,7 @@ void SyncRes::parseEDNSSubnetWhitelist(const std::string& wlist)
   stringtok(parts, wlist, ",; ");
   for(const auto& a : parts) {
     try {
-      s_ednssubnets.addMask(Netmask(a));
+      s_ednsremotesubnets.addMask(Netmask(a));
     }
     catch(...) {
       s_ednsdomains.add(DNSName(a));
@@ -2821,6 +2845,15 @@ void SyncRes::parseEDNSSubnetWhitelist(const std::string& wlist)
   }
 }
 
+void SyncRes::parseEDNSSubnetAddFor(const std::string& subnetlist)
+{
+  vector<string> parts;
+  stringtok(parts, subnetlist, ",; ");
+  for(const auto& a : parts) {
+    s_ednslocalsubnets.addMask(a);
+  }
+}
+
 // used by PowerDNSLua - note that this neglects to add the packet count & statistics back to pdns_ercursor.cc
 int directResolve(const DNSName& qname, const QType& qtype, int qclass, vector<DNSRecord>& ret)
 {
index 33dae04805802a6492774308baa1ce8acaa85687..a73bd7bc506de6555fe6cfd82f4d3ca51e877e6a 100644 (file)
@@ -421,17 +421,26 @@ public:
     s_dontQuery = nullptr;
   }
   static void parseEDNSSubnetWhitelist(const std::string& wlist);
-  static void addEDNSSubnet(const Netmask& subnet)
+  static void parseEDNSSubnetAddFor(const std::string& subnetlist);
+  static void addEDNSLocalSubnet(const std::string& subnet)
   {
-    s_ednssubnets.addMask(subnet);
+    s_ednslocalsubnets.addMask(subnet);
+  }
+  static void addEDNSRemoteSubnet(const std::string& subnet)
+  {
+    s_ednsremotesubnets.addMask(subnet);
   }
   static void addEDNSDomain(const DNSName& domain)
   {
     s_ednsdomains.add(domain);
   }
-  static void clearEDNSSubnets()
+  static void clearEDNSLocalSubnets()
   {
-    s_ednssubnets.clear();
+    s_ednslocalsubnets.clear();
+  }
+  static void clearEDNSRemoteSubnets()
+  {
+    s_ednsremotesubnets.clear();
   }
   static void clearEDNSDomains()
   {
@@ -603,11 +612,6 @@ public:
     return d_wantsRPZ;
   }
 
-  void setIncomingECSFound(bool state=true)
-  {
-    d_incomingECSFound=state;
-  }
-
   string getTrace() const
   {
     return d_trace.str();
@@ -638,7 +642,7 @@ public:
     d_skipCNAMECheck = skip;
   }
 
-  void setIncomingECS(boost::optional<const EDNSSubnetOpts&> incomingECS);
+  void setQuerySource(const ComboAddress& requestor, boost::optional<const EDNSSubnetOpts&> incomingECS);
 
 #ifdef HAVE_PROTOBUF
   void setInitialRequestId(boost::optional<const boost::uuids::uuid&> initialRequestId)
@@ -699,12 +703,14 @@ public:
   unsigned int d_timeouts;
   unsigned int d_unreachables;
   unsigned int d_totUsec;
-  ComboAddress d_requestor;
 
 private:
+  ComboAddress d_requestor;
+  ComboAddress d_cacheRemote;
 
   static std::unordered_set<DNSName> s_delegationOnly;
-  static NetmaskGroup s_ednssubnets;
+  static NetmaskGroup s_ednslocalsubnets;
+  static NetmaskGroup s_ednsremotesubnets;
   static SuffixMatchNode s_ednsdomains;
   static EDNSSubnetOpts s_ecsScopeZero;
   static LogMode s_lm;
@@ -739,6 +745,7 @@ private:
   DNSName getBestNSNamesFromCache(const DNSName &qname, const QType &qtype, NsSet& nsset, bool* flawedNSSet, unsigned int depth, set<GetBestNSAnswer>&beenthere);
 
   inline vector<DNSName> shuffleInSpeedOrder(NsSet &nameservers, const string &prefix);
+  inline vector<ComboAddress> shuffleForwardSpeed(const vector<ComboAddress> &rnameservers, const string &prefix, const bool wasRd);
   bool moreSpecificThan(const DNSName& a, const DNSName &b) const;
   vector<ComboAddress> getAddrs(const DNSName &qname, unsigned int depth, set<GetBestNSAnswer>& beenthere, bool cacheOnly);
 
@@ -754,7 +761,7 @@ private:
 
   int asyncresolveWrapper(const ComboAddress& ip, bool ednsMANDATORY, const DNSName& domain, int type, bool doTCP, bool sendRDQuery, struct timeval* now, boost::optional<Netmask>& srcmask, LWResult* res) const;
 
-  boost::optional<Netmask> getEDNSSubnetMask(const ComboAddress& local, const DNSName&dn, const ComboAddress& rem);
+  boost::optional<Netmask> getEDNSSubnetMask(const DNSName&dn, const ComboAddress& rem);
 
   bool validationEnabled() const;
   uint32_t computeLowestTTD(const std::vector<DNSRecord>& records, const std::vector<std::shared_ptr<RRSIGRecordContent> >& signatures, uint32_t signaturesTTL) const;
@@ -780,8 +787,7 @@ private:
   zonesStates_t d_cutStates;
   ostringstream d_trace;
   shared_ptr<RecursorLua4> d_pdl;
-  boost::optional<EDNSSubnetOpts> d_incomingECS;
-  ComboAddress d_incomingECSNetwork;
+  boost::optional<Netmask> d_outgoingECSNetwork;
 #ifdef HAVE_PROTOBUF
   boost::optional<const boost::uuids::uuid&> d_initialRequestId;
 #endif
@@ -797,7 +803,6 @@ private:
   bool d_doDNSSEC;
   bool d_DNSSECValidationRequested{false};
   bool d_doEDNS0{true};
-  bool d_incomingECSFound{false};
   bool d_requireAuthData{true};
   bool d_skipCNAMECheck{false};
   bool d_updatingRootNS{false};
index eece6994ff90a3fe28a2ea3f3f6b072bffd47169..3e4c00e5967acdf8cc8a1a78cf75673c3f06d982 100644 (file)
@@ -535,16 +535,23 @@ namespace {
     bool d_auth;
   };
 
-  DNSResourceRecord makeDNSRRFromSOAData(const SOAData& sd)
+  DNSZoneRecord makeEditedDNSZRFromSOAData(DNSSECKeeper& dk, const SOAData& sd)
   {
-    DNSResourceRecord soa;
-    soa.qname= sd.qname;
-    soa.qtype=QType::SOA;
-    soa.content=serializeSOAData(sd);
-    soa.ttl=sd.ttl;
-    soa.domain_id=sd.domain_id;
-    soa.auth = true;
-    return soa;
+    SOAData edited = sd;
+    edited.serial = calculateEditSOA(sd.serial, dk, sd.qname);
+
+    DNSRecord soa;
+    soa.d_name = sd.qname;
+    soa.d_type = QType::SOA;
+    soa.d_ttl = sd.ttl;
+    soa.d_place = DNSResourceRecord::ANSWER;
+    soa.d_content = makeSOAContent(edited);
+
+    DNSZoneRecord dzr;
+    dzr.auth = true;
+    dzr.dr = soa;
+
+    return dzr;
   }
 
   shared_ptr<DNSPacket> getFreshAXFRPacket(shared_ptr<DNSPacket> q)
@@ -655,16 +662,8 @@ int TCPNameserver::doAXFR(const DNSName &target, shared_ptr<DNSPacket> q, int ou
   
   // SOA *must* go out first, our signing pipe might reorder
   DLOG(L<<"Sending out SOA"<<endl);
-  DNSResourceRecord soa = makeDNSRRFromSOAData(sd);
-  DNSZoneRecord dzrsoa;
-  dzrsoa.auth=true;
-  dzrsoa.dr=DNSRecord(soa);
-
-  string kind;
-  dk.getSoaEdit(sd.qname, kind);
-  editSOARecord(dzrsoa, kind);
-
-  outpacket->addRecord(dzrsoa);
+  DNSZoneRecord soa = makeEditedDNSZRFromSOAData(dk, sd);
+  outpacket->addRecord(soa);
   if(securedZone && !presignedZone) {
     set<DNSName> authSet;
     authSet.insert(target);
@@ -1052,7 +1051,7 @@ int TCPNameserver::doAXFR(const DNSName &target, shared_ptr<DNSPacket> q, int ou
   DLOG(L<<"Done writing out records"<<endl);
   /* and terminate with yet again the SOA record */
   outpacket=getFreshAXFRPacket(q);
-  outpacket->addRecord(dzrsoa);
+  outpacket->addRecord(soa);
   if(haveTSIGDetails && !tsigkeyname.empty())
     outpacket->setTSIGDetails(trc, tsigkeyname, tsigsecret, trc.d_mac, true); 
   
@@ -1148,9 +1147,7 @@ int TCPNameserver::doIXFR(shared_ptr<DNSPacket> q, int outsock)
     return 0;
   }
 
-  string soaedit;
-  dk.getSoaEdit(target, soaedit);
-  if (!rfc1982LessThan(serial, calculateEditSOA(sd, soaedit))) {
+  if (!rfc1982LessThan(serial, calculateEditSOA(sd.serial, dk, sd.qname))) {
     TSIGRecordContent trc;
     DNSName tsigkeyname;
     string tsigsecret;
@@ -1177,13 +1174,8 @@ int TCPNameserver::doIXFR(shared_ptr<DNSPacket> q, int outsock)
 
     // SOA *must* go out first, our signing pipe might reorder
     DLOG(L<<"Sending out SOA"<<endl);
-    DNSResourceRecord soa = makeDNSRRFromSOAData(sd);
-    DNSZoneRecord dzrsoa;
-    dzrsoa.dr=DNSRecord(soa);
-    dzrsoa.auth=true;
-    
-    outpacket->addRecord(dzrsoa);
-    editSOA(dk, sd.qname, outpacket.get());
+    DNSZoneRecord soa = makeEditedDNSZRFromSOAData(dk, sd);
+    outpacket->addRecord(soa);
     if(securedZone) {
       set<DNSName> authSet;
       authSet.insert(target);
index a8b022361592bcaf8cce3b77b4bf8359767b152e..2acc4ba70d528d2eb29c88f20f2d3032fe9fc30e 100644 (file)
@@ -45,7 +45,7 @@ BOOST_AUTO_TEST_CASE(test_PacketCacheSimple) {
       bool found = PC.get(dq, a.wirelength(), 0, responseBuf, &responseBufSize, &key);
       BOOST_CHECK_EQUAL(found, false);
 
-      PC.insert(key, a, QType::A, QClass::IN, (const char*) response.data(), responseLen, false, 0);
+      PC.insert(key, a, QType::A, QClass::IN, (const char*) response.data(), responseLen, false, 0, boost::none);
 
       found = PC.get(dq, a.wirelength(), pwR.getHeader()->id, responseBuf, &responseBufSize, &key, 0, true);
       if (found == true) {
@@ -109,6 +109,52 @@ BOOST_AUTO_TEST_CASE(test_PacketCacheSimple) {
   }
 }
 
+BOOST_AUTO_TEST_CASE(test_PacketCacheServFailTTL) {
+  const size_t maxEntries = 150000;
+  DNSDistPacketCache PC(maxEntries, 86400, 1);
+
+  ComboAddress remote;
+  try {
+    DNSName a = DNSName("servfail");
+    BOOST_CHECK_EQUAL(DNSName(a.toString()), a);
+
+    vector<uint8_t> query;
+    DNSPacketWriter pwQ(query, a, QType::A, QClass::IN, 0);
+    pwQ.getHeader()->rd = 1;
+
+    vector<uint8_t> response;
+    DNSPacketWriter pwR(response, a, QType::A, QClass::IN, 0);
+    pwR.getHeader()->rd = 1;
+    pwR.getHeader()->ra = 0;
+    pwR.getHeader()->qr = 1;
+    pwR.getHeader()->rcode = RCode::ServFail;
+    pwR.getHeader()->id = pwQ.getHeader()->id;
+    pwR.commit();
+    uint16_t responseLen = response.size();
+
+    char responseBuf[4096];
+    uint16_t responseBufSize = sizeof(responseBuf);
+    uint32_t key = 0;
+    DNSQuestion dq(&a, QType::A, QClass::IN, &remote, &remote, (struct dnsheader*) query.data(), query.size(), query.size(), false);
+    bool found = PC.get(dq, a.wirelength(), 0, responseBuf, &responseBufSize, &key);
+    BOOST_CHECK_EQUAL(found, false);
+
+    // Insert with failure-TTL of 0 (-> should not enter cache).
+    PC.insert(key, a, QType::A, QClass::IN, (const char*) response.data(), responseLen, false, RCode::ServFail, boost::optional<uint32_t>(0));
+    found = PC.get(dq, a.wirelength(), pwR.getHeader()->id, responseBuf, &responseBufSize, &key, 0, true);
+    BOOST_CHECK_EQUAL(found, false);
+
+    // Insert with failure-TTL non-zero (-> should enter cache).
+    PC.insert(key, a, QType::A, QClass::IN, (const char*) response.data(), responseLen, false, RCode::ServFail, boost::optional<uint32_t>(300));
+    found = PC.get(dq, a.wirelength(), pwR.getHeader()->id, responseBuf, &responseBufSize, &key, 0, true);
+    BOOST_CHECK_EQUAL(found, true);
+  }
+  catch(PDNSException& e) {
+    cerr<<"Had error: "<<e.reason<<endl;
+    throw;
+  }
+}
+
 static DNSDistPacketCache PC(500000);
 
 static void *threadMangler(void* off)
@@ -139,7 +185,7 @@ static void *threadMangler(void* off)
       DNSQuestion dq(&a, QType::A, QClass::IN, &remote, &remote, (struct dnsheader*) query.data(), query.size(), query.size(), false);
       PC.get(dq, a.wirelength(), 0, responseBuf, &responseBufSize, &key);
 
-      PC.insert(key, a, QType::A, QClass::IN, (const char*) response.data(), responseLen, false, 0);
+      PC.insert(key, a, QType::A, QClass::IN, (const char*) response.data(), responseLen, false, 0, boost::none);
     }
   }
   catch(PDNSException& e) {
index 42c84018fec6e905da035729f1a5a80ec03c8f32..31efaf3ce84319a3a666bd834f9311bee441395c 100644 (file)
@@ -128,16 +128,18 @@ BOOST_AUTO_TEST_CASE(test_basic) {
 
   BOOST_CHECK_EQUAL(unset.toString(), "www.powerdns\\.com.com.");
 
+  DNSName rfc4343_2_1("~!.example.");
   DNSName rfc4343_2_2(R"(Donald\032E\.\032Eastlake\0323rd.example.)");
   DNSName example("example.");
+  BOOST_CHECK(rfc4343_2_1.isPartOf(example));
   BOOST_CHECK(rfc4343_2_2.isPartOf(example));
+  BOOST_CHECK_EQUAL(rfc4343_2_1.toString(), "~!.example.");
 
   auto labels=rfc4343_2_2.getRawLabels();
   BOOST_CHECK_EQUAL(*labels.begin(), "Donald E. Eastlake 3rd");
   BOOST_CHECK_EQUAL(*labels.rbegin(), "example");
   BOOST_CHECK_EQUAL(labels.size(), 2);
 
-
   DNSName build;
   build.appendRawLabel("Donald E. Eastlake 3rd");
   build.appendRawLabel("example");
@@ -604,7 +606,7 @@ BOOST_AUTO_TEST_CASE(test_compare_canonical) {
   }
   sort(vec.begin(), vec.end(), CanonDNSNameCompare());
   //  for(const auto& v : vec)
-  //    cerr<<'"'<<v.toString()<<'"'<<endl;
+  //    cerr<<'"'<<v<<'"'<<endl;
 
   vector<DNSName> right;
   for(const auto& b: {"bert.com.",  "Aleph1.powerdns.com.",
index 6644604844e48bac0076245fab847ab0d0978ec5..77a5add4026ff69864e5105455b34150054ad108 100644 (file)
 #include <boost/scoped_ptr.hpp>
 #include "dnsrecords.hh"
 
-#define CASE_L(type, inval, zoneval, lineval, broken) case_t(type, std::string(inval), std::string(zoneval), std::string(lineval, sizeof(lineval)-1), broken)
-#define CASE_S(type, zoneval, lineval, broken) CASE_L(type, zoneval, zoneval, lineval, broken)
-BOOST_AUTO_TEST_SUITE(test_dnsrecords_cc)
+namespace {
+  enum class broken_marker {
+    WORKING,
+    BROKEN,
+  };
+}
 
-#define REC_CHECK_EQUAL(a,b) { if (val.get<4>()) { BOOST_WARN_EQUAL(a,b); } else {  BOOST_CHECK_EQUAL(a,b); } }
-#define REC_CHECK_MESSAGE(cond,msg) { if (val.get<4>()) { BOOST_WARN_MESSAGE(cond,msg); } else {  BOOST_CHECK_MESSAGE(cond,msg); } }
-#define REC_FAIL_XSUCCESS(msg) { if (val.get<4>()) { BOOST_CHECK_MESSAGE(false, std::string("Test has unexpectedly passed: ") + msg); } } // fail if test succeeds
-#define REC_FAIL_XSUCCESS2(msg) { if (val.get<3>()) { BOOST_CHECK_MESSAGE(false, std::string("Test has unexpectedly passed: ") + msg); } } // fail if test succeeds (for the bad records test case)
+// use a user-defined literal operator instead? should be supported in
+// C++11, but only C++14 added the `s` suffix.
+#define BINARY(s) (std::string(s, sizeof(s) - 1))
+
+#define _CASE_L(type, inval, zoneval, lineval, broken) case_t(type, BINARY(inval), BINARY(zoneval), BINARY(lineval), broken)
+#define CASE_L(type, inval, zoneval, lineval) _CASE_L(type, inval, zoneval, lineval, broken_marker::WORKING)
+#define CASE_S(type, zoneval, lineval) _CASE_L(type, zoneval, zoneval, lineval, broken_marker::WORKING)
+#define BROKEN_CASE_L(type, inval, zoneval, lineval) _CASE_L(type, inval, zoneval, lineval, broken_marker::BROKEN)
+#define BROKEN_CASE_S(type, zoneval, lineval) _CASE_L(type, zoneval, zoneval, lineval, broken_marker::BROKEN)
+BOOST_AUTO_TEST_SUITE(test_dnsrecords_cc)
 
-enum class case_type_enum_t { zone, wire };
-static const auto zone = case_type_enum_t::zone;
-static const auto wire = case_type_enum_t::wire;
+#define REC_CHECK_EQUAL(a,b) { if (broken_marker::BROKEN == broken) { BOOST_WARN_EQUAL(a,b); } else {  BOOST_CHECK_EQUAL(a,b); } }
+#define REC_CHECK_MESSAGE(cond,msg) { if (broken_marker::BROKEN == broken) { BOOST_WARN_MESSAGE(cond,msg); } else {  BOOST_CHECK_MESSAGE(cond,msg); } }
+#define REC_FAIL_XSUCCESS(msg) { if (broken_marker::BROKEN == broken) { BOOST_CHECK_MESSAGE(false, std::string("Test has unexpectedly passed: ") + msg); } } // fail if test succeeds
 
 BOOST_AUTO_TEST_CASE(test_record_types) {
   // tuple contains <type, user value, zone representation, line value, broken>
-  typedef boost::tuple<const QType::typeenum, const std::string, const std::string, const std::string, bool> case_t;
+  typedef boost::tuple<QType::typeenum, std::string, std::string, std::string, broken_marker> case_t;
   typedef std::list<case_t> cases_t;
   reportAllTypes();
   MRRecordContent::report();
@@ -40,162 +49,167 @@ BOOST_AUTO_TEST_CASE(test_record_types) {
 
 // why yes, they are unordered by name, how nice of you to notice
 
-  cases_t cases = boost::assign::list_of
-     (CASE_S(QType::A, "127.0.0.1", "\x7F\x00\x00\x01",false))
+  const cases_t cases = boost::assign::list_of
+     (CASE_S(QType::A, "127.0.0.1", "\x7F\x00\x00\x01"))
 // local nameserver
-     (CASE_S(QType::NS, "ns.rec.test.", "\x02ns\xc0\x11",false))
+     (CASE_S(QType::NS, "ns.rec.test.", "\x02ns\xc0\x11"))
 // non-local nameserver
-     (CASE_S(QType::NS, "ns.example.com.", "\x02ns\x07""example\x03""com\x00",false))
+     (CASE_S(QType::NS, "ns.example.com.", "\x02ns\x07""example\x03""com\x00"))
 // local alias
-     (CASE_S(QType::CNAME, "name.rec.test.", "\x04name\xc0\x11",false))
+     (CASE_S(QType::CNAME, "name.rec.test.", "\x04name\xc0\x11"))
 // non-local alias
-     (CASE_S(QType::CNAME, "name.example.com.", "\x04name\x07""example\x03""com\x00",false))
+     (CASE_S(QType::CNAME, "name.example.com.", "\x04name\x07""example\x03""com\x00"))
 // max label length (63)
-     (CASE_S(QType::CNAME, "123456789012345678901234567890123456789012345678901234567890123.example.com.", "\x3f""123456789012345678901234567890123456789012345678901234567890123\x07""example\x03""com\x00", false))
+     (CASE_S(QType::CNAME, "123456789012345678901234567890123456789012345678901234567890123.example.com.", "\x3f""123456789012345678901234567890123456789012345678901234567890123\x07""example\x03""com\x00"))
 // local max name length (255)
-     (CASE_S(QType::CNAME, "123456789012345678901234567890123456789012345678901234567890123.123456789012345678901234567890123456789012345678901234567890123.123456789012345678901234567890123456789012345678901234567890123.1234567890123456789012345678901234567890123456789012.rec.test.", "\x3f""123456789012345678901234567890123456789012345678901234567890123\x3f""123456789012345678901234567890123456789012345678901234567890123\x3f""123456789012345678901234567890123456789012345678901234567890123\x34""1234567890123456789012345678901234567890123456789012\xc0\x11", false))
+     (CASE_S(QType::CNAME, "123456789012345678901234567890123456789012345678901234567890123.123456789012345678901234567890123456789012345678901234567890123.123456789012345678901234567890123456789012345678901234567890123.1234567890123456789012345678901234567890123456789012.rec.test.", "\x3f""123456789012345678901234567890123456789012345678901234567890123\x3f""123456789012345678901234567890123456789012345678901234567890123\x3f""123456789012345678901234567890123456789012345678901234567890123\x34""1234567890123456789012345678901234567890123456789012\xc0\x11"))
 // non-local max name length (255)
-     (CASE_S(QType::CNAME, "123456789012345678901234567890123456789012345678901234567890123.123456789012345678901234567890123456789012345678901234567890123.123456789012345678901234567890123456789012345678901234567890123.1234567890123456789012345678901234567890123456789012345678901.", "\x3f""123456789012345678901234567890123456789012345678901234567890123\x3f""123456789012345678901234567890123456789012345678901234567890123\x3f""123456789012345678901234567890123456789012345678901234567890123\x3d""1234567890123456789012345678901234567890123456789012345678901\x00", false))
+     (CASE_S(QType::CNAME, "123456789012345678901234567890123456789012345678901234567890123.123456789012345678901234567890123456789012345678901234567890123.123456789012345678901234567890123456789012345678901234567890123.1234567890123456789012345678901234567890123456789012345678901.", "\x3f""123456789012345678901234567890123456789012345678901234567890123\x3f""123456789012345678901234567890123456789012345678901234567890123\x3f""123456789012345678901234567890123456789012345678901234567890123\x3d""1234567890123456789012345678901234567890123456789012345678901\x00"))
 // local names
-     (CASE_S(QType::SOA, "ns.rec.test. hostmaster.test.rec. 2013051201 3600 3600 604800 120", "\x02ns\xc0\x11\x0ahostmaster\x04test\x03rec\x00\x77\xfc\xb9\x41\x00\x00\x0e\x10\x00\x00\x0e\x10\x00\x09\x3a\x80\x00\x00\x00\x78",false))
+     (CASE_S(QType::SOA, "ns.rec.test. hostmaster.test.rec. 2013051201 3600 3600 604800 120", "\x02ns\xc0\x11\x0ahostmaster\x04test\x03rec\x00\x77\xfc\xb9\x41\x00\x00\x0e\x10\x00\x00\x0e\x10\x00\x09\x3a\x80\x00\x00\x00\x78"))
 // non-local names
-     (CASE_S(QType::SOA, "ns.example.com. hostmaster.example.com. 2013051201 3600 3600 604800 120", "\x02ns\x07""example\x03""com\x00\x0ahostmaster\xc0\x28\x77\xfc\xb9\x41\x00\x00\x0e\x10\x00\x00\x0e\x10\x00\x09\x3a\x80\x00\x00\x00\x78",false))
+     (CASE_S(QType::SOA, "ns.example.com. hostmaster.example.com. 2013051201 3600 3600 604800 120", "\x02ns\x07""example\x03""com\x00\x0ahostmaster\xc0\x28\x77\xfc\xb9\x41\x00\x00\x0e\x10\x00\x00\x0e\x10\x00\x09\x3a\x80\x00\x00\x00\x78"))
 
-// BROKEN TESTS (2) (deprecated)
 // local name
-     (CASE_S(QType::MR, "newmailbox.rec.test.", "\x0anewmailbox\xc0\x11",false))
+     (CASE_S(QType::MR, "newmailbox.rec.test.", "\x0anewmailbox\xc0\x11"))
 // non-local name
-     (CASE_S(QType::MR, "newmailbox.example.com.", "\x0anewmailbox\x07""example\x03""com\x00",false))
+     (CASE_S(QType::MR, "newmailbox.example.com.", "\x0anewmailbox\x07""example\x03""com\x00"))
 
 // local name
-     (CASE_S(QType::PTR, "ptr.rec.test.", "\x03ptr\xc0\x11",false))
+     (CASE_S(QType::PTR, "ptr.rec.test.", "\x03ptr\xc0\x11"))
 // non-local name
-     (CASE_S(QType::PTR, "ptr.example.com.", "\x03ptr\x07""example\x03""com\x00",false))
-     (CASE_S(QType::HINFO, "\"i686\" \"Linux\"", "\x04i686\x05Linux",false))
-     (CASE_L(QType::HINFO, "i686 \"Linux\"", "\"i686\" \"Linux\"", "\x04i686\x05Linux",true))
-     (CASE_L(QType::HINFO, "\"i686\" Linux", "\"i686\" \"Linux\"", "\x04i686\x05Linux",false))
-     (CASE_L(QType::HINFO, "i686 Linux", "\"i686\" \"Linux\"", "\x04i686\x05Linux",true))
+     (CASE_S(QType::PTR, "ptr.example.com.", "\x03ptr\x07""example\x03""com\x00"))
+     (CASE_S(QType::HINFO, "\"i686\" \"Linux\"", "\x04i686\x05Linux"))
+     (BROKEN_CASE_L(QType::HINFO, "i686 \"Linux\"", "\"i686\" \"Linux\"", "\x04i686\x05Linux"))
+     (CASE_L(QType::HINFO, "\"i686\" Linux", "\"i686\" \"Linux\"", "\x04i686\x05Linux"))
+     (BROKEN_CASE_L(QType::HINFO, "i686 Linux", "\"i686\" \"Linux\"", "\x04i686\x05Linux"))
 // local name
-     (CASE_S(QType::MX, "10 mx.rec.test.", "\x00\x0a\02mx\xc0\x11",false))
+     (CASE_S(QType::MX, "10 mx.rec.test.", "\x00\x0a\x02mx\xc0\x11"))
 // non-local name
-     (CASE_S(QType::MX, "20 mx.example.com.", "\x00\x14\02mx\x07""example\x03""com\x00",false))
+     (CASE_S(QType::MX, "20 mx.example.com.", "\x00\x14\x02mx\x07""example\x03""com\x00"))
 // root label
-     (CASE_S(QType::MX, "20 .", "\x00\x14\x00",false))
-
-     (CASE_S(QType::TXT, "\"short text\"", "\x0ashort text",false))
-     (CASE_S(QType::TXT, "\"long record test 1111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111\" \"2222222222\"", "\xff""long record test 1111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111\x0a""2222222222",false))
-     (CASE_L(QType::TXT, "\"long record test 11111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111112222222222\"", "\"long record test 1111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111\" \"2222222222\"", "\xff""long record test 1111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111\x0a""2222222222",false))
-     (CASE_S(QType::TXT,  "\"\\195\\133LAND ISLANDS\"", "\x0e\xc3\x85LAND ISLANDS", false))
-     (CASE_L(QType::TXT, "\"\xc3\x85LAND ISLANDS\"", "\"\\195\\133LAND ISLANDS\"", "\x0e\xc3\x85LAND ISLANDS", false))
-     (CASE_S(QType::TXT, "\"nonbreakingtxt\"", "\x0enonbreakingtxt",false))
+     (CASE_S(QType::MX, "20 .", "\x00\x14\x00"))
+
+     (CASE_S(QType::TXT, "\"short text\"", "\x0ashort text"))
+     (CASE_S(QType::TXT, "\"long record test 1111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111\" \"2222222222\"", "\xff""long record test 1111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111\x0a""2222222222"))
+     (CASE_L(QType::TXT, "\"long record test 11111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111112222222222\"", "\"long record test 1111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111\" \"2222222222\"", "\xff""long record test 1111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111\x0a""2222222222"))
+     (CASE_S(QType::TXT,  "\"\\195\\133LAND ISLANDS\"", "\x0e\xc3\x85LAND ISLANDS"))
+     (CASE_L(QType::TXT, "\"\xc3\x85LAND ISLANDS\"", "\"\\195\\133LAND ISLANDS\"", "\x0e\xc3\x85LAND ISLANDS"))
+     (CASE_S(QType::TXT, "\"nonbreakingtxt\"", "\x0enonbreakingtxt"))
 // local name
-     (CASE_S(QType::RP, "admin.rec.test. admin-info.rec.test.", "\x05""admin\x03rec\x04test\x00\x0a""admin-info\x03rec\x04test\x00",false))
+     (CASE_S(QType::RP, "admin.rec.test. admin-info.rec.test.", "\x05""admin\x03rec\x04test\x00\x0a""admin-info\x03rec\x04test\x00"))
 // non-local name
-     (CASE_S(QType::RP, "admin.example.com. admin-info.example.com.", "\x05""admin\x07""example\x03""com\x00\x0a""admin-info\x07""example\x03""com\x00",false))
+     (CASE_S(QType::RP, "admin.example.com. admin-info.example.com.", "\x05""admin\x07""example\x03""com\x00\x0a""admin-info\x07""example\x03""com\x00"))
 // local name
-     (CASE_S(QType::AFSDB, "1 afs-server.rec.test.", "\x00\x01\x0a""afs-server\x03rec\x04test\x00",false))
+     (CASE_S(QType::AFSDB, "1 afs-server.rec.test.", "\x00\x01\x0a""afs-server\x03rec\x04test\x00"))
 // non-local name
-     (CASE_S(QType::AFSDB, "1 afs-server.example.com.", "\x00\x01\x0a""afs-server\x07""example\x03""com\x00",false))
-// deprecated (and i don't know what i am doing wrong)
-     (CASE_S(QType::KEY, "0 3 3 V19hwufL6LJARVIxzHDyGdvZ7dbQE0Kyl18yPIWj/sbCcsBbz7zO6Q2qgdzmWI3OvGNne2nxflhorhefKIMsUg==", "\x00\x00\x03\x03\x57\x5f\x61\xc2\xe7\xcb\xe8\xb2\x40\x45\x52\x31\xcc\x70\xf2\x19\xdb\xd9\xed\xd6\xd0\x13\x42\xb2\x97\x5f\x32\x3c\x85\xa3\xfe\xc6\xc2\x72\xc0\x5b\xcf\xbc\xce\xe9\x0d\xaa\x81\xdc\xe6\x58\x8d\xce\xbc\x63\x67\x7b\x69\xf1\x7e\x58\x68\xae\x17\x9f\x28\x83\x2c\x52",false))
-     (CASE_L(QType::LOC, "32 7 19 S 116 2 25 E", "32 7 19.000 S 116 2 25.000 E 0.00m 1.00m 10000.00m 10.00m", "\x00\x12\x16\x13\x79\x1b\x7d\x28\x98\xe6\x48\x68\x00\x98\x96\x80",false))
-     (CASE_L(QType::LOC, "32 7 19 S 116 2 25 E 10m", "32 7 19.000 S 116 2 25.000 E 10.00m 1.00m 10000.00m 10.00m", "\x00\x12\x16\x13\x79\x1b\x7d\x28\x98\xe6\x48\x68\x00\x98\x9a\x68",false))
-     (CASE_L(QType::LOC, "42 21 54 N 71 06 18 W -24m 30m", "42 21 54.000 N 71 6 18.000 W -24.00m 30.00m 10000.00m 10.00m", "\x00\x33\x16\x13\x89\x17\x2d\xd0\x70\xbe\x15\xf0\x00\x98\x8d\x20",false))
-     (CASE_L(QType::LOC, "42 21 43.952 N 71 5 6.344 W -24m 1m 200m", "42 21 43.952 N 71 5 6.344 W -24.00m 1.00m 200.00m 10.00m", "\x00\x12\x24\x13\x89\x17\x06\x90\x70\xbf\x2d\xd8\x00\x98\x8d\x20",false))
-     (CASE_S(QType::AAAA, "fe80::250:56ff:fe9b:114", "\xFE\x80\x00\x00\x00\x00\x00\x00\x02\x50\x56\xFF\xFE\x9B\x01\x14",false))
-     (CASE_S(QType::AAAA, "2a02:1b8:10:2::151", "\x2a\x02\x01\xb8\x00\x10\x00\x02\x00\x00\x00\x00\x00\x00\x01\x51",false))
-     (CASE_S(QType::AAAA, "::1", "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01",false))
+     (CASE_S(QType::AFSDB, "1 afs-server.example.com.", "\x00\x01\x0a""afs-server\x07""example\x03""com\x00"))
+     (CASE_S(QType::KEY, "0 3 3 V19hwufL6LJARVIxzHDyGdvZ7dbQE0Kyl18yPIWj/sbCcsBbz7zO6Q2qgdzmWI3OvGNne2nxflhorhefKIMsUg==", "\x00\x00\x03\x03\x57\x5f\x61\xc2\xe7\xcb\xe8\xb2\x40\x45\x52\x31\xcc\x70\xf2\x19\xdb\xd9\xed\xd6\xd0\x13\x42\xb2\x97\x5f\x32\x3c\x85\xa3\xfe\xc6\xc2\x72\xc0\x5b\xcf\xbc\xce\xe9\x0d\xaa\x81\xdc\xe6\x58\x8d\xce\xbc\x63\x67\x7b\x69\xf1\x7e\x58\x68\xae\x17\x9f\x28\x83\x2c\x52"))
+     (CASE_S(QType::AAAA, "fe80::250:56ff:fe9b:114", "\xFE\x80\x00\x00\x00\x00\x00\x00\x02\x50\x56\xFF\xFE\x9B\x01\x14"))
+     (CASE_S(QType::AAAA, "2a02:1b8:10:2::151", "\x2a\x02\x01\xb8\x00\x10\x00\x02\x00\x00\x00\x00\x00\x00\x01\x51"))
+     (CASE_S(QType::AAAA, "::1", "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01"))
+     (CASE_L(QType::LOC, "32 7 19 S 116 2 25 E", "32 7 19.000 S 116 2 25.000 E 0.00m 1.00m 10000.00m 10.00m", "\x00\x12\x16\x13\x79\x1b\x7d\x28\x98\xe6\x48\x68\x00\x98\x96\x80"))
+     (CASE_L(QType::LOC, "32 7 19 S 116 2 25 E 10m", "32 7 19.000 S 116 2 25.000 E 10.00m 1.00m 10000.00m 10.00m", "\x00\x12\x16\x13\x79\x1b\x7d\x28\x98\xe6\x48\x68\x00\x98\x9a\x68"))
+     (CASE_L(QType::LOC, "42 21 54 N 71 06 18 W -24m 30m", "42 21 54.000 N 71 6 18.000 W -24.00m 30.00m 10000.00m 10.00m", "\x00\x33\x16\x13\x89\x17\x2d\xd0\x70\xbe\x15\xf0\x00\x98\x8d\x20"))
+     (CASE_L(QType::LOC, "42 21 43.952 N 71 5 6.344 W -24m 1m 200m", "42 21 43.952 N 71 5 6.344 W -24.00m 1.00m 200.00m 10.00m", "\x00\x12\x24\x13\x89\x17\x06\x90\x70\xbf\x2d\xd8\x00\x98\x8d\x20"))
 // local name
-     (CASE_S(QType::SRV, "10 10 5060 sip.rec.test.", "\x00\x0a\x00\x0a\x13\xc4\x03sip\x03rec\x04test\x00",false))
+     (CASE_S(QType::SRV, "10 10 5060 sip.rec.test.", "\x00\x0a\x00\x0a\x13\xc4\x03sip\x03rec\x04test\x00"))
 // non-local name
-     (CASE_S(QType::SRV, "10 10 5060 sip.example.com.", "\x00\x0a\x00\x0a\x13\xc4\x03sip\x07""example\x03""com\x00",false))
+     (CASE_S(QType::SRV, "10 10 5060 sip.example.com.", "\x00\x0a\x00\x0a\x13\xc4\x03sip\x07""example\x03""com\x00"))
 // root name
-     (CASE_S(QType::SRV, "10 10 5060 .", "\x00\x0a\x00\x0a\x13\xc4\x00",false))
+     (CASE_S(QType::SRV, "10 10 5060 .", "\x00\x0a\x00\x0a\x13\xc4\x00"))
 
-     (CASE_S(QType::NAPTR, "100 10 \"\" \"\" \"/urn:cid:.+@([^\\\\.]+\\\\.)(.*)$/\\\\2/i\" .", "\x00\x64\x00\x0a\x00\x00\x20/urn:cid:.+@([^\\.]+\\.)(.*)$/\\2/i\x00",false))
-     (CASE_S(QType::NAPTR, "100 50 \"s\" \"http+I2L+I2C+I2R\" \"\" _http._tcp.rec.test.", "\x00\x64\x00\x32\x01s\x10http+I2L+I2C+I2R\x00\x05_http\x04_tcp\x03rec\x04test\x00",false))
-     (CASE_S(QType::KX, "10 mail.rec.test.", "\x00\x0a\x04mail\x03rec\x04test\x00",false))
+     (CASE_S(QType::NAPTR, "100 10 \"\" \"\" \"/urn:cid:.+@([^\\\\.]+\\\\.)(.*)$/\\\\2/i\" .", "\x00\x64\x00\x0a\x00\x00\x20/urn:cid:.+@([^\\.]+\\.)(.*)$/\\2/i\x00"))
+     (CASE_S(QType::NAPTR, "100 50 \"s\" \"http+I2L+I2C+I2R\" \"\" _http._tcp.rec.test.", "\x00\x64\x00\x32\x01s\x10http+I2L+I2C+I2R\x00\x05_http\x04_tcp\x03rec\x04test\x00"))
+     (CASE_S(QType::KX, "10 mail.rec.test.", "\x00\x0a\x04mail\x03rec\x04test\x00"))
 
 // X.509 as per PKIX
-     (CASE_S(QType::CERT, "1 0 0 MIIB9DCCAV2gAwIBAgIJAKxUfFVXhw7HMA0GCSqGSIb3DQEBBQUAMBMxETAPBgNVBAMMCHJlYy50ZXN0MB4XDTEzMDUxMjE5NDgwOVoXDTEzMDYxMTE5NDgwOVowEzERMA8GA1UEAwwIcmVjLnRlc3QwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBANKCu5aN/ewOXRPfzAo27XMXhYFCThCjfInTAUIEkzs6jBFZ/eyyIa/kFoiD0tAKwfFfykYU+9XgXeLjetD7rYt3SN3bzzCznoBGbGHHM0Fecrn0LV+tC/NfBB61Yx7e0AMUxmxIeLNRQW5ca5CW8qcIiiQ4fl0BScUjc5+E9QLHAgMBAAGjUDBOMB0GA1UdDgQWBBRzcVu/2bwrgkES+FhYbxZqr7mUgjAfBgNVHSMEGDAWgBRzcVu/2bwrgkES+FhYbxZqr7mUgjAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBBQUAA4GBAFVQ8dZBOasOhsWzA/xpAV0WdsqVkxBxrkGIRlbHHBFqOBOOz2MFSzUNx4mDy0qDKI28gcWmWaVsxoQ9VFLD6YRJuUoM8MDNcZDJbKpfDumjvvfnUAK+SiM2c4Ur3xpf0wanCA60/q2bOtFiB0tfAH6RVuIgMC3qjHAIaKEld+fE", "\x00\x01\x00\x00\x00\x30\x82\x01\xf4\x30\x82\x01\x5d\xa0\x03\x02\x01\x02\x02\x09\x00\xac\x54\x7c\x55\x57\x87\x0e\xc7\x30\x0d\x06\x09\x2a\x86\x48\x86\xf7\x0d\x01\x01\x05\x05\x00\x30\x13\x31\x11\x30\x0f\x06\x03\x55\x04\x03\x0c\x08\x72\x65\x63\x2e\x74\x65\x73\x74\x30\x1e\x17\x0d\x31\x33\x30\x35\x31\x32\x31\x39\x34\x38\x30\x39\x5a\x17\x0d\x31\x33\x30\x36\x31\x31\x31\x39\x34\x38\x30\x39\x5a\x30\x13\x31\x11\x30\x0f\x06\x03\x55\x04\x03\x0c\x08\x72\x65\x63\x2e\x74\x65\x73\x74\x30\x81\x9f\x30\x0d\x06\x09\x2a\x86\x48\x86\xf7\x0d\x01\x01\x01\x05\x00\x03\x81\x8d\x00\x30\x81\x89\x02\x81\x81\x00\xd2\x82\xbb\x96\x8d\xfd\xec\x0e\x5d\x13\xdf\xcc\x0a\x36\xed\x73\x17\x85\x81\x42\x4e\x10\xa3\x7c\x89\xd3\x01\x42\x04\x93\x3b\x3a\x8c\x11\x59\xfd\xec\xb2\x21\xaf\xe4\x16\x88\x83\xd2\xd0\x0a\xc1\xf1\x5f\xca\x46\x14\xfb\xd5\xe0\x5d\xe2\xe3\x7a\xd0\xfb\xad\x8b\x77\x48\xdd\xdb\xcf\x30\xb3\x9e\x80\x46\x6c\x61\xc7\x33\x41\x5e\x72\xb9\xf4\x2d\x5f\xad\x0b\xf3\x5f\x04\x1e\xb5\x63\x1e\xde\xd0\x03\x14\xc6\x6c\x48\x78\xb3\x51\x41\x6e\x5c\x6b\x90\x96\xf2\xa7\x08\x8a\x24\x38\x7e\x5d\x01\x49\xc5\x23\x73\x9f\x84\xf5\x02\xc7\x02\x03\x01\x00\x01\xa3\x50\x30\x4e\x30\x1d\x06\x03\x55\x1d\x0e\x04\x16\x04\x14\x73\x71\x5b\xbf\xd9\xbc\x2b\x82\x41\x12\xf8\x58\x58\x6f\x16\x6a\xaf\xb9\x94\x82\x30\x1f\x06\x03\x55\x1d\x23\x04\x18\x30\x16\x80\x14\x73\x71\x5b\xbf\xd9\xbc\x2b\x82\x41\x12\xf8\x58\x58\x6f\x16\x6a\xaf\xb9\x94\x82\x30\x0c\x06\x03\x55\x1d\x13\x04\x05\x30\x03\x01\x01\xff\x30\x0d\x06\x09\x2a\x86\x48\x86\xf7\x0d\x01\x01\x05\x05\x00\x03\x81\x81\x00\x55\x50\xf1\xd6\x41\x39\xab\x0e\x86\xc5\xb3\x03\xfc\x69\x01\x5d\x16\x76\xca\x95\x93\x10\x71\xae\x41\x88\x46\x56\xc7\x1c\x11\x6a\x38\x13\x8e\xcf\x63\x05\x4b\x35\x0d\xc7\x89\x83\xcb\x4a\x83\x28\x8d\xbc\x81\xc5\xa6\x59\xa5\x6c\xc6\x84\x3d\x54\x52\xc3\xe9\x84\x49\xb9\x4a\x0c\xf0\xc0\xcd\x71\x90\xc9\x6c\xaa\x5f\x0e\xe9\xa3\xbe\xf7\xe7\x50\x02\xbe\x4a\x23\x36\x73\x85\x2b\xdf\x1a\x5f\xd3\x06\xa7\x08\x0e\xb4\xfe\xad\x9b\x3a\xd1\x62\x07\x4b\x5f\x00\x7e\x91\x56\xe2\x20\x30\x2d\xea\x8c\x70\x08\x68\xa1\x25\x77\xe7\xc4",false))
-     (CASE_L(QType::DS, "20642 8 2 04443ABE7E94C3985196BEAE5D548C727B044DDA5151E60D7CD76A9F D931D00E", "20642 8 2 04443abe7e94c3985196beae5d548c727b044dda5151e60d7cd76a9fd931d00e", "\x50\xa2\x08\x02\x04\x44\x3a\xbe\x7e\x94\xc3\x98\x51\x96\xbe\xae\x5d\x54\x8c\x72\x7b\x04\x4d\xda\x51\x51\xe6\x0d\x7c\xd7\x6a\x9f\xd9\x31\xd0\x0e",false))
-     (CASE_S(QType::SSHFP, "1 1 aa65e3415a50d9b3519c2b17aceb815fc2538d88", "\x01\x01\xaa\x65\xe3\x41\x5a\x50\xd9\xb3\x51\x9c\x2b\x17\xac\xeb\x81\x5f\xc2\x53\x8d\x88",false))
-// as per RFC4025 
-     (CASE_L(QType::SSHFP, "1 1 aa65e3415a50d9b3519c2b17aceb815fc253 8d88", "1 1 aa65e3415a50d9b3519c2b17aceb815fc2538d88", "\x01\x01\xaa\x65\xe3\x41\x5a\x50\xd9\xb3\x51\x9c\x2b\x17\xac\xeb\x81\x5f\xc2\x53\x8d\x88",false))
+     (CASE_S(QType::CERT, "1 0 0 MIIB9DCCAV2gAwIBAgIJAKxUfFVXhw7HMA0GCSqGSIb3DQEBBQUAMBMxETAPBgNVBAMMCHJlYy50ZXN0MB4XDTEzMDUxMjE5NDgwOVoXDTEzMDYxMTE5NDgwOVowEzERMA8GA1UEAwwIcmVjLnRlc3QwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBANKCu5aN/ewOXRPfzAo27XMXhYFCThCjfInTAUIEkzs6jBFZ/eyyIa/kFoiD0tAKwfFfykYU+9XgXeLjetD7rYt3SN3bzzCznoBGbGHHM0Fecrn0LV+tC/NfBB61Yx7e0AMUxmxIeLNRQW5ca5CW8qcIiiQ4fl0BScUjc5+E9QLHAgMBAAGjUDBOMB0GA1UdDgQWBBRzcVu/2bwrgkES+FhYbxZqr7mUgjAfBgNVHSMEGDAWgBRzcVu/2bwrgkES+FhYbxZqr7mUgjAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBBQUAA4GBAFVQ8dZBOasOhsWzA/xpAV0WdsqVkxBxrkGIRlbHHBFqOBOOz2MFSzUNx4mDy0qDKI28gcWmWaVsxoQ9VFLD6YRJuUoM8MDNcZDJbKpfDumjvvfnUAK+SiM2c4Ur3xpf0wanCA60/q2bOtFiB0tfAH6RVuIgMC3qjHAIaKEld+fE", "\x00\x01\x00\x00\x00\x30\x82\x01\xf4\x30\x82\x01\x5d\xa0\x03\x02\x01\x02\x02\x09\x00\xac\x54\x7c\x55\x57\x87\x0e\xc7\x30\x0d\x06\x09\x2a\x86\x48\x86\xf7\x0d\x01\x01\x05\x05\x00\x30\x13\x31\x11\x30\x0f\x06\x03\x55\x04\x03\x0c\x08\x72\x65\x63\x2e\x74\x65\x73\x74\x30\x1e\x17\x0d\x31\x33\x30\x35\x31\x32\x31\x39\x34\x38\x30\x39\x5a\x17\x0d\x31\x33\x30\x36\x31\x31\x31\x39\x34\x38\x30\x39\x5a\x30\x13\x31\x11\x30\x0f\x06\x03\x55\x04\x03\x0c\x08\x72\x65\x63\x2e\x74\x65\x73\x74\x30\x81\x9f\x30\x0d\x06\x09\x2a\x86\x48\x86\xf7\x0d\x01\x01\x01\x05\x00\x03\x81\x8d\x00\x30\x81\x89\x02\x81\x81\x00\xd2\x82\xbb\x96\x8d\xfd\xec\x0e\x5d\x13\xdf\xcc\x0a\x36\xed\x73\x17\x85\x81\x42\x4e\x10\xa3\x7c\x89\xd3\x01\x42\x04\x93\x3b\x3a\x8c\x11\x59\xfd\xec\xb2\x21\xaf\xe4\x16\x88\x83\xd2\xd0\x0a\xc1\xf1\x5f\xca\x46\x14\xfb\xd5\xe0\x5d\xe2\xe3\x7a\xd0\xfb\xad\x8b\x77\x48\xdd\xdb\xcf\x30\xb3\x9e\x80\x46\x6c\x61\xc7\x33\x41\x5e\x72\xb9\xf4\x2d\x5f\xad\x0b\xf3\x5f\x04\x1e\xb5\x63\x1e\xde\xd0\x03\x14\xc6\x6c\x48\x78\xb3\x51\x41\x6e\x5c\x6b\x90\x96\xf2\xa7\x08\x8a\x24\x38\x7e\x5d\x01\x49\xc5\x23\x73\x9f\x84\xf5\x02\xc7\x02\x03\x01\x00\x01\xa3\x50\x30\x4e\x30\x1d\x06\x03\x55\x1d\x0e\x04\x16\x04\x14\x73\x71\x5b\xbf\xd9\xbc\x2b\x82\x41\x12\xf8\x58\x58\x6f\x16\x6a\xaf\xb9\x94\x82\x30\x1f\x06\x03\x55\x1d\x23\x04\x18\x30\x16\x80\x14\x73\x71\x5b\xbf\xd9\xbc\x2b\x82\x41\x12\xf8\x58\x58\x6f\x16\x6a\xaf\xb9\x94\x82\x30\x0c\x06\x03\x55\x1d\x13\x04\x05\x30\x03\x01\x01\xff\x30\x0d\x06\x09\x2a\x86\x48\x86\xf7\x0d\x01\x01\x05\x05\x00\x03\x81\x81\x00\x55\x50\xf1\xd6\x41\x39\xab\x0e\x86\xc5\xb3\x03\xfc\x69\x01\x5d\x16\x76\xca\x95\x93\x10\x71\xae\x41\x88\x46\x56\xc7\x1c\x11\x6a\x38\x13\x8e\xcf\x63\x05\x4b\x35\x0d\xc7\x89\x83\xcb\x4a\x83\x28\x8d\xbc\x81\xc5\xa6\x59\xa5\x6c\xc6\x84\x3d\x54\x52\xc3\xe9\x84\x49\xb9\x4a\x0c\xf0\xc0\xcd\x71\x90\xc9\x6c\xaa\x5f\x0e\xe9\xa3\xbe\xf7\xe7\x50\x02\xbe\x4a\x23\x36\x73\x85\x2b\xdf\x1a\x5f\xd3\x06\xa7\x08\x0e\xb4\xfe\xad\x9b\x3a\xd1\x62\x07\x4b\x5f\x00\x7e\x91\x56\xe2\x20\x30\x2d\xea\x8c\x70\x08\x68\xa1\x25\x77\xe7\xc4"))
+     (CASE_L(QType::DS, "20642 8 2 04443ABE7E94C3985196BEAE5D548C727B044DDA5151E60D7CD76A9F D931D00E", "20642 8 2 04443abe7e94c3985196beae5d548c727b044dda5151e60d7cd76a9fd931d00e", "\x50\xa2\x08\x02\x04\x44\x3a\xbe\x7e\x94\xc3\x98\x51\x96\xbe\xae\x5d\x54\x8c\x72\x7b\x04\x4d\xda\x51\x51\xe6\x0d\x7c\xd7\x6a\x9f\xd9\x31\xd0\x0e"))
+     (CASE_S(QType::SSHFP, "1 1 aa65e3415a50d9b3519c2b17aceb815fc2538d88", "\x01\x01\xaa\x65\xe3\x41\x5a\x50\xd9\xb3\x51\x9c\x2b\x17\xac\xeb\x81\x5f\xc2\x53\x8d\x88"))
+     (CASE_L(QType::SSHFP, "1 1 aa65e3415a50d9b3519c2b17aceb815fc253 8d88", "1 1 aa65e3415a50d9b3519c2b17aceb815fc2538d88", "\x01\x01\xaa\x65\xe3\x41\x5a\x50\xd9\xb3\x51\x9c\x2b\x17\xac\xeb\x81\x5f\xc2\x53\x8d\x88"))
 // as per RFC4025
-     (CASE_S(QType::IPSECKEY, "255 0 0", "\xff\x00\x00",false))
-     (CASE_S(QType::IPSECKEY, "255 0 1 V19hwufL6LJARVIxzHDyGdvZ7dbQE0Kyl18yPIWj/sbCcsBbz7zO6Q2qgdzmWI3OvGNne2nxflhorhefKIMsUg==", "\xff\x00\x01\x57\x5f\x61\xc2\xe7\xcb\xe8\xb2\x40\x45\x52\x31\xcc\x70\xf2\x19\xdb\xd9\xed\xd6\xd0\x13\x42\xb2\x97\x5f\x32\x3c\x85\xa3\xfe\xc6\xc2\x72\xc0\x5b\xcf\xbc\xce\xe9\x0d\xaa\x81\xdc\xe6\x58\x8d\xce\xbc\x63\x67\x7b\x69\xf1\x7e\x58\x68\xae\x17\x9f\x28\x83\x2c\x52",false))
-     (CASE_S(QType::IPSECKEY, "255 1 0 127.0.0.1", "\xff\x01\x00\x7f\x00\x00\x01", false))
-     (CASE_S(QType::IPSECKEY, "255 2 0 fe80::250:56ff:fe9b:114", "\xff\x02\x00\xFE\x80\x00\x00\x00\x00\x00\x00\x02\x50\x56\xFF\xFE\x9B\x01\x14", false))
-     (CASE_S(QType::IPSECKEY, "10 1 1 127.0.0.1 V19hwufL6LJARVIxzHDyGdvZ7dbQE0Kyl18yPIWj/sbCcsBbz7zO6Q2qgdzmWI3OvGNne2nxflhorhefKIMsUg==", "\x0a\x01\x01\x7f\x00\x00\x01\x57\x5f\x61\xc2\xe7\xcb\xe8\xb2\x40\x45\x52\x31\xcc\x70\xf2\x19\xdb\xd9\xed\xd6\xd0\x13\x42\xb2\x97\x5f\x32\x3c\x85\xa3\xfe\xc6\xc2\x72\xc0\x5b\xcf\xbc\xce\xe9\x0d\xaa\x81\xdc\xe6\x58\x8d\xce\xbc\x63\x67\x7b\x69\xf1\x7e\x58\x68\xae\x17\x9f\x28\x83\x2c\x52", false))
-     (CASE_S(QType::IPSECKEY, "10 2 1 fe80::250:56ff:fe9b:114 V19hwufL6LJARVIxzHDyGdvZ7dbQE0Kyl18yPIWj/sbCcsBbz7zO6Q2qgdzmWI3OvGNne2nxflhorhefKIMsUg==", "\x0a\x02\x01\xFE\x80\x00\x00\x00\x00\x00\x00\x02\x50\x56\xFF\xFE\x9B\x01\x14\x57\x5f\x61\xc2\xe7\xcb\xe8\xb2\x40\x45\x52\x31\xcc\x70\xf2\x19\xdb\xd9\xed\xd6\xd0\x13\x42\xb2\x97\x5f\x32\x3c\x85\xa3\xfe\xc6\xc2\x72\xc0\x5b\xcf\xbc\xce\xe9\x0d\xaa\x81\xdc\xe6\x58\x8d\xce\xbc\x63\x67\x7b\x69\xf1\x7e\x58\x68\xae\x17\x9f\x28\x83\x2c\x52", false))
-     (CASE_S(QType::IPSECKEY, "10 3 1 gw.rec.test. V19hwufL6LJARVIxzHDyGdvZ7dbQE0Kyl18yPIWj/sbCcsBbz7zO6Q2qgdzmWI3OvGNne2nxflhorhefKIMsUg==", "\x0a\x03\x01\x02gw\x03rec\x04test\x00\x57\x5f\x61\xc2\xe7\xcb\xe8\xb2\x40\x45\x52\x31\xcc\x70\xf2\x19\xdb\xd9\xed\xd6\xd0\x13\x42\xb2\x97\x5f\x32\x3c\x85\xa3\xfe\xc6\xc2\x72\xc0\x5b\xcf\xbc\xce\xe9\x0d\xaa\x81\xdc\xe6\x58\x8d\xce\xbc\x63\x67\x7b\x69\xf1\x7e\x58\x68\xae\x17\x9f\x28\x83\x2c\x52",false))
-
-     (CASE_S(QType::RRSIG, "SOA 8 3 300 20130523000000 20130509000000 54216 rec.test. ecWKD/OsdAiXpbM/sgPT82KVD/WiQnnqcxoJgiH3ixHa+LOAcYU7FG7V4BRRJxLriY1e0rB2gAs3kCel9D4bzfK6wAqG4Di/eHUgHptRlaR2ycELJ4t1pjzrnuGiIzA1wM2izRmeE+Xoy1367Qu0pOz5DLzTfQITWFsB2iUzN4Y=", "\x00\x06\x08\x03\x00\x00\x01\x2c\x51\x9d\x5c\x00\x51\x8a\xe7\x00\xd3\xc8\x03\x72\x65\x63\x04\x74\x65\x73\x74\x00\x79\xc5\x8a\x0f\xf3\xac\x74\x08\x97\xa5\xb3\x3f\xb2\x03\xd3\xf3\x62\x95\x0f\xf5\xa2\x42\x79\xea\x73\x1a\x09\x82\x21\xf7\x8b\x11\xda\xf8\xb3\x80\x71\x85\x3b\x14\x6e\xd5\xe0\x14\x51\x27\x12\xeb\x89\x8d\x5e\xd2\xb0\x76\x80\x0b\x37\x90\x27\xa5\xf4\x3e\x1b\xcd\xf2\xba\xc0\x0a\x86\xe0\x38\xbf\x78\x75\x20\x1e\x9b\x51\x95\xa4\x76\xc9\xc1\x0b\x27\x8b\x75\xa6\x3c\xeb\x9e\xe1\xa2\x23\x30\x35\xc0\xcd\xa2\xcd\x19\x9e\x13\xe5\xe8\xcb\x5d\xfa\xed\x0b\xb4\xa4\xec\xf9\x0c\xbc\xd3\x7d\x02\x13\x58\x5b\x01\xda\x25\x33\x37\x86",false))
+     (CASE_S(QType::IPSECKEY, "255 0 0", "\xff\x00\x00"))
+     (CASE_S(QType::IPSECKEY, "255 0 1 V19hwufL6LJARVIxzHDyGdvZ7dbQE0Kyl18yPIWj/sbCcsBbz7zO6Q2qgdzmWI3OvGNne2nxflhorhefKIMsUg==", "\xff\x00\x01\x57\x5f\x61\xc2\xe7\xcb\xe8\xb2\x40\x45\x52\x31\xcc\x70\xf2\x19\xdb\xd9\xed\xd6\xd0\x13\x42\xb2\x97\x5f\x32\x3c\x85\xa3\xfe\xc6\xc2\x72\xc0\x5b\xcf\xbc\xce\xe9\x0d\xaa\x81\xdc\xe6\x58\x8d\xce\xbc\x63\x67\x7b\x69\xf1\x7e\x58\x68\xae\x17\x9f\x28\x83\x2c\x52"))
+     (CASE_S(QType::IPSECKEY, "255 1 0 127.0.0.1", "\xff\x01\x00\x7f\x00\x00\x01"))
+     (CASE_S(QType::IPSECKEY, "255 2 0 fe80::250:56ff:fe9b:114", "\xff\x02\x00\xFE\x80\x00\x00\x00\x00\x00\x00\x02\x50\x56\xFF\xFE\x9B\x01\x14"))
+     (CASE_S(QType::IPSECKEY, "10 1 1 127.0.0.1 V19hwufL6LJARVIxzHDyGdvZ7dbQE0Kyl18yPIWj/sbCcsBbz7zO6Q2qgdzmWI3OvGNne2nxflhorhefKIMsUg==", "\x0a\x01\x01\x7f\x00\x00\x01\x57\x5f\x61\xc2\xe7\xcb\xe8\xb2\x40\x45\x52\x31\xcc\x70\xf2\x19\xdb\xd9\xed\xd6\xd0\x13\x42\xb2\x97\x5f\x32\x3c\x85\xa3\xfe\xc6\xc2\x72\xc0\x5b\xcf\xbc\xce\xe9\x0d\xaa\x81\xdc\xe6\x58\x8d\xce\xbc\x63\x67\x7b\x69\xf1\x7e\x58\x68\xae\x17\x9f\x28\x83\x2c\x52"))
+     (CASE_S(QType::IPSECKEY, "10 2 1 fe80::250:56ff:fe9b:114 V19hwufL6LJARVIxzHDyGdvZ7dbQE0Kyl18yPIWj/sbCcsBbz7zO6Q2qgdzmWI3OvGNne2nxflhorhefKIMsUg==", "\x0a\x02\x01\xFE\x80\x00\x00\x00\x00\x00\x00\x02\x50\x56\xFF\xFE\x9B\x01\x14\x57\x5f\x61\xc2\xe7\xcb\xe8\xb2\x40\x45\x52\x31\xcc\x70\xf2\x19\xdb\xd9\xed\xd6\xd0\x13\x42\xb2\x97\x5f\x32\x3c\x85\xa3\xfe\xc6\xc2\x72\xc0\x5b\xcf\xbc\xce\xe9\x0d\xaa\x81\xdc\xe6\x58\x8d\xce\xbc\x63\x67\x7b\x69\xf1\x7e\x58\x68\xae\x17\x9f\x28\x83\x2c\x52"))
+     (CASE_S(QType::IPSECKEY, "10 3 1 gw.rec.test. V19hwufL6LJARVIxzHDyGdvZ7dbQE0Kyl18yPIWj/sbCcsBbz7zO6Q2qgdzmWI3OvGNne2nxflhorhefKIMsUg==", "\x0a\x03\x01\x02gw\x03rec\x04test\x00\x57\x5f\x61\xc2\xe7\xcb\xe8\xb2\x40\x45\x52\x31\xcc\x70\xf2\x19\xdb\xd9\xed\xd6\xd0\x13\x42\xb2\x97\x5f\x32\x3c\x85\xa3\xfe\xc6\xc2\x72\xc0\x5b\xcf\xbc\xce\xe9\x0d\xaa\x81\xdc\xe6\x58\x8d\xce\xbc\x63\x67\x7b\x69\xf1\x7e\x58\x68\xae\x17\x9f\x28\x83\x2c\x52"))
+
+     (CASE_S(QType::RRSIG, "SOA 8 3 300 20130523000000 20130509000000 54216 rec.test. ecWKD/OsdAiXpbM/sgPT82KVD/WiQnnqcxoJgiH3ixHa+LOAcYU7FG7V4BRRJxLriY1e0rB2gAs3kCel9D4bzfK6wAqG4Di/eHUgHptRlaR2ycELJ4t1pjzrnuGiIzA1wM2izRmeE+Xoy1367Qu0pOz5DLzTfQITWFsB2iUzN4Y=", "\x00\x06\x08\x03\x00\x00\x01\x2c\x51\x9d\x5c\x00\x51\x8a\xe7\x00\xd3\xc8\x03\x72\x65\x63\x04\x74\x65\x73\x74\x00\x79\xc5\x8a\x0f\xf3\xac\x74\x08\x97\xa5\xb3\x3f\xb2\x03\xd3\xf3\x62\x95\x0f\xf5\xa2\x42\x79\xea\x73\x1a\x09\x82\x21\xf7\x8b\x11\xda\xf8\xb3\x80\x71\x85\x3b\x14\x6e\xd5\xe0\x14\x51\x27\x12\xeb\x89\x8d\x5e\xd2\xb0\x76\x80\x0b\x37\x90\x27\xa5\xf4\x3e\x1b\xcd\xf2\xba\xc0\x0a\x86\xe0\x38\xbf\x78\x75\x20\x1e\x9b\x51\x95\xa4\x76\xc9\xc1\x0b\x27\x8b\x75\xa6\x3c\xeb\x9e\xe1\xa2\x23\x30\x35\xc0\xcd\xa2\xcd\x19\x9e\x13\xe5\xe8\xcb\x5d\xfa\xed\x0b\xb4\xa4\xec\xf9\x0c\xbc\xd3\x7d\x02\x13\x58\x5b\x01\xda\x25\x33\x37\x86"))
      // timestamps can be given as (32-bit wrapped) epoch too:
-     (CASE_L(QType::RRSIG, "SOA 8 3 300 1369267200 1368057600 54216 rec.test. ecWKD/OsdAiXpbM/sgPT82KVD/WiQnnqcxoJgiH3ixHa+LOAcYU7FG7V4BRRJxLriY1e0rB2gAs3kCel9D4bzfK6wAqG4Di/eHUgHptRlaR2ycELJ4t1pjzrnuGiIzA1wM2izRmeE+Xoy1367Qu0pOz5DLzTfQITWFsB2iUzN4Y=", "SOA 8 3 300 20130523000000 20130509000000 54216 rec.test. ecWKD/OsdAiXpbM/sgPT82KVD/WiQnnqcxoJgiH3ixHa+LOAcYU7FG7V4BRRJxLriY1e0rB2gAs3kCel9D4bzfK6wAqG4Di/eHUgHptRlaR2ycELJ4t1pjzrnuGiIzA1wM2izRmeE+Xoy1367Qu0pOz5DLzTfQITWFsB2iUzN4Y=", "\x00\x06\x08\x03\x00\x00\x01\x2c\x51\x9d\x5c\x00\x51\x8a\xe7\x00\xd3\xc8\x03\x72\x65\x63\x04\x74\x65\x73\x74\x00\x79\xc5\x8a\x0f\xf3\xac\x74\x08\x97\xa5\xb3\x3f\xb2\x03\xd3\xf3\x62\x95\x0f\xf5\xa2\x42\x79\xea\x73\x1a\x09\x82\x21\xf7\x8b\x11\xda\xf8\xb3\x80\x71\x85\x3b\x14\x6e\xd5\xe0\x14\x51\x27\x12\xeb\x89\x8d\x5e\xd2\xb0\x76\x80\x0b\x37\x90\x27\xa5\xf4\x3e\x1b\xcd\xf2\xba\xc0\x0a\x86\xe0\x38\xbf\x78\x75\x20\x1e\x9b\x51\x95\xa4\x76\xc9\xc1\x0b\x27\x8b\x75\xa6\x3c\xeb\x9e\xe1\xa2\x23\x30\x35\xc0\xcd\xa2\xcd\x19\x9e\x13\xe5\xe8\xcb\x5d\xfa\xed\x0b\xb4\xa4\xec\xf9\x0c\xbc\xd3\x7d\x02\x13\x58\x5b\x01\xda\x25\x33\x37\x86",false))
-     (CASE_S(QType::NSEC, "a.rec.test. A NS SOA MX AAAA RRSIG NSEC DNSKEY", "\x01""a\x03rec\x04test\x00\x00\x07\x62\x01\x00\x08\x00\x03\x80",false))
-     (CASE_S(QType::DNSKEY, "257 3 5 AwEAAZVtlHc8O4TVmlGx/PGJTc7hbVjMR7RywxLuAm1dqgyHvgNRD7chYLsALOdZKW6VRvusbyhoOPilnh8XpucBDqjGD6lIemsURz7drZEqcLupVA0TPxXABZ6auJ3jumqIhSOcLj9rpSwI4xuWt0yu6LR9tL2q8+A0yEZxcAaKS+Wq0fExJ93NxgXl1/fY+JcYQvonjd31GxXXef9uf0exXyzowh5h8+IIBETU+ZiYVB5BqiwkICZL/OX57idm99ycA2/tIen66F8u2ueTvgPcecnoqHvW0MtLQKzeNmqdGNthHhV5di0SZdMZQeo/izs68uN2WzqQDZy9Ec2JwBTbxWE=", "\x01\x01\x03\x05\x03\x01\x00\x01\x95\x6d\x94\x77\x3c\x3b\x84\xd5\x9a\x51\xb1\xfc\xf1\x89\x4d\xce\xe1\x6d\x58\xcc\x47\xb4\x72\xc3\x12\xee\x02\x6d\x5d\xaa\x0c\x87\xbe\x03\x51\x0f\xb7\x21\x60\xbb\x00\x2c\xe7\x59\x29\x6e\x95\x46\xfb\xac\x6f\x28\x68\x38\xf8\xa5\x9e\x1f\x17\xa6\xe7\x01\x0e\xa8\xc6\x0f\xa9\x48\x7a\x6b\x14\x47\x3e\xdd\xad\x91\x2a\x70\xbb\xa9\x54\x0d\x13\x3f\x15\xc0\x05\x9e\x9a\xb8\x9d\xe3\xba\x6a\x88\x85\x23\x9c\x2e\x3f\x6b\xa5\x2c\x08\xe3\x1b\x96\xb7\x4c\xae\xe8\xb4\x7d\xb4\xbd\xaa\xf3\xe0\x34\xc8\x46\x71\x70\x06\x8a\x4b\xe5\xaa\xd1\xf1\x31\x27\xdd\xcd\xc6\x05\xe5\xd7\xf7\xd8\xf8\x97\x18\x42\xfa\x27\x8d\xdd\xf5\x1b\x15\xd7\x79\xff\x6e\x7f\x47\xb1\x5f\x2c\xe8\xc2\x1e\x61\xf3\xe2\x08\x04\x44\xd4\xf9\x98\x98\x54\x1e\x41\xaa\x2c\x24\x20\x26\x4b\xfc\xe5\xf9\xee\x27\x66\xf7\xdc\x9c\x03\x6f\xed\x21\xe9\xfa\xe8\x5f\x2e\xda\xe7\x93\xbe\x03\xdc\x79\xc9\xe8\xa8\x7b\xd6\xd0\xcb\x4b\x40\xac\xde\x36\x6a\x9d\x18\xdb\x61\x1e\x15\x79\x76\x2d\x12\x65\xd3\x19\x41\xea\x3f\x8b\x3b\x3a\xf2\xe3\x76\x5b\x3a\x90\x0d\x9c\xbd\x11\xcd\x89\xc0\x14\xdb\xc5\x61",false))
-
-     (CASE_S(QType::DHCID, "AAAB92JtyyO73zqENgu9LVua+0PZoeCcKapTw4asbYmx5F4=", "\x00\x00\x01\xf7\x62\x6d\xcb\x23\xbb\xdf\x3a\x84\x36\x0b\xbd\x2d\x5b\x9a\xfb\x43\xd9\xa1\xe0\x9c\x29\xaa\x53\xc3\x86\xac\x6d\x89\xb1\xe4\x5e",false))
-     (CASE_S(QType::DHCID, "AAEB92JtyyO73zqENgu9LVua+0PZoeCcKapTw4asbYmx5F4=", "\x00\x01\x01\xf7\x62\x6d\xcb\x23\xbb\xdf\x3a\x84\x36\x0b\xbd\x2d\x5b\x9a\xfb\x43\xd9\xa1\xe0\x9c\x29\xaa\x53\xc3\x86\xac\x6d\x89\xb1\xe4\x5e",false))
-     (CASE_S(QType::DHCID, "AAIB92JtyyO73zqENgu9LVua+0PZoeCcKapTw4asbYmx5F4=", "\x00\x02\x01\xf7\x62\x6d\xcb\x23\xbb\xdf\x3a\x84\x36\x0b\xbd\x2d\x5b\x9a\xfb\x43\xd9\xa1\xe0\x9c\x29\xaa\x53\xc3\x86\xac\x6d\x89\xb1\xe4\x5e",false))
-
-     (CASE_S(QType::NSEC3, "1 1 1 f00b RPF1JGFCCNFA7STPTIJ9FPFNM40A4FLL NS SOA RRSIG DNSKEY NSEC3PARAM", "\x01\x01\x00\x01\x02\xf0\x0b\x14\xde\x5e\x19\xc1\xec\x65\xde\xa3\xf3\xb9\xec\xa6\x97\xe5\xf7\xb1\x00\xa2\x3e\xb5\x00\x07\x22\x00\x00\x00\x00\x02\x90",false))
-     (CASE_S(QType::NSEC3PARAM, "1 0 1 f00b", "\x01\x00\x00\x01\x02\xf0\x0b",false))
-
-     (CASE_S(QType::TLSA, "0 0 0 308201f43082015da003020102020900ac547c5557870ec7300d06092a864886f70d010105050030133111300f06035504030c087265632e74657374301e170d3133303531323139343830395a170d3133303631313139343830395a30133111300f06035504030c087265632e7465737430819f300d06092a864886f70d010101050003818d0030818902818100d282bb968dfdec0e5d13dfcc0a36ed73178581424e10a37c89d3014204933b3a8c1159fdecb221afe4168883d2d00ac1f15fca4614fbd5e05de2e37ad0fbad8b7748dddbcf30b39e80466c61c733415e72b9f42d5fad0bf35f041eb5631eded00314c66c4878b351416e5c6b9096f2a7088a24387e5d0149c523739f84f502c70203010001a350304e301d0603551d0e0416041473715bbfd9bc2b824112f858586f166aafb99482301f0603551d2304183016801473715bbfd9bc2b824112f858586f166aafb99482300c0603551d13040530030101ff300d06092a864886f70d0101050500038181005550f1d64139ab0e86c5b303fc69015d1676ca95931071ae41884656c71c116a38138ecf63054b350dc78983cb4a83288dbc81c5a659a56cc6843d5452c3e98449b94a0cf0c0cd7190c96caa5f0ee9a3bef7e75002be4a233673852bdf1a5fd306a7080eb4fead9b3ad162074b5f007e9156e220302dea8c700868a12577e7c4", "\x00\x00\x00\x30\x82\x01\xf4\x30\x82\x01\x5d\xa0\x03\x02\x01\x02\x02\x09\x00\xac\x54\x7c\x55\x57\x87\x0e\xc7\x30\x0d\x06\x09\x2a\x86\x48\x86\xf7\x0d\x01\x01\x05\x05\x00\x30\x13\x31\x11\x30\x0f\x06\x03\x55\x04\x03\x0c\x08\x72\x65\x63\x2e\x74\x65\x73\x74\x30\x1e\x17\x0d\x31\x33\x30\x35\x31\x32\x31\x39\x34\x38\x30\x39\x5a\x17\x0d\x31\x33\x30\x36\x31\x31\x31\x39\x34\x38\x30\x39\x5a\x30\x13\x31\x11\x30\x0f\x06\x03\x55\x04\x03\x0c\x08\x72\x65\x63\x2e\x74\x65\x73\x74\x30\x81\x9f\x30\x0d\x06\x09\x2a\x86\x48\x86\xf7\x0d\x01\x01\x01\x05\x00\x03\x81\x8d\x00\x30\x81\x89\x02\x81\x81\x00\xd2\x82\xbb\x96\x8d\xfd\xec\x0e\x5d\x13\xdf\xcc\x0a\x36\xed\x73\x17\x85\x81\x42\x4e\x10\xa3\x7c\x89\xd3\x01\x42\x04\x93\x3b\x3a\x8c\x11\x59\xfd\xec\xb2\x21\xaf\xe4\x16\x88\x83\xd2\xd0\x0a\xc1\xf1\x5f\xca\x46\x14\xfb\xd5\xe0\x5d\xe2\xe3\x7a\xd0\xfb\xad\x8b\x77\x48\xdd\xdb\xcf\x30\xb3\x9e\x80\x46\x6c\x61\xc7\x33\x41\x5e\x72\xb9\xf4\x2d\x5f\xad\x0b\xf3\x5f\x04\x1e\xb5\x63\x1e\xde\xd0\x03\x14\xc6\x6c\x48\x78\xb3\x51\x41\x6e\x5c\x6b\x90\x96\xf2\xa7\x08\x8a\x24\x38\x7e\x5d\x01\x49\xc5\x23\x73\x9f\x84\xf5\x02\xc7\x02\x03\x01\x00\x01\xa3\x50\x30\x4e\x30\x1d\x06\x03\x55\x1d\x0e\x04\x16\x04\x14\x73\x71\x5b\xbf\xd9\xbc\x2b\x82\x41\x12\xf8\x58\x58\x6f\x16\x6a\xaf\xb9\x94\x82\x30\x1f\x06\x03\x55\x1d\x23\x04\x18\x30\x16\x80\x14\x73\x71\x5b\xbf\xd9\xbc\x2b\x82\x41\x12\xf8\x58\x58\x6f\x16\x6a\xaf\xb9\x94\x82\x30\x0c\x06\x03\x55\x1d\x13\x04\x05\x30\x03\x01\x01\xff\x30\x0d\x06\x09\x2a\x86\x48\x86\xf7\x0d\x01\x01\x05\x05\x00\x03\x81\x81\x00\x55\x50\xf1\xd6\x41\x39\xab\x0e\x86\xc5\xb3\x03\xfc\x69\x01\x5d\x16\x76\xca\x95\x93\x10\x71\xae\x41\x88\x46\x56\xc7\x1c\x11\x6a\x38\x13\x8e\xcf\x63\x05\x4b\x35\x0d\xc7\x89\x83\xcb\x4a\x83\x28\x8d\xbc\x81\xc5\xa6\x59\xa5\x6c\xc6\x84\x3d\x54\x52\xc3\xe9\x84\x49\xb9\x4a\x0c\xf0\xc0\xcd\x71\x90\xc9\x6c\xaa\x5f\x0e\xe9\xa3\xbe\xf7\xe7\x50\x02\xbe\x4a\x23\x36\x73\x85\x2b\xdf\x1a\x5f\xd3\x06\xa7\x08\x0e\xb4\xfe\xad\x9b\x3a\xd1\x62\x07\x4b\x5f\x00\x7e\x91\x56\xe2\x20\x30\x2d\xea\x8c\x70\x08\x68\xa1\x25\x77\xe7\xc4",false))
-     (CASE_S(QType::TLSA, "0 1 0 30819f300d06092a864886f70d010101050003818d0030818902818100d282bb968dfdec0e5d13dfcc0a36ed73178581424e10a37c89d3014204933b3a8c1159fdecb221afe4168883d2d00ac1f15fca4614fbd5e05de2e37ad0fbad8b7748dddbcf30b39e80466c61c733415e72b9f42d5fad0bf35f041eb5631eded00314c66c4878b351416e5c6b9096f2a7088a24387e5d0149c523739f84f502c70203010001", "\x00\x01\x00\x30\x81\x9f\x30\x0d\x06\x09\x2a\x86\x48\x86\xf7\x0d\x01\x01\x01\x05\x00\x03\x81\x8d\x00\x30\x81\x89\x02\x81\x81\x00\xd2\x82\xbb\x96\x8d\xfd\xec\x0e\x5d\x13\xdf\xcc\x0a\x36\xed\x73\x17\x85\x81\x42\x4e\x10\xa3\x7c\x89\xd3\x01\x42\x04\x93\x3b\x3a\x8c\x11\x59\xfd\xec\xb2\x21\xaf\xe4\x16\x88\x83\xd2\xd0\x0a\xc1\xf1\x5f\xca\x46\x14\xfb\xd5\xe0\x5d\xe2\xe3\x7a\xd0\xfb\xad\x8b\x77\x48\xdd\xdb\xcf\x30\xb3\x9e\x80\x46\x6c\x61\xc7\x33\x41\x5e\x72\xb9\xf4\x2d\x5f\xad\x0b\xf3\x5f\x04\x1e\xb5\x63\x1e\xde\xd0\x03\x14\xc6\x6c\x48\x78\xb3\x51\x41\x6e\x5c\x6b\x90\x96\xf2\xa7\x08\x8a\x24\x38\x7e\x5d\x01\x49\xc5\x23\x73\x9f\x84\xf5\x02\xc7\x02\x03\x01\x00\x01",false))
-     (CASE_S(QType::TLSA, "1 0 0 308201f43082015da003020102020900ac547c5557870ec7300d06092a864886f70d010105050030133111300f06035504030c087265632e74657374301e170d3133303531323139343830395a170d3133303631313139343830395a30133111300f06035504030c087265632e7465737430819f300d06092a864886f70d010101050003818d0030818902818100d282bb968dfdec0e5d13dfcc0a36ed73178581424e10a37c89d3014204933b3a8c1159fdecb221afe4168883d2d00ac1f15fca4614fbd5e05de2e37ad0fbad8b7748dddbcf30b39e80466c61c733415e72b9f42d5fad0bf35f041eb5631eded00314c66c4878b351416e5c6b9096f2a7088a24387e5d0149c523739f84f502c70203010001a350304e301d0603551d0e0416041473715bbfd9bc2b824112f858586f166aafb99482301f0603551d2304183016801473715bbfd9bc2b824112f858586f166aafb99482300c0603551d13040530030101ff300d06092a864886f70d0101050500038181005550f1d64139ab0e86c5b303fc69015d1676ca95931071ae41884656c71c116a38138ecf63054b350dc78983cb4a83288dbc81c5a659a56cc6843d5452c3e98449b94a0cf0c0cd7190c96caa5f0ee9a3bef7e75002be4a233673852bdf1a5fd306a7080eb4fead9b3ad162074b5f007e9156e220302dea8c700868a12577e7c4", "\x01\x00\x00\x30\x82\x01\xf4\x30\x82\x01\x5d\xa0\x03\x02\x01\x02\x02\x09\x00\xac\x54\x7c\x55\x57\x87\x0e\xc7\x30\x0d\x06\x09\x2a\x86\x48\x86\xf7\x0d\x01\x01\x05\x05\x00\x30\x13\x31\x11\x30\x0f\x06\x03\x55\x04\x03\x0c\x08\x72\x65\x63\x2e\x74\x65\x73\x74\x30\x1e\x17\x0d\x31\x33\x30\x35\x31\x32\x31\x39\x34\x38\x30\x39\x5a\x17\x0d\x31\x33\x30\x36\x31\x31\x31\x39\x34\x38\x30\x39\x5a\x30\x13\x31\x11\x30\x0f\x06\x03\x55\x04\x03\x0c\x08\x72\x65\x63\x2e\x74\x65\x73\x74\x30\x81\x9f\x30\x0d\x06\x09\x2a\x86\x48\x86\xf7\x0d\x01\x01\x01\x05\x00\x03\x81\x8d\x00\x30\x81\x89\x02\x81\x81\x00\xd2\x82\xbb\x96\x8d\xfd\xec\x0e\x5d\x13\xdf\xcc\x0a\x36\xed\x73\x17\x85\x81\x42\x4e\x10\xa3\x7c\x89\xd3\x01\x42\x04\x93\x3b\x3a\x8c\x11\x59\xfd\xec\xb2\x21\xaf\xe4\x16\x88\x83\xd2\xd0\x0a\xc1\xf1\x5f\xca\x46\x14\xfb\xd5\xe0\x5d\xe2\xe3\x7a\xd0\xfb\xad\x8b\x77\x48\xdd\xdb\xcf\x30\xb3\x9e\x80\x46\x6c\x61\xc7\x33\x41\x5e\x72\xb9\xf4\x2d\x5f\xad\x0b\xf3\x5f\x04\x1e\xb5\x63\x1e\xde\xd0\x03\x14\xc6\x6c\x48\x78\xb3\x51\x41\x6e\x5c\x6b\x90\x96\xf2\xa7\x08\x8a\x24\x38\x7e\x5d\x01\x49\xc5\x23\x73\x9f\x84\xf5\x02\xc7\x02\x03\x01\x00\x01\xa3\x50\x30\x4e\x30\x1d\x06\x03\x55\x1d\x0e\x04\x16\x04\x14\x73\x71\x5b\xbf\xd9\xbc\x2b\x82\x41\x12\xf8\x58\x58\x6f\x16\x6a\xaf\xb9\x94\x82\x30\x1f\x06\x03\x55\x1d\x23\x04\x18\x30\x16\x80\x14\x73\x71\x5b\xbf\xd9\xbc\x2b\x82\x41\x12\xf8\x58\x58\x6f\x16\x6a\xaf\xb9\x94\x82\x30\x0c\x06\x03\x55\x1d\x13\x04\x05\x30\x03\x01\x01\xff\x30\x0d\x06\x09\x2a\x86\x48\x86\xf7\x0d\x01\x01\x05\x05\x00\x03\x81\x81\x00\x55\x50\xf1\xd6\x41\x39\xab\x0e\x86\xc5\xb3\x03\xfc\x69\x01\x5d\x16\x76\xca\x95\x93\x10\x71\xae\x41\x88\x46\x56\xc7\x1c\x11\x6a\x38\x13\x8e\xcf\x63\x05\x4b\x35\x0d\xc7\x89\x83\xcb\x4a\x83\x28\x8d\xbc\x81\xc5\xa6\x59\xa5\x6c\xc6\x84\x3d\x54\x52\xc3\xe9\x84\x49\xb9\x4a\x0c\xf0\xc0\xcd\x71\x90\xc9\x6c\xaa\x5f\x0e\xe9\xa3\xbe\xf7\xe7\x50\x02\xbe\x4a\x23\x36\x73\x85\x2b\xdf\x1a\x5f\xd3\x06\xa7\x08\x0e\xb4\xfe\xad\x9b\x3a\xd1\x62\x07\x4b\x5f\x00\x7e\x91\x56\xe2\x20\x30\x2d\xea\x8c\x70\x08\x68\xa1\x25\x77\xe7\xc4",false))
-     (CASE_S(QType::TLSA, "1 1 0 30819f300d06092a864886f70d010101050003818d0030818902818100d282bb968dfdec0e5d13dfcc0a36ed73178581424e10a37c89d3014204933b3a8c1159fdecb221afe4168883d2d00ac1f15fca4614fbd5e05de2e37ad0fbad8b7748dddbcf30b39e80466c61c733415e72b9f42d5fad0bf35f041eb5631eded00314c66c4878b351416e5c6b9096f2a7088a24387e5d0149c523739f84f502c70203010001", "\x01\x01\x00\x30\x81\x9f\x30\x0d\x06\x09\x2a\x86\x48\x86\xf7\x0d\x01\x01\x01\x05\x00\x03\x81\x8d\x00\x30\x81\x89\x02\x81\x81\x00\xd2\x82\xbb\x96\x8d\xfd\xec\x0e\x5d\x13\xdf\xcc\x0a\x36\xed\x73\x17\x85\x81\x42\x4e\x10\xa3\x7c\x89\xd3\x01\x42\x04\x93\x3b\x3a\x8c\x11\x59\xfd\xec\xb2\x21\xaf\xe4\x16\x88\x83\xd2\xd0\x0a\xc1\xf1\x5f\xca\x46\x14\xfb\xd5\xe0\x5d\xe2\xe3\x7a\xd0\xfb\xad\x8b\x77\x48\xdd\xdb\xcf\x30\xb3\x9e\x80\x46\x6c\x61\xc7\x33\x41\x5e\x72\xb9\xf4\x2d\x5f\xad\x0b\xf3\x5f\x04\x1e\xb5\x63\x1e\xde\xd0\x03\x14\xc6\x6c\x48\x78\xb3\x51\x41\x6e\x5c\x6b\x90\x96\xf2\xa7\x08\x8a\x24\x38\x7e\x5d\x01\x49\xc5\x23\x73\x9f\x84\xf5\x02\xc7\x02\x03\x01\x00\x01",false))
-     (CASE_S(QType::TLSA, "1 0 1 6acea2f68b03d9efe97a967e137aca6ac3a89490d532d87806d9e9c257668453", "\x01\x00\x01\x6a\xce\xa2\xf6\x8b\x03\xd9\xef\xe9\x7a\x96\x7e\x13\x7a\xca\x6a\xc3\xa8\x94\x90\xd5\x32\xd8\x78\x06\xd9\xe9\xc2\x57\x66\x84\x53",false))
-     (CASE_S(QType::TLSA, "1 0 2 e6dce237992803488d11d828b7728deddd4577de73d7d078338c8a45880beddff98e076a28bf8e3068da8e73667b802a721c95d7323b038c60200a430cb6fbd4", "\x01\x00\x02\xe6\xdc\xe2\x37\x99\x28\x03\x48\x8d\x11\xd8\x28\xb7\x72\x8d\xed\xdd\x45\x77\xde\x73\xd7\xd0\x78\x33\x8c\x8a\x45\x88\x0b\xed\xdf\xf9\x8e\x07\x6a\x28\xbf\x8e\x30\x68\xda\x8e\x73\x66\x7b\x80\x2a\x72\x1c\x95\xd7\x32\x3b\x03\x8c\x60\x20\x0a\x43\x0c\xb6\xfb\xd4",false))
-     (CASE_S(QType::TLSA, "2 0 0 308201f43082015da003020102020900ac547c5557870ec7300d06092a864886f70d010105050030133111300f06035504030c087265632e74657374301e170d3133303531323139343830395a170d3133303631313139343830395a30133111300f06035504030c087265632e7465737430819f300d06092a864886f70d010101050003818d0030818902818100d282bb968dfdec0e5d13dfcc0a36ed73178581424e10a37c89d3014204933b3a8c1159fdecb221afe4168883d2d00ac1f15fca4614fbd5e05de2e37ad0fbad8b7748dddbcf30b39e80466c61c733415e72b9f42d5fad0bf35f041eb5631eded00314c66c4878b351416e5c6b9096f2a7088a24387e5d0149c523739f84f502c70203010001a350304e301d0603551d0e0416041473715bbfd9bc2b824112f858586f166aafb99482301f0603551d2304183016801473715bbfd9bc2b824112f858586f166aafb99482300c0603551d13040530030101ff300d06092a864886f70d0101050500038181005550f1d64139ab0e86c5b303fc69015d1676ca95931071ae41884656c71c116a38138ecf63054b350dc78983cb4a83288dbc81c5a659a56cc6843d5452c3e98449b94a0cf0c0cd7190c96caa5f0ee9a3bef7e75002be4a233673852bdf1a5fd306a7080eb4fead9b3ad162074b5f007e9156e220302dea8c700868a12577e7c4", "\x02\x00\x00\x30\x82\x01\xf4\x30\x82\x01\x5d\xa0\x03\x02\x01\x02\x02\x09\x00\xac\x54\x7c\x55\x57\x87\x0e\xc7\x30\x0d\x06\x09\x2a\x86\x48\x86\xf7\x0d\x01\x01\x05\x05\x00\x30\x13\x31\x11\x30\x0f\x06\x03\x55\x04\x03\x0c\x08\x72\x65\x63\x2e\x74\x65\x73\x74\x30\x1e\x17\x0d\x31\x33\x30\x35\x31\x32\x31\x39\x34\x38\x30\x39\x5a\x17\x0d\x31\x33\x30\x36\x31\x31\x31\x39\x34\x38\x30\x39\x5a\x30\x13\x31\x11\x30\x0f\x06\x03\x55\x04\x03\x0c\x08\x72\x65\x63\x2e\x74\x65\x73\x74\x30\x81\x9f\x30\x0d\x06\x09\x2a\x86\x48\x86\xf7\x0d\x01\x01\x01\x05\x00\x03\x81\x8d\x00\x30\x81\x89\x02\x81\x81\x00\xd2\x82\xbb\x96\x8d\xfd\xec\x0e\x5d\x13\xdf\xcc\x0a\x36\xed\x73\x17\x85\x81\x42\x4e\x10\xa3\x7c\x89\xd3\x01\x42\x04\x93\x3b\x3a\x8c\x11\x59\xfd\xec\xb2\x21\xaf\xe4\x16\x88\x83\xd2\xd0\x0a\xc1\xf1\x5f\xca\x46\x14\xfb\xd5\xe0\x5d\xe2\xe3\x7a\xd0\xfb\xad\x8b\x77\x48\xdd\xdb\xcf\x30\xb3\x9e\x80\x46\x6c\x61\xc7\x33\x41\x5e\x72\xb9\xf4\x2d\x5f\xad\x0b\xf3\x5f\x04\x1e\xb5\x63\x1e\xde\xd0\x03\x14\xc6\x6c\x48\x78\xb3\x51\x41\x6e\x5c\x6b\x90\x96\xf2\xa7\x08\x8a\x24\x38\x7e\x5d\x01\x49\xc5\x23\x73\x9f\x84\xf5\x02\xc7\x02\x03\x01\x00\x01\xa3\x50\x30\x4e\x30\x1d\x06\x03\x55\x1d\x0e\x04\x16\x04\x14\x73\x71\x5b\xbf\xd9\xbc\x2b\x82\x41\x12\xf8\x58\x58\x6f\x16\x6a\xaf\xb9\x94\x82\x30\x1f\x06\x03\x55\x1d\x23\x04\x18\x30\x16\x80\x14\x73\x71\x5b\xbf\xd9\xbc\x2b\x82\x41\x12\xf8\x58\x58\x6f\x16\x6a\xaf\xb9\x94\x82\x30\x0c\x06\x03\x55\x1d\x13\x04\x05\x30\x03\x01\x01\xff\x30\x0d\x06\x09\x2a\x86\x48\x86\xf7\x0d\x01\x01\x05\x05\x00\x03\x81\x81\x00\x55\x50\xf1\xd6\x41\x39\xab\x0e\x86\xc5\xb3\x03\xfc\x69\x01\x5d\x16\x76\xca\x95\x93\x10\x71\xae\x41\x88\x46\x56\xc7\x1c\x11\x6a\x38\x13\x8e\xcf\x63\x05\x4b\x35\x0d\xc7\x89\x83\xcb\x4a\x83\x28\x8d\xbc\x81\xc5\xa6\x59\xa5\x6c\xc6\x84\x3d\x54\x52\xc3\xe9\x84\x49\xb9\x4a\x0c\xf0\xc0\xcd\x71\x90\xc9\x6c\xaa\x5f\x0e\xe9\xa3\xbe\xf7\xe7\x50\x02\xbe\x4a\x23\x36\x73\x85\x2b\xdf\x1a\x5f\xd3\x06\xa7\x08\x0e\xb4\xfe\xad\x9b\x3a\xd1\x62\x07\x4b\x5f\x00\x7e\x91\x56\xe2\x20\x30\x2d\xea\x8c\x70\x08\x68\xa1\x25\x77\xe7\xc4",false))
-     (CASE_S(QType::TLSA, "2 1 0 30819f300d06092a864886f70d010101050003818d0030818902818100d282bb968dfdec0e5d13dfcc0a36ed73178581424e10a37c89d3014204933b3a8c1159fdecb221afe4168883d2d00ac1f15fca4614fbd5e05de2e37ad0fbad8b7748dddbcf30b39e80466c61c733415e72b9f42d5fad0bf35f041eb5631eded00314c66c4878b351416e5c6b9096f2a7088a24387e5d0149c523739f84f502c70203010001", "\x02\x01\x00\x30\x81\x9f\x30\x0d\x06\x09\x2a\x86\x48\x86\xf7\x0d\x01\x01\x01\x05\x00\x03\x81\x8d\x00\x30\x81\x89\x02\x81\x81\x00\xd2\x82\xbb\x96\x8d\xfd\xec\x0e\x5d\x13\xdf\xcc\x0a\x36\xed\x73\x17\x85\x81\x42\x4e\x10\xa3\x7c\x89\xd3\x01\x42\x04\x93\x3b\x3a\x8c\x11\x59\xfd\xec\xb2\x21\xaf\xe4\x16\x88\x83\xd2\xd0\x0a\xc1\xf1\x5f\xca\x46\x14\xfb\xd5\xe0\x5d\xe2\xe3\x7a\xd0\xfb\xad\x8b\x77\x48\xdd\xdb\xcf\x30\xb3\x9e\x80\x46\x6c\x61\xc7\x33\x41\x5e\x72\xb9\xf4\x2d\x5f\xad\x0b\xf3\x5f\x04\x1e\xb5\x63\x1e\xde\xd0\x03\x14\xc6\x6c\x48\x78\xb3\x51\x41\x6e\x5c\x6b\x90\x96\xf2\xa7\x08\x8a\x24\x38\x7e\x5d\x01\x49\xc5\x23\x73\x9f\x84\xf5\x02\xc7\x02\x03\x01\x00\x01",false))
-
-     (CASE_S(QType::TLSA, "3 0 0 308201f43082015da003020102020900ac547c5557870ec7300d06092a864886f70d010105050030133111300f06035504030c087265632e74657374301e170d3133303531323139343830395a170d3133303631313139343830395a30133111300f06035504030c087265632e7465737430819f300d06092a864886f70d010101050003818d0030818902818100d282bb968dfdec0e5d13dfcc0a36ed73178581424e10a37c89d3014204933b3a8c1159fdecb221afe4168883d2d00ac1f15fca4614fbd5e05de2e37ad0fbad8b7748dddbcf30b39e80466c61c733415e72b9f42d5fad0bf35f041eb5631eded00314c66c4878b351416e5c6b9096f2a7088a24387e5d0149c523739f84f502c70203010001a350304e301d0603551d0e0416041473715bbfd9bc2b824112f858586f166aafb99482301f0603551d2304183016801473715bbfd9bc2b824112f858586f166aafb99482300c0603551d13040530030101ff300d06092a864886f70d0101050500038181005550f1d64139ab0e86c5b303fc69015d1676ca95931071ae41884656c71c116a38138ecf63054b350dc78983cb4a83288dbc81c5a659a56cc6843d5452c3e98449b94a0cf0c0cd7190c96caa5f0ee9a3bef7e75002be4a233673852bdf1a5fd306a7080eb4fead9b3ad162074b5f007e9156e220302dea8c700868a12577e7c4", "\x03\x00\x00\x30\x82\x01\xf4\x30\x82\x01\x5d\xa0\x03\x02\x01\x02\x02\x09\x00\xac\x54\x7c\x55\x57\x87\x0e\xc7\x30\x0d\x06\x09\x2a\x86\x48\x86\xf7\x0d\x01\x01\x05\x05\x00\x30\x13\x31\x11\x30\x0f\x06\x03\x55\x04\x03\x0c\x08\x72\x65\x63\x2e\x74\x65\x73\x74\x30\x1e\x17\x0d\x31\x33\x30\x35\x31\x32\x31\x39\x34\x38\x30\x39\x5a\x17\x0d\x31\x33\x30\x36\x31\x31\x31\x39\x34\x38\x30\x39\x5a\x30\x13\x31\x11\x30\x0f\x06\x03\x55\x04\x03\x0c\x08\x72\x65\x63\x2e\x74\x65\x73\x74\x30\x81\x9f\x30\x0d\x06\x09\x2a\x86\x48\x86\xf7\x0d\x01\x01\x01\x05\x00\x03\x81\x8d\x00\x30\x81\x89\x02\x81\x81\x00\xd2\x82\xbb\x96\x8d\xfd\xec\x0e\x5d\x13\xdf\xcc\x0a\x36\xed\x73\x17\x85\x81\x42\x4e\x10\xa3\x7c\x89\xd3\x01\x42\x04\x93\x3b\x3a\x8c\x11\x59\xfd\xec\xb2\x21\xaf\xe4\x16\x88\x83\xd2\xd0\x0a\xc1\xf1\x5f\xca\x46\x14\xfb\xd5\xe0\x5d\xe2\xe3\x7a\xd0\xfb\xad\x8b\x77\x48\xdd\xdb\xcf\x30\xb3\x9e\x80\x46\x6c\x61\xc7\x33\x41\x5e\x72\xb9\xf4\x2d\x5f\xad\x0b\xf3\x5f\x04\x1e\xb5\x63\x1e\xde\xd0\x03\x14\xc6\x6c\x48\x78\xb3\x51\x41\x6e\x5c\x6b\x90\x96\xf2\xa7\x08\x8a\x24\x38\x7e\x5d\x01\x49\xc5\x23\x73\x9f\x84\xf5\x02\xc7\x02\x03\x01\x00\x01\xa3\x50\x30\x4e\x30\x1d\x06\x03\x55\x1d\x0e\x04\x16\x04\x14\x73\x71\x5b\xbf\xd9\xbc\x2b\x82\x41\x12\xf8\x58\x58\x6f\x16\x6a\xaf\xb9\x94\x82\x30\x1f\x06\x03\x55\x1d\x23\x04\x18\x30\x16\x80\x14\x73\x71\x5b\xbf\xd9\xbc\x2b\x82\x41\x12\xf8\x58\x58\x6f\x16\x6a\xaf\xb9\x94\x82\x30\x0c\x06\x03\x55\x1d\x13\x04\x05\x30\x03\x01\x01\xff\x30\x0d\x06\x09\x2a\x86\x48\x86\xf7\x0d\x01\x01\x05\x05\x00\x03\x81\x81\x00\x55\x50\xf1\xd6\x41\x39\xab\x0e\x86\xc5\xb3\x03\xfc\x69\x01\x5d\x16\x76\xca\x95\x93\x10\x71\xae\x41\x88\x46\x56\xc7\x1c\x11\x6a\x38\x13\x8e\xcf\x63\x05\x4b\x35\x0d\xc7\x89\x83\xcb\x4a\x83\x28\x8d\xbc\x81\xc5\xa6\x59\xa5\x6c\xc6\x84\x3d\x54\x52\xc3\xe9\x84\x49\xb9\x4a\x0c\xf0\xc0\xcd\x71\x90\xc9\x6c\xaa\x5f\x0e\xe9\xa3\xbe\xf7\xe7\x50\x02\xbe\x4a\x23\x36\x73\x85\x2b\xdf\x1a\x5f\xd3\x06\xa7\x08\x0e\xb4\xfe\xad\x9b\x3a\xd1\x62\x07\x4b\x5f\x00\x7e\x91\x56\xe2\x20\x30\x2d\xea\x8c\x70\x08\x68\xa1\x25\x77\xe7\xc4",false))
-     (CASE_S(QType::TLSA, "3 1 0 30819f300d06092a864886f70d010101050003818d0030818902818100d282bb968dfdec0e5d13dfcc0a36ed73178581424e10a37c89d3014204933b3a8c1159fdecb221afe4168883d2d00ac1f15fca4614fbd5e05de2e37ad0fbad8b7748dddbcf30b39e80466c61c733415e72b9f42d5fad0bf35f041eb5631eded00314c66c4878b351416e5c6b9096f2a7088a24387e5d0149c523739f84f502c70203010001", "\x03\x01\x00\x30\x81\x9f\x30\x0d\x06\x09\x2a\x86\x48\x86\xf7\x0d\x01\x01\x01\x05\x00\x03\x81\x8d\x00\x30\x81\x89\x02\x81\x81\x00\xd2\x82\xbb\x96\x8d\xfd\xec\x0e\x5d\x13\xdf\xcc\x0a\x36\xed\x73\x17\x85\x81\x42\x4e\x10\xa3\x7c\x89\xd3\x01\x42\x04\x93\x3b\x3a\x8c\x11\x59\xfd\xec\xb2\x21\xaf\xe4\x16\x88\x83\xd2\xd0\x0a\xc1\xf1\x5f\xca\x46\x14\xfb\xd5\xe0\x5d\xe2\xe3\x7a\xd0\xfb\xad\x8b\x77\x48\xdd\xdb\xcf\x30\xb3\x9e\x80\x46\x6c\x61\xc7\x33\x41\x5e\x72\xb9\xf4\x2d\x5f\xad\x0b\xf3\x5f\x04\x1e\xb5\x63\x1e\xde\xd0\x03\x14\xc6\x6c\x48\x78\xb3\x51\x41\x6e\x5c\x6b\x90\x96\xf2\xa7\x08\x8a\x24\x38\x7e\x5d\x01\x49\xc5\x23\x73\x9f\x84\xf5\x02\xc7\x02\x03\x01\x00\x01",false))
-
-     (CASE_S(QType::SMIMEA, "0 0 0 308201f43082015da003020102020900ac547c5557870ec7300d06092a864886f70d010105050030133111300f06035504030c087265632e74657374301e170d3133303531323139343830395a170d3133303631313139343830395a30133111300f06035504030c087265632e7465737430819f300d06092a864886f70d010101050003818d0030818902818100d282bb968dfdec0e5d13dfcc0a36ed73178581424e10a37c89d3014204933b3a8c1159fdecb221afe4168883d2d00ac1f15fca4614fbd5e05de2e37ad0fbad8b7748dddbcf30b39e80466c61c733415e72b9f42d5fad0bf35f041eb5631eded00314c66c4878b351416e5c6b9096f2a7088a24387e5d0149c523739f84f502c70203010001a350304e301d0603551d0e0416041473715bbfd9bc2b824112f858586f166aafb99482301f0603551d2304183016801473715bbfd9bc2b824112f858586f166aafb99482300c0603551d13040530030101ff300d06092a864886f70d0101050500038181005550f1d64139ab0e86c5b303fc69015d1676ca95931071ae41884656c71c116a38138ecf63054b350dc78983cb4a83288dbc81c5a659a56cc6843d5452c3e98449b94a0cf0c0cd7190c96caa5f0ee9a3bef7e75002be4a233673852bdf1a5fd306a7080eb4fead9b3ad162074b5f007e9156e220302dea8c700868a12577e7c4", "\x00\x00\x00\x30\x82\x01\xf4\x30\x82\x01\x5d\xa0\x03\x02\x01\x02\x02\x09\x00\xac\x54\x7c\x55\x57\x87\x0e\xc7\x30\x0d\x06\x09\x2a\x86\x48\x86\xf7\x0d\x01\x01\x05\x05\x00\x30\x13\x31\x11\x30\x0f\x06\x03\x55\x04\x03\x0c\x08\x72\x65\x63\x2e\x74\x65\x73\x74\x30\x1e\x17\x0d\x31\x33\x30\x35\x31\x32\x31\x39\x34\x38\x30\x39\x5a\x17\x0d\x31\x33\x30\x36\x31\x31\x31\x39\x34\x38\x30\x39\x5a\x30\x13\x31\x11\x30\x0f\x06\x03\x55\x04\x03\x0c\x08\x72\x65\x63\x2e\x74\x65\x73\x74\x30\x81\x9f\x30\x0d\x06\x09\x2a\x86\x48\x86\xf7\x0d\x01\x01\x01\x05\x00\x03\x81\x8d\x00\x30\x81\x89\x02\x81\x81\x00\xd2\x82\xbb\x96\x8d\xfd\xec\x0e\x5d\x13\xdf\xcc\x0a\x36\xed\x73\x17\x85\x81\x42\x4e\x10\xa3\x7c\x89\xd3\x01\x42\x04\x93\x3b\x3a\x8c\x11\x59\xfd\xec\xb2\x21\xaf\xe4\x16\x88\x83\xd2\xd0\x0a\xc1\xf1\x5f\xca\x46\x14\xfb\xd5\xe0\x5d\xe2\xe3\x7a\xd0\xfb\xad\x8b\x77\x48\xdd\xdb\xcf\x30\xb3\x9e\x80\x46\x6c\x61\xc7\x33\x41\x5e\x72\xb9\xf4\x2d\x5f\xad\x0b\xf3\x5f\x04\x1e\xb5\x63\x1e\xde\xd0\x03\x14\xc6\x6c\x48\x78\xb3\x51\x41\x6e\x5c\x6b\x90\x96\xf2\xa7\x08\x8a\x24\x38\x7e\x5d\x01\x49\xc5\x23\x73\x9f\x84\xf5\x02\xc7\x02\x03\x01\x00\x01\xa3\x50\x30\x4e\x30\x1d\x06\x03\x55\x1d\x0e\x04\x16\x04\x14\x73\x71\x5b\xbf\xd9\xbc\x2b\x82\x41\x12\xf8\x58\x58\x6f\x16\x6a\xaf\xb9\x94\x82\x30\x1f\x06\x03\x55\x1d\x23\x04\x18\x30\x16\x80\x14\x73\x71\x5b\xbf\xd9\xbc\x2b\x82\x41\x12\xf8\x58\x58\x6f\x16\x6a\xaf\xb9\x94\x82\x30\x0c\x06\x03\x55\x1d\x13\x04\x05\x30\x03\x01\x01\xff\x30\x0d\x06\x09\x2a\x86\x48\x86\xf7\x0d\x01\x01\x05\x05\x00\x03\x81\x81\x00\x55\x50\xf1\xd6\x41\x39\xab\x0e\x86\xc5\xb3\x03\xfc\x69\x01\x5d\x16\x76\xca\x95\x93\x10\x71\xae\x41\x88\x46\x56\xc7\x1c\x11\x6a\x38\x13\x8e\xcf\x63\x05\x4b\x35\x0d\xc7\x89\x83\xcb\x4a\x83\x28\x8d\xbc\x81\xc5\xa6\x59\xa5\x6c\xc6\x84\x3d\x54\x52\xc3\xe9\x84\x49\xb9\x4a\x0c\xf0\xc0\xcd\x71\x90\xc9\x6c\xaa\x5f\x0e\xe9\xa3\xbe\xf7\xe7\x50\x02\xbe\x4a\x23\x36\x73\x85\x2b\xdf\x1a\x5f\xd3\x06\xa7\x08\x0e\xb4\xfe\xad\x9b\x3a\xd1\x62\x07\x4b\x5f\x00\x7e\x91\x56\xe2\x20\x30\x2d\xea\x8c\x70\x08\x68\xa1\x25\x77\xe7\xc4",false))
-     (CASE_S(QType::SMIMEA, "0 1 0 30819f300d06092a864886f70d010101050003818d0030818902818100d282bb968dfdec0e5d13dfcc0a36ed73178581424e10a37c89d3014204933b3a8c1159fdecb221afe4168883d2d00ac1f15fca4614fbd5e05de2e37ad0fbad8b7748dddbcf30b39e80466c61c733415e72b9f42d5fad0bf35f041eb5631eded00314c66c4878b351416e5c6b9096f2a7088a24387e5d0149c523739f84f502c70203010001", "\x00\x01\x00\x30\x81\x9f\x30\x0d\x06\x09\x2a\x86\x48\x86\xf7\x0d\x01\x01\x01\x05\x00\x03\x81\x8d\x00\x30\x81\x89\x02\x81\x81\x00\xd2\x82\xbb\x96\x8d\xfd\xec\x0e\x5d\x13\xdf\xcc\x0a\x36\xed\x73\x17\x85\x81\x42\x4e\x10\xa3\x7c\x89\xd3\x01\x42\x04\x93\x3b\x3a\x8c\x11\x59\xfd\xec\xb2\x21\xaf\xe4\x16\x88\x83\xd2\xd0\x0a\xc1\xf1\x5f\xca\x46\x14\xfb\xd5\xe0\x5d\xe2\xe3\x7a\xd0\xfb\xad\x8b\x77\x48\xdd\xdb\xcf\x30\xb3\x9e\x80\x46\x6c\x61\xc7\x33\x41\x5e\x72\xb9\xf4\x2d\x5f\xad\x0b\xf3\x5f\x04\x1e\xb5\x63\x1e\xde\xd0\x03\x14\xc6\x6c\x48\x78\xb3\x51\x41\x6e\x5c\x6b\x90\x96\xf2\xa7\x08\x8a\x24\x38\x7e\x5d\x01\x49\xc5\x23\x73\x9f\x84\xf5\x02\xc7\x02\x03\x01\x00\x01",false))
-     (CASE_S(QType::SMIMEA, "1 0 0 308201f43082015da003020102020900ac547c5557870ec7300d06092a864886f70d010105050030133111300f06035504030c087265632e74657374301e170d3133303531323139343830395a170d3133303631313139343830395a30133111300f06035504030c087265632e7465737430819f300d06092a864886f70d010101050003818d0030818902818100d282bb968dfdec0e5d13dfcc0a36ed73178581424e10a37c89d3014204933b3a8c1159fdecb221afe4168883d2d00ac1f15fca4614fbd5e05de2e37ad0fbad8b7748dddbcf30b39e80466c61c733415e72b9f42d5fad0bf35f041eb5631eded00314c66c4878b351416e5c6b9096f2a7088a24387e5d0149c523739f84f502c70203010001a350304e301d0603551d0e0416041473715bbfd9bc2b824112f858586f166aafb99482301f0603551d2304183016801473715bbfd9bc2b824112f858586f166aafb99482300c0603551d13040530030101ff300d06092a864886f70d0101050500038181005550f1d64139ab0e86c5b303fc69015d1676ca95931071ae41884656c71c116a38138ecf63054b350dc78983cb4a83288dbc81c5a659a56cc6843d5452c3e98449b94a0cf0c0cd7190c96caa5f0ee9a3bef7e75002be4a233673852bdf1a5fd306a7080eb4fead9b3ad162074b5f007e9156e220302dea8c700868a12577e7c4", "\x01\x00\x00\x30\x82\x01\xf4\x30\x82\x01\x5d\xa0\x03\x02\x01\x02\x02\x09\x00\xac\x54\x7c\x55\x57\x87\x0e\xc7\x30\x0d\x06\x09\x2a\x86\x48\x86\xf7\x0d\x01\x01\x05\x05\x00\x30\x13\x31\x11\x30\x0f\x06\x03\x55\x04\x03\x0c\x08\x72\x65\x63\x2e\x74\x65\x73\x74\x30\x1e\x17\x0d\x31\x33\x30\x35\x31\x32\x31\x39\x34\x38\x30\x39\x5a\x17\x0d\x31\x33\x30\x36\x31\x31\x31\x39\x34\x38\x30\x39\x5a\x30\x13\x31\x11\x30\x0f\x06\x03\x55\x04\x03\x0c\x08\x72\x65\x63\x2e\x74\x65\x73\x74\x30\x81\x9f\x30\x0d\x06\x09\x2a\x86\x48\x86\xf7\x0d\x01\x01\x01\x05\x00\x03\x81\x8d\x00\x30\x81\x89\x02\x81\x81\x00\xd2\x82\xbb\x96\x8d\xfd\xec\x0e\x5d\x13\xdf\xcc\x0a\x36\xed\x73\x17\x85\x81\x42\x4e\x10\xa3\x7c\x89\xd3\x01\x42\x04\x93\x3b\x3a\x8c\x11\x59\xfd\xec\xb2\x21\xaf\xe4\x16\x88\x83\xd2\xd0\x0a\xc1\xf1\x5f\xca\x46\x14\xfb\xd5\xe0\x5d\xe2\xe3\x7a\xd0\xfb\xad\x8b\x77\x48\xdd\xdb\xcf\x30\xb3\x9e\x80\x46\x6c\x61\xc7\x33\x41\x5e\x72\xb9\xf4\x2d\x5f\xad\x0b\xf3\x5f\x04\x1e\xb5\x63\x1e\xde\xd0\x03\x14\xc6\x6c\x48\x78\xb3\x51\x41\x6e\x5c\x6b\x90\x96\xf2\xa7\x08\x8a\x24\x38\x7e\x5d\x01\x49\xc5\x23\x73\x9f\x84\xf5\x02\xc7\x02\x03\x01\x00\x01\xa3\x50\x30\x4e\x30\x1d\x06\x03\x55\x1d\x0e\x04\x16\x04\x14\x73\x71\x5b\xbf\xd9\xbc\x2b\x82\x41\x12\xf8\x58\x58\x6f\x16\x6a\xaf\xb9\x94\x82\x30\x1f\x06\x03\x55\x1d\x23\x04\x18\x30\x16\x80\x14\x73\x71\x5b\xbf\xd9\xbc\x2b\x82\x41\x12\xf8\x58\x58\x6f\x16\x6a\xaf\xb9\x94\x82\x30\x0c\x06\x03\x55\x1d\x13\x04\x05\x30\x03\x01\x01\xff\x30\x0d\x06\x09\x2a\x86\x48\x86\xf7\x0d\x01\x01\x05\x05\x00\x03\x81\x81\x00\x55\x50\xf1\xd6\x41\x39\xab\x0e\x86\xc5\xb3\x03\xfc\x69\x01\x5d\x16\x76\xca\x95\x93\x10\x71\xae\x41\x88\x46\x56\xc7\x1c\x11\x6a\x38\x13\x8e\xcf\x63\x05\x4b\x35\x0d\xc7\x89\x83\xcb\x4a\x83\x28\x8d\xbc\x81\xc5\xa6\x59\xa5\x6c\xc6\x84\x3d\x54\x52\xc3\xe9\x84\x49\xb9\x4a\x0c\xf0\xc0\xcd\x71\x90\xc9\x6c\xaa\x5f\x0e\xe9\xa3\xbe\xf7\xe7\x50\x02\xbe\x4a\x23\x36\x73\x85\x2b\xdf\x1a\x5f\xd3\x06\xa7\x08\x0e\xb4\xfe\xad\x9b\x3a\xd1\x62\x07\x4b\x5f\x00\x7e\x91\x56\xe2\x20\x30\x2d\xea\x8c\x70\x08\x68\xa1\x25\x77\xe7\xc4",false))
-     (CASE_S(QType::SMIMEA, "1 1 0 30819f300d06092a864886f70d010101050003818d0030818902818100d282bb968dfdec0e5d13dfcc0a36ed73178581424e10a37c89d3014204933b3a8c1159fdecb221afe4168883d2d00ac1f15fca4614fbd5e05de2e37ad0fbad8b7748dddbcf30b39e80466c61c733415e72b9f42d5fad0bf35f041eb5631eded00314c66c4878b351416e5c6b9096f2a7088a24387e5d0149c523739f84f502c70203010001", "\x01\x01\x00\x30\x81\x9f\x30\x0d\x06\x09\x2a\x86\x48\x86\xf7\x0d\x01\x01\x01\x05\x00\x03\x81\x8d\x00\x30\x81\x89\x02\x81\x81\x00\xd2\x82\xbb\x96\x8d\xfd\xec\x0e\x5d\x13\xdf\xcc\x0a\x36\xed\x73\x17\x85\x81\x42\x4e\x10\xa3\x7c\x89\xd3\x01\x42\x04\x93\x3b\x3a\x8c\x11\x59\xfd\xec\xb2\x21\xaf\xe4\x16\x88\x83\xd2\xd0\x0a\xc1\xf1\x5f\xca\x46\x14\xfb\xd5\xe0\x5d\xe2\xe3\x7a\xd0\xfb\xad\x8b\x77\x48\xdd\xdb\xcf\x30\xb3\x9e\x80\x46\x6c\x61\xc7\x33\x41\x5e\x72\xb9\xf4\x2d\x5f\xad\x0b\xf3\x5f\x04\x1e\xb5\x63\x1e\xde\xd0\x03\x14\xc6\x6c\x48\x78\xb3\x51\x41\x6e\x5c\x6b\x90\x96\xf2\xa7\x08\x8a\x24\x38\x7e\x5d\x01\x49\xc5\x23\x73\x9f\x84\xf5\x02\xc7\x02\x03\x01\x00\x01",false))
-     (CASE_S(QType::SMIMEA, "1 0 1 6acea2f68b03d9efe97a967e137aca6ac3a89490d532d87806d9e9c257668453", "\x01\x00\x01\x6a\xce\xa2\xf6\x8b\x03\xd9\xef\xe9\x7a\x96\x7e\x13\x7a\xca\x6a\xc3\xa8\x94\x90\xd5\x32\xd8\x78\x06\xd9\xe9\xc2\x57\x66\x84\x53",false))
-     (CASE_S(QType::SMIMEA, "1 0 2 e6dce237992803488d11d828b7728deddd4577de73d7d078338c8a45880beddff98e076a28bf8e3068da8e73667b802a721c95d7323b038c60200a430cb6fbd4", "\x01\x00\x02\xe6\xdc\xe2\x37\x99\x28\x03\x48\x8d\x11\xd8\x28\xb7\x72\x8d\xed\xdd\x45\x77\xde\x73\xd7\xd0\x78\x33\x8c\x8a\x45\x88\x0b\xed\xdf\xf9\x8e\x07\x6a\x28\xbf\x8e\x30\x68\xda\x8e\x73\x66\x7b\x80\x2a\x72\x1c\x95\xd7\x32\x3b\x03\x8c\x60\x20\x0a\x43\x0c\xb6\xfb\xd4",false))
-     (CASE_S(QType::SMIMEA, "2 0 0 308201f43082015da003020102020900ac547c5557870ec7300d06092a864886f70d010105050030133111300f06035504030c087265632e74657374301e170d3133303531323139343830395a170d3133303631313139343830395a30133111300f06035504030c087265632e7465737430819f300d06092a864886f70d010101050003818d0030818902818100d282bb968dfdec0e5d13dfcc0a36ed73178581424e10a37c89d3014204933b3a8c1159fdecb221afe4168883d2d00ac1f15fca4614fbd5e05de2e37ad0fbad8b7748dddbcf30b39e80466c61c733415e72b9f42d5fad0bf35f041eb5631eded00314c66c4878b351416e5c6b9096f2a7088a24387e5d0149c523739f84f502c70203010001a350304e301d0603551d0e0416041473715bbfd9bc2b824112f858586f166aafb99482301f0603551d2304183016801473715bbfd9bc2b824112f858586f166aafb99482300c0603551d13040530030101ff300d06092a864886f70d0101050500038181005550f1d64139ab0e86c5b303fc69015d1676ca95931071ae41884656c71c116a38138ecf63054b350dc78983cb4a83288dbc81c5a659a56cc6843d5452c3e98449b94a0cf0c0cd7190c96caa5f0ee9a3bef7e75002be4a233673852bdf1a5fd306a7080eb4fead9b3ad162074b5f007e9156e220302dea8c700868a12577e7c4", "\x02\x00\x00\x30\x82\x01\xf4\x30\x82\x01\x5d\xa0\x03\x02\x01\x02\x02\x09\x00\xac\x54\x7c\x55\x57\x87\x0e\xc7\x30\x0d\x06\x09\x2a\x86\x48\x86\xf7\x0d\x01\x01\x05\x05\x00\x30\x13\x31\x11\x30\x0f\x06\x03\x55\x04\x03\x0c\x08\x72\x65\x63\x2e\x74\x65\x73\x74\x30\x1e\x17\x0d\x31\x33\x30\x35\x31\x32\x31\x39\x34\x38\x30\x39\x5a\x17\x0d\x31\x33\x30\x36\x31\x31\x31\x39\x34\x38\x30\x39\x5a\x30\x13\x31\x11\x30\x0f\x06\x03\x55\x04\x03\x0c\x08\x72\x65\x63\x2e\x74\x65\x73\x74\x30\x81\x9f\x30\x0d\x06\x09\x2a\x86\x48\x86\xf7\x0d\x01\x01\x01\x05\x00\x03\x81\x8d\x00\x30\x81\x89\x02\x81\x81\x00\xd2\x82\xbb\x96\x8d\xfd\xec\x0e\x5d\x13\xdf\xcc\x0a\x36\xed\x73\x17\x85\x81\x42\x4e\x10\xa3\x7c\x89\xd3\x01\x42\x04\x93\x3b\x3a\x8c\x11\x59\xfd\xec\xb2\x21\xaf\xe4\x16\x88\x83\xd2\xd0\x0a\xc1\xf1\x5f\xca\x46\x14\xfb\xd5\xe0\x5d\xe2\xe3\x7a\xd0\xfb\xad\x8b\x77\x48\xdd\xdb\xcf\x30\xb3\x9e\x80\x46\x6c\x61\xc7\x33\x41\x5e\x72\xb9\xf4\x2d\x5f\xad\x0b\xf3\x5f\x04\x1e\xb5\x63\x1e\xde\xd0\x03\x14\xc6\x6c\x48\x78\xb3\x51\x41\x6e\x5c\x6b\x90\x96\xf2\xa7\x08\x8a\x24\x38\x7e\x5d\x01\x49\xc5\x23\x73\x9f\x84\xf5\x02\xc7\x02\x03\x01\x00\x01\xa3\x50\x30\x4e\x30\x1d\x06\x03\x55\x1d\x0e\x04\x16\x04\x14\x73\x71\x5b\xbf\xd9\xbc\x2b\x82\x41\x12\xf8\x58\x58\x6f\x16\x6a\xaf\xb9\x94\x82\x30\x1f\x06\x03\x55\x1d\x23\x04\x18\x30\x16\x80\x14\x73\x71\x5b\xbf\xd9\xbc\x2b\x82\x41\x12\xf8\x58\x58\x6f\x16\x6a\xaf\xb9\x94\x82\x30\x0c\x06\x03\x55\x1d\x13\x04\x05\x30\x03\x01\x01\xff\x30\x0d\x06\x09\x2a\x86\x48\x86\xf7\x0d\x01\x01\x05\x05\x00\x03\x81\x81\x00\x55\x50\xf1\xd6\x41\x39\xab\x0e\x86\xc5\xb3\x03\xfc\x69\x01\x5d\x16\x76\xca\x95\x93\x10\x71\xae\x41\x88\x46\x56\xc7\x1c\x11\x6a\x38\x13\x8e\xcf\x63\x05\x4b\x35\x0d\xc7\x89\x83\xcb\x4a\x83\x28\x8d\xbc\x81\xc5\xa6\x59\xa5\x6c\xc6\x84\x3d\x54\x52\xc3\xe9\x84\x49\xb9\x4a\x0c\xf0\xc0\xcd\x71\x90\xc9\x6c\xaa\x5f\x0e\xe9\xa3\xbe\xf7\xe7\x50\x02\xbe\x4a\x23\x36\x73\x85\x2b\xdf\x1a\x5f\xd3\x06\xa7\x08\x0e\xb4\xfe\xad\x9b\x3a\xd1\x62\x07\x4b\x5f\x00\x7e\x91\x56\xe2\x20\x30\x2d\xea\x8c\x70\x08\x68\xa1\x25\x77\xe7\xc4",false))
-     (CASE_S(QType::SMIMEA, "2 1 0 30819f300d06092a864886f70d010101050003818d0030818902818100d282bb968dfdec0e5d13dfcc0a36ed73178581424e10a37c89d3014204933b3a8c1159fdecb221afe4168883d2d00ac1f15fca4614fbd5e05de2e37ad0fbad8b7748dddbcf30b39e80466c61c733415e72b9f42d5fad0bf35f041eb5631eded00314c66c4878b351416e5c6b9096f2a7088a24387e5d0149c523739f84f502c70203010001", "\x02\x01\x00\x30\x81\x9f\x30\x0d\x06\x09\x2a\x86\x48\x86\xf7\x0d\x01\x01\x01\x05\x00\x03\x81\x8d\x00\x30\x81\x89\x02\x81\x81\x00\xd2\x82\xbb\x96\x8d\xfd\xec\x0e\x5d\x13\xdf\xcc\x0a\x36\xed\x73\x17\x85\x81\x42\x4e\x10\xa3\x7c\x89\xd3\x01\x42\x04\x93\x3b\x3a\x8c\x11\x59\xfd\xec\xb2\x21\xaf\xe4\x16\x88\x83\xd2\xd0\x0a\xc1\xf1\x5f\xca\x46\x14\xfb\xd5\xe0\x5d\xe2\xe3\x7a\xd0\xfb\xad\x8b\x77\x48\xdd\xdb\xcf\x30\xb3\x9e\x80\x46\x6c\x61\xc7\x33\x41\x5e\x72\xb9\xf4\x2d\x5f\xad\x0b\xf3\x5f\x04\x1e\xb5\x63\x1e\xde\xd0\x03\x14\xc6\x6c\x48\x78\xb3\x51\x41\x6e\x5c\x6b\x90\x96\xf2\xa7\x08\x8a\x24\x38\x7e\x5d\x01\x49\xc5\x23\x73\x9f\x84\xf5\x02\xc7\x02\x03\x01\x00\x01",false))
-     (CASE_S(QType::SMIMEA, "3 0 0 308201f43082015da003020102020900ac547c5557870ec7300d06092a864886f70d010105050030133111300f06035504030c087265632e74657374301e170d3133303531323139343830395a170d3133303631313139343830395a30133111300f06035504030c087265632e7465737430819f300d06092a864886f70d010101050003818d0030818902818100d282bb968dfdec0e5d13dfcc0a36ed73178581424e10a37c89d3014204933b3a8c1159fdecb221afe4168883d2d00ac1f15fca4614fbd5e05de2e37ad0fbad8b7748dddbcf30b39e80466c61c733415e72b9f42d5fad0bf35f041eb5631eded00314c66c4878b351416e5c6b9096f2a7088a24387e5d0149c523739f84f502c70203010001a350304e301d0603551d0e0416041473715bbfd9bc2b824112f858586f166aafb99482301f0603551d2304183016801473715bbfd9bc2b824112f858586f166aafb99482300c0603551d13040530030101ff300d06092a864886f70d0101050500038181005550f1d64139ab0e86c5b303fc69015d1676ca95931071ae41884656c71c116a38138ecf63054b350dc78983cb4a83288dbc81c5a659a56cc6843d5452c3e98449b94a0cf0c0cd7190c96caa5f0ee9a3bef7e75002be4a233673852bdf1a5fd306a7080eb4fead9b3ad162074b5f007e9156e220302dea8c700868a12577e7c4", "\x03\x00\x00\x30\x82\x01\xf4\x30\x82\x01\x5d\xa0\x03\x02\x01\x02\x02\x09\x00\xac\x54\x7c\x55\x57\x87\x0e\xc7\x30\x0d\x06\x09\x2a\x86\x48\x86\xf7\x0d\x01\x01\x05\x05\x00\x30\x13\x31\x11\x30\x0f\x06\x03\x55\x04\x03\x0c\x08\x72\x65\x63\x2e\x74\x65\x73\x74\x30\x1e\x17\x0d\x31\x33\x30\x35\x31\x32\x31\x39\x34\x38\x30\x39\x5a\x17\x0d\x31\x33\x30\x36\x31\x31\x31\x39\x34\x38\x30\x39\x5a\x30\x13\x31\x11\x30\x0f\x06\x03\x55\x04\x03\x0c\x08\x72\x65\x63\x2e\x74\x65\x73\x74\x30\x81\x9f\x30\x0d\x06\x09\x2a\x86\x48\x86\xf7\x0d\x01\x01\x01\x05\x00\x03\x81\x8d\x00\x30\x81\x89\x02\x81\x81\x00\xd2\x82\xbb\x96\x8d\xfd\xec\x0e\x5d\x13\xdf\xcc\x0a\x36\xed\x73\x17\x85\x81\x42\x4e\x10\xa3\x7c\x89\xd3\x01\x42\x04\x93\x3b\x3a\x8c\x11\x59\xfd\xec\xb2\x21\xaf\xe4\x16\x88\x83\xd2\xd0\x0a\xc1\xf1\x5f\xca\x46\x14\xfb\xd5\xe0\x5d\xe2\xe3\x7a\xd0\xfb\xad\x8b\x77\x48\xdd\xdb\xcf\x30\xb3\x9e\x80\x46\x6c\x61\xc7\x33\x41\x5e\x72\xb9\xf4\x2d\x5f\xad\x0b\xf3\x5f\x04\x1e\xb5\x63\x1e\xde\xd0\x03\x14\xc6\x6c\x48\x78\xb3\x51\x41\x6e\x5c\x6b\x90\x96\xf2\xa7\x08\x8a\x24\x38\x7e\x5d\x01\x49\xc5\x23\x73\x9f\x84\xf5\x02\xc7\x02\x03\x01\x00\x01\xa3\x50\x30\x4e\x30\x1d\x06\x03\x55\x1d\x0e\x04\x16\x04\x14\x73\x71\x5b\xbf\xd9\xbc\x2b\x82\x41\x12\xf8\x58\x58\x6f\x16\x6a\xaf\xb9\x94\x82\x30\x1f\x06\x03\x55\x1d\x23\x04\x18\x30\x16\x80\x14\x73\x71\x5b\xbf\xd9\xbc\x2b\x82\x41\x12\xf8\x58\x58\x6f\x16\x6a\xaf\xb9\x94\x82\x30\x0c\x06\x03\x55\x1d\x13\x04\x05\x30\x03\x01\x01\xff\x30\x0d\x06\x09\x2a\x86\x48\x86\xf7\x0d\x01\x01\x05\x05\x00\x03\x81\x81\x00\x55\x50\xf1\xd6\x41\x39\xab\x0e\x86\xc5\xb3\x03\xfc\x69\x01\x5d\x16\x76\xca\x95\x93\x10\x71\xae\x41\x88\x46\x56\xc7\x1c\x11\x6a\x38\x13\x8e\xcf\x63\x05\x4b\x35\x0d\xc7\x89\x83\xcb\x4a\x83\x28\x8d\xbc\x81\xc5\xa6\x59\xa5\x6c\xc6\x84\x3d\x54\x52\xc3\xe9\x84\x49\xb9\x4a\x0c\xf0\xc0\xcd\x71\x90\xc9\x6c\xaa\x5f\x0e\xe9\xa3\xbe\xf7\xe7\x50\x02\xbe\x4a\x23\x36\x73\x85\x2b\xdf\x1a\x5f\xd3\x06\xa7\x08\x0e\xb4\xfe\xad\x9b\x3a\xd1\x62\x07\x4b\x5f\x00\x7e\x91\x56\xe2\x20\x30\x2d\xea\x8c\x70\x08\x68\xa1\x25\x77\xe7\xc4",false))
-     (CASE_S(QType::SMIMEA, "3 1 0 30819f300d06092a864886f70d010101050003818d0030818902818100d282bb968dfdec0e5d13dfcc0a36ed73178581424e10a37c89d3014204933b3a8c1159fdecb221afe4168883d2d00ac1f15fca4614fbd5e05de2e37ad0fbad8b7748dddbcf30b39e80466c61c733415e72b9f42d5fad0bf35f041eb5631eded00314c66c4878b351416e5c6b9096f2a7088a24387e5d0149c523739f84f502c70203010001", "\x03\x01\x00\x30\x81\x9f\x30\x0d\x06\x09\x2a\x86\x48\x86\xf7\x0d\x01\x01\x01\x05\x00\x03\x81\x8d\x00\x30\x81\x89\x02\x81\x81\x00\xd2\x82\xbb\x96\x8d\xfd\xec\x0e\x5d\x13\xdf\xcc\x0a\x36\xed\x73\x17\x85\x81\x42\x4e\x10\xa3\x7c\x89\xd3\x01\x42\x04\x93\x3b\x3a\x8c\x11\x59\xfd\xec\xb2\x21\xaf\xe4\x16\x88\x83\xd2\xd0\x0a\xc1\xf1\x5f\xca\x46\x14\xfb\xd5\xe0\x5d\xe2\xe3\x7a\xd0\xfb\xad\x8b\x77\x48\xdd\xdb\xcf\x30\xb3\x9e\x80\x46\x6c\x61\xc7\x33\x41\x5e\x72\xb9\xf4\x2d\x5f\xad\x0b\xf3\x5f\x04\x1e\xb5\x63\x1e\xde\xd0\x03\x14\xc6\x6c\x48\x78\xb3\x51\x41\x6e\x5c\x6b\x90\x96\xf2\xa7\x08\x8a\x24\x38\x7e\x5d\x01\x49\xc5\x23\x73\x9f\x84\xf5\x02\xc7\x02\x03\x01\x00\x01",false))
-
-     (CASE_S(QType::OPENPGPKEY, "mQINBFUIXh0BEADNPlL6NpWEaR2KJx6p19scIVpsBIo7UqzCIzeFbRJaGDhn/HlQgcwAalcVNmWUX0ZQsrdn9CEfLWuFu9ON2o1TslYiwn+oSAlH2raFm2eyJTp/iM7IUUCte5jmf3d+L9rjVI7JjmMnbVo6SVY2KDDD72dULcg7IqYcCAN4CT+tPZP5y4cYf+DxRlpxhxvqqiGyAi6lAcJ24/8fJ4hsG0lS1vU12LWeWTHa5aRMM+x9kmv3GYdXG+FxFqZw52kZEnAscpC2ymbX+1YFCr8sjGYGde/D+5cLvuu4PGNZ4fkSeS+0yXve/s6u1mX6RkkF6SOGWuJfBJOGdWzYwber9kqgqpHTjpr8HOybzVroBijtTlB/tommIUd4BTk9Jv4fv2gA4UkC13UM9KBF1NnzUnKC+Js49O3mj0HZDoCrkWMnZyDsEmhMyQPU6YRFHWmB6OTKeD/Znk+b1uz+HIBgrbNuiG/A0c00Vnj7lR4p94oOuypI00XusLsJwPsjI4EgFGKdoRtM0spJhi+3gf88Vq0NENBaFVHLBGWVFaVrffurGcDZYUAdnvm8jSPCgBPfFxpZutexNkLjyaaXjDtga5/n5gSd/3RpWCvp9u3W5jcTNDZF4TORnOXUWHcot/+XmyH8/+cn8ydt0prOLGQ+FtdI+AWyMCXHen6aaZ1jeSLZqwARAQABtFpwZG5zIHJlZ3Jlc3Npb24gdGVzdGluZyBrZXkgKG9ubHkgZm9yIHRlc3RpbmcgdGhlIG9wZW5wZ3BrZXkgcnIpIDxyZWdyZXNzaW9uQHBvd2VyZG5zLm9yZz6JAjcEEwEIACEFAlUIXh0CGwMFCwkIBwIGFQgJCgsCBBYCAwECHgECF4AACgkQPr/KqoyK2Z7A6Q//YOBu8nwt+fguSo/vyCln0PqnTiBm4RvE2gPDUnsKuoXoP5F56XHBXKl9kEgmycht/nc7c7NRHzUhacM2RQau6CgNZE8KLaqDTKlEuc/ANtrnGGYG8gMId4TlzU5taLEA8yrHIHnwnMuDDpx1a0ETkbYCrj7CynqdhXCABqFjgRL7Qb37UnLPE7YdFt7fRGwZVLnb3GVZLKHurZ0TANvLdRVDST3f0lCcYMppPbHAvi2MIU71FPGkms++tj4gTltq0VRvrMNm1e5v4+hHZ++QN4sm4+DJGlo7l850gnMXc7c7GkRGtg8gV0h5k5jX5icdgxyvENTuBQ+QprkYTRh9uYzpoTQ+NYRZlgaJxxaDIv1K/kb3oPtnAEKJBC02IZbB0EiS3R5pxYXhUNoWV7ez2A4hX1L+tfvlgCAbbQ/cBLvqXgpgsf9x4ygSi52vQBy3twZyrtLsogxacxADfPcyleHtju/+lSku+Z6+W6OojA0kY2HlaMyQATJLIXd+6NE/tYy70RsU9Oq0OyVTjxh21SPLsExeSwSfciVSLn7IuKGIOV82MEHFhpo1Uhv+G52J8T1fI730sS4Tl5DekLaCz1pg/FmI/EQeAsYqm98uDAaFQcs6gDse8VYGmp2XYsoCW72as8ElKmMIbQ/xD7qxDORLmjCtVoyKH19+s6Pp61a5Ag0EVQheHQEQAMN6pcLJUhw9bfO5kqhLv4prt0AqVBUok6U8tIaEc9vDaasBcFHXgPsoOG97DXB6BdvsHuK/5uMVH5PNe58MLp08iCoIt0C0CbN3+D9Qbeg37AyKyFanB/CXq1tPKVCJc6BMNkO/BswnUsTTmlcd4GovpaJUOOZzblGUQBbhzRohhmOGfdsScGeeYME/yNFqzt1ZArV4va1hOLOUpNFv9TOy0ZVi/yDi+sYA9fCSZU9alWI/cbBct5I+3bh1l26umlZsYQm8uqnSgiQWpRm6UJO6xQbmUN9GzCYyKCmpzZRduqqjjtiF10W1yzioTfTtq2cvU6PdINYY8w2UuOjRd9gChtvGuduOIwqlRTYSaXX1dDoFe1vWqZzRm+pIDumO9eX5jMmzFXLDG2pD2l97zoSjVFf/pYoBasgX43e3V/aEk9PUgXbYFm2QxFMcIYSO9GEDMoE+QxoMXf1UjLxMCK5gD5iHL3Ff2zyXLzlTZE+fHPMLcAkzcp2u6pJ9xpAGekqqeqnISXZ2o8yXsqv8NVvl1zaSiSqU+kak9mIg/2+WC9W1qO2PeSLW2tiis980QnmyDOBg2oL01ITh/u+GTodEGwfRYJoNAJgUjcUMpWl0LuoG8lG6wukhA4QYFWpf2QPVgTR63VbpFgwCnUcSEPqHB0BRCsDHsd0k+/YSuPolABEBAAGJAh8EGAEIAAkFAlUIXh0CGwwACgkQPr/KqoyK2Z6zPA/+PkJTzP8kQw4GW0x2ZxXfOmkRVYpSEoHehf6y9YFN00+T8pb71RGItvuX5v6oPKPClOnIVg2WVHOq6Q3HsXEzl7oIbOtPE98WXHiVXud/djc54uHz9WjSPfy/idP7SMslo29BHR/K9nQkiGtayD57wdxgbLXObE3fA0gl4AsWl1EZzNcWVL4SIrvnBGpYIUGBcsTIiP3p09bu4Qf6HjJRXZlBuizigIgeO39l/G6tb6GA1cnbq4y6aCtQeXHLrnvak1jRqznlJWUqS1mQgOPF1MuOduHAvQbfMBQXAEfgOTzuH9PuKoGm7MePwTrU5GsOpNgS4LbvIRODJxYD+vIwA5BniijgfN9aj9KQVMURrd4Np7i0EVmj8P9FtNgYsEaDt7laGpNB9+9Y9heb6kNEulF7KI7y8CKikgvFGHHCyX2BCCbQBqi6wbEGq16qkTJmesYu9ig4v4xD/Q/cLJFziJLjEcWsL7hq7q2o6e7NL6hf5aTH0/bdeMXMqRzDCAFQ5Z+x0QUCgVonxzj+CuTD/LeOs/QHu/9emvm9EOMYY/X9vidLf58PT/AMqMiYbNWty6qY6k2LMw74Yd4+hO+Tjrk8MrqbCUs9h6ih9IOCo68JTWQQbgWSk2TAyd3U4OqTyBnHWr0HhHDRTOxyDbZUtXbk/r4Q4gTcAt+qjpswPyk=", "\x99\x02\x0d\x04\x55\x08\x5e\x1d\x01\x10\x00\xcd\x3e\x52\xfa\x36\x95\x84\x69\x1d\x8a\x27\x1e\xa9\xd7\xdb\x1c\x21\x5a\x6c\x04\x8a\x3b\x52\xac\xc2\x23\x37\x85\x6d\x12\x5a\x18\x38\x67\xfc\x79\x50\x81\xcc\x00\x6a\x57\x15\x36\x65\x94\x5f\x46\x50\xb2\xb7\x67\xf4\x21\x1f\x2d\x6b\x85\xbb\xd3\x8d\xda\x8d\x53\xb2\x56\x22\xc2\x7f\xa8\x48\x09\x47\xda\xb6\x85\x9b\x67\xb2\x25\x3a\x7f\x88\xce\xc8\x51\x40\xad\x7b\x98\xe6\x7f\x77\x7e\x2f\xda\xe3\x54\x8e\xc9\x8e\x63\x27\x6d\x5a\x3a\x49\x56\x36\x28\x30\xc3\xef\x67\x54\x2d\xc8\x3b\x22\xa6\x1c\x08\x03\x78\x09\x3f\xad\x3d\x93\xf9\xcb\x87\x18\x7f\xe0\xf1\x46\x5a\x71\x87\x1b\xea\xaa\x21\xb2\x02\x2e\xa5\x01\xc2\x76\xe3\xff\x1f\x27\x88\x6c\x1b\x49\x52\xd6\xf5\x35\xd8\xb5\x9e\x59\x31\xda\xe5\xa4\x4c\x33\xec\x7d\x92\x6b\xf7\x19\x87\x57\x1b\xe1\x71\x16\xa6\x70\xe7\x69\x19\x12\x70\x2c\x72\x90\xb6\xca\x66\xd7\xfb\x56\x05\x0a\xbf\x2c\x8c\x66\x06\x75\xef\xc3\xfb\x97\x0b\xbe\xeb\xb8\x3c\x63\x59\xe1\xf9\x12\x79\x2f\xb4\xc9\x7b\xde\xfe\xce\xae\xd6\x65\xfa\x46\x49\x05\xe9\x23\x86\x5a\xe2\x5f\x04\x93\x86\x75\x6c\xd8\xc1\xb7\xab\xf6\x4a\xa0\xaa\x91\xd3\x8e\x9a\xfc\x1c\xec\x9b\xcd\x5a\xe8\x06\x28\xed\x4e\x50\x7f\xb6\x89\xa6\x21\x47\x78\x05\x39\x3d\x26\xfe\x1f\xbf\x68\x00\xe1\x49\x02\xd7\x75\x0c\xf4\xa0\x45\xd4\xd9\xf3\x52\x72\x82\xf8\x9b\x38\xf4\xed\xe6\x8f\x41\xd9\x0e\x80\xab\x91\x63\x27\x67\x20\xec\x12\x68\x4c\xc9\x03\xd4\xe9\x84\x45\x1d\x69\x81\xe8\xe4\xca\x78\x3f\xd9\x9e\x4f\x9b\xd6\xec\xfe\x1c\x80\x60\xad\xb3\x6e\x88\x6f\xc0\xd1\xcd\x34\x56\x78\xfb\x95\x1e\x29\xf7\x8a\x0e\xbb\x2a\x48\xd3\x45\xee\xb0\xbb\x09\xc0\xfb\x23\x23\x81\x20\x14\x62\x9d\xa1\x1b\x4c\xd2\xca\x49\x86\x2f\xb7\x81\xff\x3c\x56\xad\x0d\x10\xd0\x5a\x15\x51\xcb\x04\x65\x95\x15\xa5\x6b\x7d\xfb\xab\x19\xc0\xd9\x61\x40\x1d\x9e\xf9\xbc\x8d\x23\xc2\x80\x13\xdf\x17\x1a\x59\xba\xd7\xb1\x36\x42\xe3\xc9\xa6\x97\x8c\x3b\x60\x6b\x9f\xe7\xe6\x04\x9d\xff\x74\x69\x58\x2b\xe9\xf6\xed\xd6\xe6\x37\x13\x34\x36\x45\xe1\x33\x91\x9c\xe5\xd4\x58\x77\x28\xb7\xff\x97\x9b\x21\xfc\xff\xe7\x27\xf3\x27\x6d\xd2\x9a\xce\x2c\x64\x3e\x16\xd7\x48\xf8\x05\xb2\x30\x25\xc7\x7a\x7e\x9a\x69\x9d\x63\x79\x22\xd9\xab\x00\x11\x01\x00\x01\xb4\x5a\x70\x64\x6e\x73\x20\x72\x65\x67\x72\x65\x73\x73\x69\x6f\x6e\x20\x74\x65\x73\x74\x69\x6e\x67\x20\x6b\x65\x79\x20\x28\x6f\x6e\x6c\x79\x20\x66\x6f\x72\x20\x74\x65\x73\x74\x69\x6e\x67\x20\x74\x68\x65\x20\x6f\x70\x65\x6e\x70\x67\x70\x6b\x65\x79\x20\x72\x72\x29\x20\x3c\x72\x65\x67\x72\x65\x73\x73\x69\x6f\x6e\x40\x70\x6f\x77\x65\x72\x64\x6e\x73\x2e\x6f\x72\x67\x3e\x89\x02\x37\x04\x13\x01\x08\x00\x21\x05\x02\x55\x08\x5e\x1d\x02\x1b\x03\x05\x0b\x09\x08\x07\x02\x06\x15\x08\x09\x0a\x0b\x02\x04\x16\x02\x03\x01\x02\x1e\x01\x02\x17\x80\x00\x0a\x09\x10\x3e\xbf\xca\xaa\x8c\x8a\xd9\x9e\xc0\xe9\x0f\xff\x60\xe0\x6e\xf2\x7c\x2d\xf9\xf8\x2e\x4a\x8f\xef\xc8\x29\x67\xd0\xfa\xa7\x4e\x20\x66\xe1\x1b\xc4\xda\x03\xc3\x52\x7b\x0a\xba\x85\xe8\x3f\x91\x79\xe9\x71\xc1\x5c\xa9\x7d\x90\x48\x26\xc9\xc8\x6d\xfe\x77\x3b\x73\xb3\x51\x1f\x35\x21\x69\xc3\x36\x45\x06\xae\xe8\x28\x0d\x64\x4f\x0a\x2d\xaa\x83\x4c\xa9\x44\xb9\xcf\xc0\x36\xda\xe7\x18\x66\x06\xf2\x03\x08\x77\x84\xe5\xcd\x4e\x6d\x68\xb1\x00\xf3\x2a\xc7\x20\x79\xf0\x9c\xcb\x83\x0e\x9c\x75\x6b\x41\x13\x91\xb6\x02\xae\x3e\xc2\xca\x7a\x9d\x85\x70\x80\x06\xa1\x63\x81\x12\xfb\x41\xbd\xfb\x52\x72\xcf\x13\xb6\x1d\x16\xde\xdf\x44\x6c\x19\x54\xb9\xdb\xdc\x65\x59\x2c\xa1\xee\xad\x9d\x13\x00\xdb\xcb\x75\x15\x43\x49\x3d\xdf\xd2\x50\x9c\x60\xca\x69\x3d\xb1\xc0\xbe\x2d\x8c\x21\x4e\xf5\x14\xf1\xa4\x9a\xcf\xbe\xb6\x3e\x20\x4e\x5b\x6a\xd1\x54\x6f\xac\xc3\x66\xd5\xee\x6f\xe3\xe8\x47\x67\xef\x90\x37\x8b\x26\xe3\xe0\xc9\x1a\x5a\x3b\x97\xce\x74\x82\x73\x17\x73\xb7\x3b\x1a\x44\x46\xb6\x0f\x20\x57\x48\x79\x93\x98\xd7\xe6\x27\x1d\x83\x1c\xaf\x10\xd4\xee\x05\x0f\x90\xa6\xb9\x18\x4d\x18\x7d\xb9\x8c\xe9\xa1\x34\x3e\x35\x84\x59\x96\x06\x89\xc7\x16\x83\x22\xfd\x4a\xfe\x46\xf7\xa0\xfb\x67\x00\x42\x89\x04\x2d\x36\x21\x96\xc1\xd0\x48\x92\xdd\x1e\x69\xc5\x85\xe1\x50\xda\x16\x57\xb7\xb3\xd8\x0e\x21\x5f\x52\xfe\xb5\xfb\xe5\x80\x20\x1b\x6d\x0f\xdc\x04\xbb\xea\x5e\x0a\x60\xb1\xff\x71\xe3\x28\x12\x8b\x9d\xaf\x40\x1c\xb7\xb7\x06\x72\xae\xd2\xec\xa2\x0c\x5a\x73\x10\x03\x7c\xf7\x32\x95\xe1\xed\x8e\xef\xfe\x95\x29\x2e\xf9\x9e\xbe\x5b\xa3\xa8\x8c\x0d\x24\x63\x61\xe5\x68\xcc\x90\x01\x32\x4b\x21\x77\x7e\xe8\xd1\x3f\xb5\x8c\xbb\xd1\x1b\x14\xf4\xea\xb4\x3b\x25\x53\x8f\x18\x76\xd5\x23\xcb\xb0\x4c\x5e\x4b\x04\x9f\x72\x25\x52\x2e\x7e\xc8\xb8\xa1\x88\x39\x5f\x36\x30\x41\xc5\x86\x9a\x35\x52\x1b\xfe\x1b\x9d\x89\xf1\x3d\x5f\x23\xbd\xf4\xb1\x2e\x13\x97\x90\xde\x90\xb6\x82\xcf\x5a\x60\xfc\x59\x88\xfc\x44\x1e\x02\xc6\x2a\x9b\xdf\x2e\x0c\x06\x85\x41\xcb\x3a\x80\x3b\x1e\xf1\x56\x06\x9a\x9d\x97\x62\xca\x02\x5b\xbd\x9a\xb3\xc1\x25\x2a\x63\x08\x6d\x0f\xf1\x0f\xba\xb1\x0c\xe4\x4b\x9a\x30\xad\x56\x8c\x8a\x1f\x5f\x7e\xb3\xa3\xe9\xeb\x56\xb9\x02\x0d\x04\x55\x08\x5e\x1d\x01\x10\x00\xc3\x7a\xa5\xc2\xc9\x52\x1c\x3d\x6d\xf3\xb9\x92\xa8\x4b\xbf\x8a\x6b\xb7\x40\x2a\x54\x15\x28\x93\xa5\x3c\xb4\x86\x84\x73\xdb\xc3\x69\xab\x01\x70\x51\xd7\x80\xfb\x28\x38\x6f\x7b\x0d\x70\x7a\x05\xdb\xec\x1e\xe2\xbf\xe6\xe3\x15\x1f\x93\xcd\x7b\x9f\x0c\x2e\x9d\x3c\x88\x2a\x08\xb7\x40\xb4\x09\xb3\x77\xf8\x3f\x50\x6d\xe8\x37\xec\x0c\x8a\xc8\x56\xa7\x07\xf0\x97\xab\x5b\x4f\x29\x50\x89\x73\xa0\x4c\x36\x43\xbf\x06\xcc\x27\x52\xc4\xd3\x9a\x57\x1d\xe0\x6a\x2f\xa5\xa2\x54\x38\xe6\x73\x6e\x51\x94\x40\x16\xe1\xcd\x1a\x21\x86\x63\x86\x7d\xdb\x12\x70\x67\x9e\x60\xc1\x3f\xc8\xd1\x6a\xce\xdd\x59\x02\xb5\x78\xbd\xad\x61\x38\xb3\x94\xa4\xd1\x6f\xf5\x33\xb2\xd1\x95\x62\xff\x20\xe2\xfa\xc6\x00\xf5\xf0\x92\x65\x4f\x5a\x95\x62\x3f\x71\xb0\x5c\xb7\x92\x3e\xdd\xb8\x75\x97\x6e\xae\x9a\x56\x6c\x61\x09\xbc\xba\xa9\xd2\x82\x24\x16\xa5\x19\xba\x50\x93\xba\xc5\x06\xe6\x50\xdf\x46\xcc\x26\x32\x28\x29\xa9\xcd\x94\x5d\xba\xaa\xa3\x8e\xd8\x85\xd7\x45\xb5\xcb\x38\xa8\x4d\xf4\xed\xab\x67\x2f\x53\xa3\xdd\x20\xd6\x18\xf3\x0d\x94\xb8\xe8\xd1\x77\xd8\x02\x86\xdb\xc6\xb9\xdb\x8e\x23\x0a\xa5\x45\x36\x12\x69\x75\xf5\x74\x3a\x05\x7b\x5b\xd6\xa9\x9c\xd1\x9b\xea\x48\x0e\xe9\x8e\xf5\xe5\xf9\x8c\xc9\xb3\x15\x72\xc3\x1b\x6a\x43\xda\x5f\x7b\xce\x84\xa3\x54\x57\xff\xa5\x8a\x01\x6a\xc8\x17\xe3\x77\xb7\x57\xf6\x84\x93\xd3\xd4\x81\x76\xd8\x16\x6d\x90\xc4\x53\x1c\x21\x84\x8e\xf4\x61\x03\x32\x81\x3e\x43\x1a\x0c\x5d\xfd\x54\x8c\xbc\x4c\x08\xae\x60\x0f\x98\x87\x2f\x71\x5f\xdb\x3c\x97\x2f\x39\x53\x64\x4f\x9f\x1c\xf3\x0b\x70\x09\x33\x72\x9d\xae\xea\x92\x7d\xc6\x90\x06\x7a\x4a\xaa\x7a\xa9\xc8\x49\x76\x76\xa3\xcc\x97\xb2\xab\xfc\x35\x5b\xe5\xd7\x36\x92\x89\x2a\x94\xfa\x46\xa4\xf6\x62\x20\xff\x6f\x96\x0b\xd5\xb5\xa8\xed\x8f\x79\x22\xd6\xda\xd8\xa2\xb3\xdf\x34\x42\x79\xb2\x0c\xe0\x60\xda\x82\xf4\xd4\x84\xe1\xfe\xef\x86\x4e\x87\x44\x1b\x07\xd1\x60\x9a\x0d\x00\x98\x14\x8d\xc5\x0c\xa5\x69\x74\x2e\xea\x06\xf2\x51\xba\xc2\xe9\x21\x03\x84\x18\x15\x6a\x5f\xd9\x03\xd5\x81\x34\x7a\xdd\x56\xe9\x16\x0c\x02\x9d\x47\x12\x10\xfa\x87\x07\x40\x51\x0a\xc0\xc7\xb1\xdd\x24\xfb\xf6\x12\xb8\xfa\x25\x00\x11\x01\x00\x01\x89\x02\x1f\x04\x18\x01\x08\x00\x09\x05\x02\x55\x08\x5e\x1d\x02\x1b\x0c\x00\x0a\x09\x10\x3e\xbf\xca\xaa\x8c\x8a\xd9\x9e\xb3\x3c\x0f\xfe\x3e\x42\x53\xcc\xff\x24\x43\x0e\x06\x5b\x4c\x76\x67\x15\xdf\x3a\x69\x11\x55\x8a\x52\x12\x81\xde\x85\xfe\xb2\xf5\x81\x4d\xd3\x4f\x93\xf2\x96\xfb\xd5\x11\x88\xb6\xfb\x97\xe6\xfe\xa8\x3c\xa3\xc2\x94\xe9\xc8\x56\x0d\x96\x54\x73\xaa\xe9\x0d\xc7\xb1\x71\x33\x97\xba\x08\x6c\xeb\x4f\x13\xdf\x16\x5c\x78\x95\x5e\xe7\x7f\x76\x37\x39\xe2\xe1\xf3\xf5\x68\xd2\x3d\xfc\xbf\x89\xd3\xfb\x48\xcb\x25\xa3\x6f\x41\x1d\x1f\xca\xf6\x74\x24\x88\x6b\x5a\xc8\x3e\x7b\xc1\xdc\x60\x6c\xb5\xce\x6c\x4d\xdf\x03\x48\x25\xe0\x0b\x16\x97\x51\x19\xcc\xd7\x16\x54\xbe\x12\x22\xbb\xe7\x04\x6a\x58\x21\x41\x81\x72\xc4\xc8\x88\xfd\xe9\xd3\xd6\xee\xe1\x07\xfa\x1e\x32\x51\x5d\x99\x41\xba\x2c\xe2\x80\x88\x1e\x3b\x7f\x65\xfc\x6e\xad\x6f\xa1\x80\xd5\xc9\xdb\xab\x8c\xba\x68\x2b\x50\x79\x71\xcb\xae\x7b\xda\x93\x58\xd1\xab\x39\xe5\x25\x65\x2a\x4b\x59\x90\x80\xe3\xc5\xd4\xcb\x8e\x76\xe1\xc0\xbd\x06\xdf\x30\x14\x17\x00\x47\xe0\x39\x3c\xee\x1f\xd3\xee\x2a\x81\xa6\xec\xc7\x8f\xc1\x3a\xd4\xe4\x6b\x0e\xa4\xd8\x12\xe0\xb6\xef\x21\x13\x83\x27\x16\x03\xfa\xf2\x30\x03\x90\x67\x8a\x28\xe0\x7c\xdf\x5a\x8f\xd2\x90\x54\xc5\x11\xad\xde\x0d\xa7\xb8\xb4\x11\x59\xa3\xf0\xff\x45\xb4\xd8\x18\xb0\x46\x83\xb7\xb9\x5a\x1a\x93\x41\xf7\xef\x58\xf6\x17\x9b\xea\x43\x44\xba\x51\x7b\x28\x8e\xf2\xf0\x22\xa2\x92\x0b\xc5\x18\x71\xc2\xc9\x7d\x81\x08\x26\xd0\x06\xa8\xba\xc1\xb1\x06\xab\x5e\xaa\x91\x32\x66\x7a\xc6\x2e\xf6\x28\x38\xbf\x8c\x43\xfd\x0f\xdc\x2c\x91\x73\x88\x92\xe3\x11\xc5\xac\x2f\xb8\x6a\xee\xad\xa8\xe9\xee\xcd\x2f\xa8\x5f\xe5\xa4\xc7\xd3\xf6\xdd\x78\xc5\xcc\xa9\x1c\xc3\x08\x01\x50\xe5\x9f\xb1\xd1\x05\x02\x81\x5a\x27\xc7\x38\xfe\x0a\xe4\xc3\xfc\xb7\x8e\xb3\xf4\x07\xbb\xff\x5e\x9a\xf9\xbd\x10\xe3\x18\x63\xf5\xfd\xbe\x27\x4b\x7f\x9f\x0f\x4f\xf0\x0c\xa8\xc8\x98\x6c\xd5\xad\xcb\xaa\x98\xea\x4d\x8b\x33\x0e\xf8\x61\xde\x3e\x84\xef\x93\x8e\xb9\x3c\x32\xba\x9b\x09\x4b\x3d\x87\xa8\xa1\xf4\x83\x82\xa3\xaf\x09\x4d\x64\x10\x6e\x05\x92\x93\x64\xc0\xc9\xdd\xd4\xe0\xea\x93\xc8\x19\xc7\x5a\xbd\x07\x84\x70\xd1\x4c\xec\x72\x0d\xb6\x54\xb5\x76\xe4\xfe\xbe\x10\xe2\x04\xdc\x02\xdf\xaa\x8e\x9b\x30\x3f\x29",false))
-
-     (CASE_S(QType::SPF, "\"v=spf1 a:mail.rec.test ~all\"", "\x1bv=spf1 a:mail.rec.test ~all",false))
-     (CASE_S(QType::EUI48, "00-11-22-33-44-55", "\x00\x11\x22\x33\x44\x55",false))
-     (CASE_S(QType::EUI64, "00-11-22-33-44-55-66-77", "\x00\x11\x22\x33\x44\x55\x66\x77",false))
-     (CASE_S(QType::TSIG, "HMAC-MD5.SIG-ALG.REG.INT. 1368386956 60 16 TkbpD66/Mtgo8GUEFZIwhg== 12345 0 0", "\x08HMAC-MD5\x07SIG-ALG\x03REG\x03INT\x00\x00\x00\x51\x8f\xed\x8c\x00\x3c\x00\x10\x4e\x46\xe9\x0f\xae\xbf\x32\xd8\x28\xf0\x65\x04\x15\x92\x30\x86\x30\x39\x00\x00\x00\x00",false))
-     (CASE_S(QType::TSIG, "HMAC-MD5.SIG-ALG.REG.INT. 1368386956 60 16 TkbpD66/Mtgo8GUEFZIwhg== 12345 18 16 TkbpD66/Mtgo8GUEFZIwhg==", "\x08HMAC-MD5\x07SIG-ALG\x03REG\x03INT\x00\x00\x00\x51\x8f\xed\x8c\x00\x3c\x00\x10\x4e\x46\xe9\x0f\xae\xbf\x32\xd8\x28\xf0\x65\x04\x15\x92\x30\x86\x30\x39\x00\x12\x00\x10\x4e\x46\xe9\x0f\xae\xbf\x32\xd8\x28\xf0\x65\x04\x15\x92\x30\x86",false))
-     (CASE_S(QType::TKEY, "gss-tsig. 12345 12345 3 21 4 dGVzdA== 4 dGVzdA==", "\x08gss-tsig\x00\x00\x00\x30\x39\x00\x00\x30\x39\x00\x03\x00\x15\x00\x04test\x00\x04test", false))
-/*        (CASE_S(QType::ADDR, "zone format", "line format",false)) */
-     (CASE_S(QType::DLV, "20642 8 2 04443abe7e94c3985196beae5d548c727b044dda5151e60d7cd76a9fd931d00e", "\x50\xa2\x08\x02\x04\x44\x3a\xbe\x7e\x94\xc3\x98\x51\x96\xbe\xae\x5d\x54\x8c\x72\x7b\x04\x4d\xda\x51\x51\xe6\x0d\x7c\xd7\x6a\x9f\xd9\x31\xd0\x0e",false))
-     (CASE_S(QType::URI, "10000 1 \"ftp://ftp1.example.com/public\"", "\x27\x10\x00\x01\x66\x74\x70\x3a\x2f\x2f\x66\x74\x70\x31\x2e\x65\x78\x61\x6d\x70\x6c\x65\x2e\x63\x6f\x6d\x2f\x70\x75\x62\x6c\x69\x63", false))
-     (CASE_S(QType::URI, "10 1 \"ftp://ftp1.example.com/public/with/a/very/very/very/very/very/very/very/very/very/very/very/very/very/very/very/very/very/very/very/very/very/very/very/very/very/very/very/very/very/very/very/very/very/very/very/very/very/very/very/very/very/very/very/long/url\"", "\x00\x0a\x00\x01\x66\x74\x70\x3a\x2f\x2f\x66\x74\x70\x31\x2e\x65\x78\x61\x6d\x70\x6c\x65\x2e\x63\x6f\x6d\x2f\x70\x75\x62\x6c\x69\x63\x2f\x77\x69\x74\x68\x2f\x61\x2f\x76\x65\x72\x79\x2f\x76\x65\x72\x79\x2f\x76\x65\x72\x79\x2f\x76\x65\x72\x79\x2f\x76\x65\x72\x79\x2f\x76\x65\x72\x79\x2f\x76\x65\x72\x79\x2f\x76\x65\x72\x79\x2f\x76\x65\x72\x79\x2f\x76\x65\x72\x79\x2f\x76\x65\x72\x79\x2f\x76\x65\x72\x79\x2f\x76\x65\x72\x79\x2f\x76\x65\x72\x79\x2f\x76\x65\x72\x79\x2f\x76\x65\x72\x79\x2f\x76\x65\x72\x79\x2f\x76\x65\x72\x79\x2f\x76\x65\x72\x79\x2f\x76\x65\x72\x79\x2f\x76\x65\x72\x79\x2f\x76\x65\x72\x79\x2f\x76\x65\x72\x79\x2f\x76\x65\x72\x79\x2f\x76\x65\x72\x79\x2f\x76\x65\x72\x79\x2f\x76\x65\x72\x79\x2f\x76\x65\x72\x79\x2f\x76\x65\x72\x79\x2f\x76\x65\x72\x79\x2f\x76\x65\x72\x79\x2f\x76\x65\x72\x79\x2f\x76\x65\x72\x79\x2f\x76\x65\x72\x79\x2f\x76\x65\x72\x79\x2f\x76\x65\x72\x79\x2f\x76\x65\x72\x79\x2f\x76\x65\x72\x79\x2f\x76\x65\x72\x79\x2f\x76\x65\x72\x79\x2f\x76\x65\x72\x79\x2f\x76\x65\x72\x79\x2f\x76\x65\x72\x79\x2f\x6c\x6f\x6e\x67\x2f\x75\x72\x6c", false))
-     (CASE_S(QType::CAA, "0 issue \"example.net\"", "\x00\x05\x69\x73\x73\x75\x65\x65\x78\x61\x6d\x70\x6c\x65\x2e\x6e\x65\x74",false))
-     (CASE_S((QType::typeenum)65226,"\\# 3 414243","\x41\x42\x43",false))
+     (CASE_L(QType::RRSIG, "SOA 8 3 300 1369267200 1368057600 54216 rec.test. ecWKD/OsdAiXpbM/sgPT82KVD/WiQnnqcxoJgiH3ixHa+LOAcYU7FG7V4BRRJxLriY1e0rB2gAs3kCel9D4bzfK6wAqG4Di/eHUgHptRlaR2ycELJ4t1pjzrnuGiIzA1wM2izRmeE+Xoy1367Qu0pOz5DLzTfQITWFsB2iUzN4Y=", "SOA 8 3 300 20130523000000 20130509000000 54216 rec.test. ecWKD/OsdAiXpbM/sgPT82KVD/WiQnnqcxoJgiH3ixHa+LOAcYU7FG7V4BRRJxLriY1e0rB2gAs3kCel9D4bzfK6wAqG4Di/eHUgHptRlaR2ycELJ4t1pjzrnuGiIzA1wM2izRmeE+Xoy1367Qu0pOz5DLzTfQITWFsB2iUzN4Y=", "\x00\x06\x08\x03\x00\x00\x01\x2c\x51\x9d\x5c\x00\x51\x8a\xe7\x00\xd3\xc8\x03\x72\x65\x63\x04\x74\x65\x73\x74\x00\x79\xc5\x8a\x0f\xf3\xac\x74\x08\x97\xa5\xb3\x3f\xb2\x03\xd3\xf3\x62\x95\x0f\xf5\xa2\x42\x79\xea\x73\x1a\x09\x82\x21\xf7\x8b\x11\xda\xf8\xb3\x80\x71\x85\x3b\x14\x6e\xd5\xe0\x14\x51\x27\x12\xeb\x89\x8d\x5e\xd2\xb0\x76\x80\x0b\x37\x90\x27\xa5\xf4\x3e\x1b\xcd\xf2\xba\xc0\x0a\x86\xe0\x38\xbf\x78\x75\x20\x1e\x9b\x51\x95\xa4\x76\xc9\xc1\x0b\x27\x8b\x75\xa6\x3c\xeb\x9e\xe1\xa2\x23\x30\x35\xc0\xcd\xa2\xcd\x19\x9e\x13\xe5\xe8\xcb\x5d\xfa\xed\x0b\xb4\xa4\xec\xf9\x0c\xbc\xd3\x7d\x02\x13\x58\x5b\x01\xda\x25\x33\x37\x86"))
+     (CASE_S(QType::NSEC, "a.rec.test. A NS SOA MX AAAA RRSIG NSEC DNSKEY", "\x01""a\x03rec\x04test\x00\x00\x07\x62\x01\x00\x08\x00\x03\x80"))
+     (CASE_S(QType::DNSKEY, "257 3 5 AwEAAZVtlHc8O4TVmlGx/PGJTc7hbVjMR7RywxLuAm1dqgyHvgNRD7chYLsALOdZKW6VRvusbyhoOPilnh8XpucBDqjGD6lIemsURz7drZEqcLupVA0TPxXABZ6auJ3jumqIhSOcLj9rpSwI4xuWt0yu6LR9tL2q8+A0yEZxcAaKS+Wq0fExJ93NxgXl1/fY+JcYQvonjd31GxXXef9uf0exXyzowh5h8+IIBETU+ZiYVB5BqiwkICZL/OX57idm99ycA2/tIen66F8u2ueTvgPcecnoqHvW0MtLQKzeNmqdGNthHhV5di0SZdMZQeo/izs68uN2WzqQDZy9Ec2JwBTbxWE=", "\x01\x01\x03\x05\x03\x01\x00\x01\x95\x6d\x94\x77\x3c\x3b\x84\xd5\x9a\x51\xb1\xfc\xf1\x89\x4d\xce\xe1\x6d\x58\xcc\x47\xb4\x72\xc3\x12\xee\x02\x6d\x5d\xaa\x0c\x87\xbe\x03\x51\x0f\xb7\x21\x60\xbb\x00\x2c\xe7\x59\x29\x6e\x95\x46\xfb\xac\x6f\x28\x68\x38\xf8\xa5\x9e\x1f\x17\xa6\xe7\x01\x0e\xa8\xc6\x0f\xa9\x48\x7a\x6b\x14\x47\x3e\xdd\xad\x91\x2a\x70\xbb\xa9\x54\x0d\x13\x3f\x15\xc0\x05\x9e\x9a\xb8\x9d\xe3\xba\x6a\x88\x85\x23\x9c\x2e\x3f\x6b\xa5\x2c\x08\xe3\x1b\x96\xb7\x4c\xae\xe8\xb4\x7d\xb4\xbd\xaa\xf3\xe0\x34\xc8\x46\x71\x70\x06\x8a\x4b\xe5\xaa\xd1\xf1\x31\x27\xdd\xcd\xc6\x05\xe5\xd7\xf7\xd8\xf8\x97\x18\x42\xfa\x27\x8d\xdd\xf5\x1b\x15\xd7\x79\xff\x6e\x7f\x47\xb1\x5f\x2c\xe8\xc2\x1e\x61\xf3\xe2\x08\x04\x44\xd4\xf9\x98\x98\x54\x1e\x41\xaa\x2c\x24\x20\x26\x4b\xfc\xe5\xf9\xee\x27\x66\xf7\xdc\x9c\x03\x6f\xed\x21\xe9\xfa\xe8\x5f\x2e\xda\xe7\x93\xbe\x03\xdc\x79\xc9\xe8\xa8\x7b\xd6\xd0\xcb\x4b\x40\xac\xde\x36\x6a\x9d\x18\xdb\x61\x1e\x15\x79\x76\x2d\x12\x65\xd3\x19\x41\xea\x3f\x8b\x3b\x3a\xf2\xe3\x76\x5b\x3a\x90\x0d\x9c\xbd\x11\xcd\x89\xc0\x14\xdb\xc5\x61"))
+
+     (CASE_S(QType::DHCID, "AAAB92JtyyO73zqENgu9LVua+0PZoeCcKapTw4asbYmx5F4=", "\x00\x00\x01\xf7\x62\x6d\xcb\x23\xbb\xdf\x3a\x84\x36\x0b\xbd\x2d\x5b\x9a\xfb\x43\xd9\xa1\xe0\x9c\x29\xaa\x53\xc3\x86\xac\x6d\x89\xb1\xe4\x5e"))
+     (CASE_S(QType::DHCID, "AAEB92JtyyO73zqENgu9LVua+0PZoeCcKapTw4asbYmx5F4=", "\x00\x01\x01\xf7\x62\x6d\xcb\x23\xbb\xdf\x3a\x84\x36\x0b\xbd\x2d\x5b\x9a\xfb\x43\xd9\xa1\xe0\x9c\x29\xaa\x53\xc3\x86\xac\x6d\x89\xb1\xe4\x5e"))
+     (CASE_S(QType::DHCID, "AAIB92JtyyO73zqENgu9LVua+0PZoeCcKapTw4asbYmx5F4=", "\x00\x02\x01\xf7\x62\x6d\xcb\x23\xbb\xdf\x3a\x84\x36\x0b\xbd\x2d\x5b\x9a\xfb\x43\xd9\xa1\xe0\x9c\x29\xaa\x53\xc3\x86\xac\x6d\x89\xb1\xe4\x5e"))
+
+     (CASE_S(QType::NSEC3, "1 1 1 f00b RPF1JGFCCNFA7STPTIJ9FPFNM40A4FLL NS SOA RRSIG DNSKEY NSEC3PARAM", "\x01\x01\x00\x01\x02\xf0\x0b\x14\xde\x5e\x19\xc1\xec\x65\xde\xa3\xf3\xb9\xec\xa6\x97\xe5\xf7\xb1\x00\xa2\x3e\xb5\x00\x07\x22\x00\x00\x00\x00\x02\x90"))
+     (CASE_S(QType::NSEC3PARAM, "1 0 1 f00b", "\x01\x00\x00\x01\x02\xf0\x0b"))
+
+     (CASE_S(QType::TLSA, "0 0 0 308201f43082015da003020102020900ac547c5557870ec7300d06092a864886f70d010105050030133111300f06035504030c087265632e74657374301e170d3133303531323139343830395a170d3133303631313139343830395a30133111300f06035504030c087265632e7465737430819f300d06092a864886f70d010101050003818d0030818902818100d282bb968dfdec0e5d13dfcc0a36ed73178581424e10a37c89d3014204933b3a8c1159fdecb221afe4168883d2d00ac1f15fca4614fbd5e05de2e37ad0fbad8b7748dddbcf30b39e80466c61c733415e72b9f42d5fad0bf35f041eb5631eded00314c66c4878b351416e5c6b9096f2a7088a24387e5d0149c523739f84f502c70203010001a350304e301d0603551d0e0416041473715bbfd9bc2b824112f858586f166aafb99482301f0603551d2304183016801473715bbfd9bc2b824112f858586f166aafb99482300c0603551d13040530030101ff300d06092a864886f70d0101050500038181005550f1d64139ab0e86c5b303fc69015d1676ca95931071ae41884656c71c116a38138ecf63054b350dc78983cb4a83288dbc81c5a659a56cc6843d5452c3e98449b94a0cf0c0cd7190c96caa5f0ee9a3bef7e75002be4a233673852bdf1a5fd306a7080eb4fead9b3ad162074b5f007e9156e220302dea8c700868a12577e7c4", "\x00\x00\x00\x30\x82\x01\xf4\x30\x82\x01\x5d\xa0\x03\x02\x01\x02\x02\x09\x00\xac\x54\x7c\x55\x57\x87\x0e\xc7\x30\x0d\x06\x09\x2a\x86\x48\x86\xf7\x0d\x01\x01\x05\x05\x00\x30\x13\x31\x11\x30\x0f\x06\x03\x55\x04\x03\x0c\x08\x72\x65\x63\x2e\x74\x65\x73\x74\x30\x1e\x17\x0d\x31\x33\x30\x35\x31\x32\x31\x39\x34\x38\x30\x39\x5a\x17\x0d\x31\x33\x30\x36\x31\x31\x31\x39\x34\x38\x30\x39\x5a\x30\x13\x31\x11\x30\x0f\x06\x03\x55\x04\x03\x0c\x08\x72\x65\x63\x2e\x74\x65\x73\x74\x30\x81\x9f\x30\x0d\x06\x09\x2a\x86\x48\x86\xf7\x0d\x01\x01\x01\x05\x00\x03\x81\x8d\x00\x30\x81\x89\x02\x81\x81\x00\xd2\x82\xbb\x96\x8d\xfd\xec\x0e\x5d\x13\xdf\xcc\x0a\x36\xed\x73\x17\x85\x81\x42\x4e\x10\xa3\x7c\x89\xd3\x01\x42\x04\x93\x3b\x3a\x8c\x11\x59\xfd\xec\xb2\x21\xaf\xe4\x16\x88\x83\xd2\xd0\x0a\xc1\xf1\x5f\xca\x46\x14\xfb\xd5\xe0\x5d\xe2\xe3\x7a\xd0\xfb\xad\x8b\x77\x48\xdd\xdb\xcf\x30\xb3\x9e\x80\x46\x6c\x61\xc7\x33\x41\x5e\x72\xb9\xf4\x2d\x5f\xad\x0b\xf3\x5f\x04\x1e\xb5\x63\x1e\xde\xd0\x03\x14\xc6\x6c\x48\x78\xb3\x51\x41\x6e\x5c\x6b\x90\x96\xf2\xa7\x08\x8a\x24\x38\x7e\x5d\x01\x49\xc5\x23\x73\x9f\x84\xf5\x02\xc7\x02\x03\x01\x00\x01\xa3\x50\x30\x4e\x30\x1d\x06\x03\x55\x1d\x0e\x04\x16\x04\x14\x73\x71\x5b\xbf\xd9\xbc\x2b\x82\x41\x12\xf8\x58\x58\x6f\x16\x6a\xaf\xb9\x94\x82\x30\x1f\x06\x03\x55\x1d\x23\x04\x18\x30\x16\x80\x14\x73\x71\x5b\xbf\xd9\xbc\x2b\x82\x41\x12\xf8\x58\x58\x6f\x16\x6a\xaf\xb9\x94\x82\x30\x0c\x06\x03\x55\x1d\x13\x04\x05\x30\x03\x01\x01\xff\x30\x0d\x06\x09\x2a\x86\x48\x86\xf7\x0d\x01\x01\x05\x05\x00\x03\x81\x81\x00\x55\x50\xf1\xd6\x41\x39\xab\x0e\x86\xc5\xb3\x03\xfc\x69\x01\x5d\x16\x76\xca\x95\x93\x10\x71\xae\x41\x88\x46\x56\xc7\x1c\x11\x6a\x38\x13\x8e\xcf\x63\x05\x4b\x35\x0d\xc7\x89\x83\xcb\x4a\x83\x28\x8d\xbc\x81\xc5\xa6\x59\xa5\x6c\xc6\x84\x3d\x54\x52\xc3\xe9\x84\x49\xb9\x4a\x0c\xf0\xc0\xcd\x71\x90\xc9\x6c\xaa\x5f\x0e\xe9\xa3\xbe\xf7\xe7\x50\x02\xbe\x4a\x23\x36\x73\x85\x2b\xdf\x1a\x5f\xd3\x06\xa7\x08\x0e\xb4\xfe\xad\x9b\x3a\xd1\x62\x07\x4b\x5f\x00\x7e\x91\x56\xe2\x20\x30\x2d\xea\x8c\x70\x08\x68\xa1\x25\x77\xe7\xc4"))
+     (CASE_S(QType::TLSA, "0 1 0 30819f300d06092a864886f70d010101050003818d0030818902818100d282bb968dfdec0e5d13dfcc0a36ed73178581424e10a37c89d3014204933b3a8c1159fdecb221afe4168883d2d00ac1f15fca4614fbd5e05de2e37ad0fbad8b7748dddbcf30b39e80466c61c733415e72b9f42d5fad0bf35f041eb5631eded00314c66c4878b351416e5c6b9096f2a7088a24387e5d0149c523739f84f502c70203010001", "\x00\x01\x00\x30\x81\x9f\x30\x0d\x06\x09\x2a\x86\x48\x86\xf7\x0d\x01\x01\x01\x05\x00\x03\x81\x8d\x00\x30\x81\x89\x02\x81\x81\x00\xd2\x82\xbb\x96\x8d\xfd\xec\x0e\x5d\x13\xdf\xcc\x0a\x36\xed\x73\x17\x85\x81\x42\x4e\x10\xa3\x7c\x89\xd3\x01\x42\x04\x93\x3b\x3a\x8c\x11\x59\xfd\xec\xb2\x21\xaf\xe4\x16\x88\x83\xd2\xd0\x0a\xc1\xf1\x5f\xca\x46\x14\xfb\xd5\xe0\x5d\xe2\xe3\x7a\xd0\xfb\xad\x8b\x77\x48\xdd\xdb\xcf\x30\xb3\x9e\x80\x46\x6c\x61\xc7\x33\x41\x5e\x72\xb9\xf4\x2d\x5f\xad\x0b\xf3\x5f\x04\x1e\xb5\x63\x1e\xde\xd0\x03\x14\xc6\x6c\x48\x78\xb3\x51\x41\x6e\x5c\x6b\x90\x96\xf2\xa7\x08\x8a\x24\x38\x7e\x5d\x01\x49\xc5\x23\x73\x9f\x84\xf5\x02\xc7\x02\x03\x01\x00\x01"))
+     (CASE_S(QType::TLSA, "1 0 0 308201f43082015da003020102020900ac547c5557870ec7300d06092a864886f70d010105050030133111300f06035504030c087265632e74657374301e170d3133303531323139343830395a170d3133303631313139343830395a30133111300f06035504030c087265632e7465737430819f300d06092a864886f70d010101050003818d0030818902818100d282bb968dfdec0e5d13dfcc0a36ed73178581424e10a37c89d3014204933b3a8c1159fdecb221afe4168883d2d00ac1f15fca4614fbd5e05de2e37ad0fbad8b7748dddbcf30b39e80466c61c733415e72b9f42d5fad0bf35f041eb5631eded00314c66c4878b351416e5c6b9096f2a7088a24387e5d0149c523739f84f502c70203010001a350304e301d0603551d0e0416041473715bbfd9bc2b824112f858586f166aafb99482301f0603551d2304183016801473715bbfd9bc2b824112f858586f166aafb99482300c0603551d13040530030101ff300d06092a864886f70d0101050500038181005550f1d64139ab0e86c5b303fc69015d1676ca95931071ae41884656c71c116a38138ecf63054b350dc78983cb4a83288dbc81c5a659a56cc6843d5452c3e98449b94a0cf0c0cd7190c96caa5f0ee9a3bef7e75002be4a233673852bdf1a5fd306a7080eb4fead9b3ad162074b5f007e9156e220302dea8c700868a12577e7c4", "\x01\x00\x00\x30\x82\x01\xf4\x30\x82\x01\x5d\xa0\x03\x02\x01\x02\x02\x09\x00\xac\x54\x7c\x55\x57\x87\x0e\xc7\x30\x0d\x06\x09\x2a\x86\x48\x86\xf7\x0d\x01\x01\x05\x05\x00\x30\x13\x31\x11\x30\x0f\x06\x03\x55\x04\x03\x0c\x08\x72\x65\x63\x2e\x74\x65\x73\x74\x30\x1e\x17\x0d\x31\x33\x30\x35\x31\x32\x31\x39\x34\x38\x30\x39\x5a\x17\x0d\x31\x33\x30\x36\x31\x31\x31\x39\x34\x38\x30\x39\x5a\x30\x13\x31\x11\x30\x0f\x06\x03\x55\x04\x03\x0c\x08\x72\x65\x63\x2e\x74\x65\x73\x74\x30\x81\x9f\x30\x0d\x06\x09\x2a\x86\x48\x86\xf7\x0d\x01\x01\x01\x05\x00\x03\x81\x8d\x00\x30\x81\x89\x02\x81\x81\x00\xd2\x82\xbb\x96\x8d\xfd\xec\x0e\x5d\x13\xdf\xcc\x0a\x36\xed\x73\x17\x85\x81\x42\x4e\x10\xa3\x7c\x89\xd3\x01\x42\x04\x93\x3b\x3a\x8c\x11\x59\xfd\xec\xb2\x21\xaf\xe4\x16\x88\x83\xd2\xd0\x0a\xc1\xf1\x5f\xca\x46\x14\xfb\xd5\xe0\x5d\xe2\xe3\x7a\xd0\xfb\xad\x8b\x77\x48\xdd\xdb\xcf\x30\xb3\x9e\x80\x46\x6c\x61\xc7\x33\x41\x5e\x72\xb9\xf4\x2d\x5f\xad\x0b\xf3\x5f\x04\x1e\xb5\x63\x1e\xde\xd0\x03\x14\xc6\x6c\x48\x78\xb3\x51\x41\x6e\x5c\x6b\x90\x96\xf2\xa7\x08\x8a\x24\x38\x7e\x5d\x01\x49\xc5\x23\x73\x9f\x84\xf5\x02\xc7\x02\x03\x01\x00\x01\xa3\x50\x30\x4e\x30\x1d\x06\x03\x55\x1d\x0e\x04\x16\x04\x14\x73\x71\x5b\xbf\xd9\xbc\x2b\x82\x41\x12\xf8\x58\x58\x6f\x16\x6a\xaf\xb9\x94\x82\x30\x1f\x06\x03\x55\x1d\x23\x04\x18\x30\x16\x80\x14\x73\x71\x5b\xbf\xd9\xbc\x2b\x82\x41\x12\xf8\x58\x58\x6f\x16\x6a\xaf\xb9\x94\x82\x30\x0c\x06\x03\x55\x1d\x13\x04\x05\x30\x03\x01\x01\xff\x30\x0d\x06\x09\x2a\x86\x48\x86\xf7\x0d\x01\x01\x05\x05\x00\x03\x81\x81\x00\x55\x50\xf1\xd6\x41\x39\xab\x0e\x86\xc5\xb3\x03\xfc\x69\x01\x5d\x16\x76\xca\x95\x93\x10\x71\xae\x41\x88\x46\x56\xc7\x1c\x11\x6a\x38\x13\x8e\xcf\x63\x05\x4b\x35\x0d\xc7\x89\x83\xcb\x4a\x83\x28\x8d\xbc\x81\xc5\xa6\x59\xa5\x6c\xc6\x84\x3d\x54\x52\xc3\xe9\x84\x49\xb9\x4a\x0c\xf0\xc0\xcd\x71\x90\xc9\x6c\xaa\x5f\x0e\xe9\xa3\xbe\xf7\xe7\x50\x02\xbe\x4a\x23\x36\x73\x85\x2b\xdf\x1a\x5f\xd3\x06\xa7\x08\x0e\xb4\xfe\xad\x9b\x3a\xd1\x62\x07\x4b\x5f\x00\x7e\x91\x56\xe2\x20\x30\x2d\xea\x8c\x70\x08\x68\xa1\x25\x77\xe7\xc4"))
+     (CASE_S(QType::TLSA, "1 1 0 30819f300d06092a864886f70d010101050003818d0030818902818100d282bb968dfdec0e5d13dfcc0a36ed73178581424e10a37c89d3014204933b3a8c1159fdecb221afe4168883d2d00ac1f15fca4614fbd5e05de2e37ad0fbad8b7748dddbcf30b39e80466c61c733415e72b9f42d5fad0bf35f041eb5631eded00314c66c4878b351416e5c6b9096f2a7088a24387e5d0149c523739f84f502c70203010001", "\x01\x01\x00\x30\x81\x9f\x30\x0d\x06\x09\x2a\x86\x48\x86\xf7\x0d\x01\x01\x01\x05\x00\x03\x81\x8d\x00\x30\x81\x89\x02\x81\x81\x00\xd2\x82\xbb\x96\x8d\xfd\xec\x0e\x5d\x13\xdf\xcc\x0a\x36\xed\x73\x17\x85\x81\x42\x4e\x10\xa3\x7c\x89\xd3\x01\x42\x04\x93\x3b\x3a\x8c\x11\x59\xfd\xec\xb2\x21\xaf\xe4\x16\x88\x83\xd2\xd0\x0a\xc1\xf1\x5f\xca\x46\x14\xfb\xd5\xe0\x5d\xe2\xe3\x7a\xd0\xfb\xad\x8b\x77\x48\xdd\xdb\xcf\x30\xb3\x9e\x80\x46\x6c\x61\xc7\x33\x41\x5e\x72\xb9\xf4\x2d\x5f\xad\x0b\xf3\x5f\x04\x1e\xb5\x63\x1e\xde\xd0\x03\x14\xc6\x6c\x48\x78\xb3\x51\x41\x6e\x5c\x6b\x90\x96\xf2\xa7\x08\x8a\x24\x38\x7e\x5d\x01\x49\xc5\x23\x73\x9f\x84\xf5\x02\xc7\x02\x03\x01\x00\x01"))
+     (CASE_S(QType::TLSA, "1 0 1 6acea2f68b03d9efe97a967e137aca6ac3a89490d532d87806d9e9c257668453", "\x01\x00\x01\x6a\xce\xa2\xf6\x8b\x03\xd9\xef\xe9\x7a\x96\x7e\x13\x7a\xca\x6a\xc3\xa8\x94\x90\xd5\x32\xd8\x78\x06\xd9\xe9\xc2\x57\x66\x84\x53"))
+     (CASE_S(QType::TLSA, "1 0 2 e6dce237992803488d11d828b7728deddd4577de73d7d078338c8a45880beddff98e076a28bf8e3068da8e73667b802a721c95d7323b038c60200a430cb6fbd4", "\x01\x00\x02\xe6\xdc\xe2\x37\x99\x28\x03\x48\x8d\x11\xd8\x28\xb7\x72\x8d\xed\xdd\x45\x77\xde\x73\xd7\xd0\x78\x33\x8c\x8a\x45\x88\x0b\xed\xdf\xf9\x8e\x07\x6a\x28\xbf\x8e\x30\x68\xda\x8e\x73\x66\x7b\x80\x2a\x72\x1c\x95\xd7\x32\x3b\x03\x8c\x60\x20\x0a\x43\x0c\xb6\xfb\xd4"))
+     (CASE_S(QType::TLSA, "2 0 0 308201f43082015da003020102020900ac547c5557870ec7300d06092a864886f70d010105050030133111300f06035504030c087265632e74657374301e170d3133303531323139343830395a170d3133303631313139343830395a30133111300f06035504030c087265632e7465737430819f300d06092a864886f70d010101050003818d0030818902818100d282bb968dfdec0e5d13dfcc0a36ed73178581424e10a37c89d3014204933b3a8c1159fdecb221afe4168883d2d00ac1f15fca4614fbd5e05de2e37ad0fbad8b7748dddbcf30b39e80466c61c733415e72b9f42d5fad0bf35f041eb5631eded00314c66c4878b351416e5c6b9096f2a7088a24387e5d0149c523739f84f502c70203010001a350304e301d0603551d0e0416041473715bbfd9bc2b824112f858586f166aafb99482301f0603551d2304183016801473715bbfd9bc2b824112f858586f166aafb99482300c0603551d13040530030101ff300d06092a864886f70d0101050500038181005550f1d64139ab0e86c5b303fc69015d1676ca95931071ae41884656c71c116a38138ecf63054b350dc78983cb4a83288dbc81c5a659a56cc6843d5452c3e98449b94a0cf0c0cd7190c96caa5f0ee9a3bef7e75002be4a233673852bdf1a5fd306a7080eb4fead9b3ad162074b5f007e9156e220302dea8c700868a12577e7c4", "\x02\x00\x00\x30\x82\x01\xf4\x30\x82\x01\x5d\xa0\x03\x02\x01\x02\x02\x09\x00\xac\x54\x7c\x55\x57\x87\x0e\xc7\x30\x0d\x06\x09\x2a\x86\x48\x86\xf7\x0d\x01\x01\x05\x05\x00\x30\x13\x31\x11\x30\x0f\x06\x03\x55\x04\x03\x0c\x08\x72\x65\x63\x2e\x74\x65\x73\x74\x30\x1e\x17\x0d\x31\x33\x30\x35\x31\x32\x31\x39\x34\x38\x30\x39\x5a\x17\x0d\x31\x33\x30\x36\x31\x31\x31\x39\x34\x38\x30\x39\x5a\x30\x13\x31\x11\x30\x0f\x06\x03\x55\x04\x03\x0c\x08\x72\x65\x63\x2e\x74\x65\x73\x74\x30\x81\x9f\x30\x0d\x06\x09\x2a\x86\x48\x86\xf7\x0d\x01\x01\x01\x05\x00\x03\x81\x8d\x00\x30\x81\x89\x02\x81\x81\x00\xd2\x82\xbb\x96\x8d\xfd\xec\x0e\x5d\x13\xdf\xcc\x0a\x36\xed\x73\x17\x85\x81\x42\x4e\x10\xa3\x7c\x89\xd3\x01\x42\x04\x93\x3b\x3a\x8c\x11\x59\xfd\xec\xb2\x21\xaf\xe4\x16\x88\x83\xd2\xd0\x0a\xc1\xf1\x5f\xca\x46\x14\xfb\xd5\xe0\x5d\xe2\xe3\x7a\xd0\xfb\xad\x8b\x77\x48\xdd\xdb\xcf\x30\xb3\x9e\x80\x46\x6c\x61\xc7\x33\x41\x5e\x72\xb9\xf4\x2d\x5f\xad\x0b\xf3\x5f\x04\x1e\xb5\x63\x1e\xde\xd0\x03\x14\xc6\x6c\x48\x78\xb3\x51\x41\x6e\x5c\x6b\x90\x96\xf2\xa7\x08\x8a\x24\x38\x7e\x5d\x01\x49\xc5\x23\x73\x9f\x84\xf5\x02\xc7\x02\x03\x01\x00\x01\xa3\x50\x30\x4e\x30\x1d\x06\x03\x55\x1d\x0e\x04\x16\x04\x14\x73\x71\x5b\xbf\xd9\xbc\x2b\x82\x41\x12\xf8\x58\x58\x6f\x16\x6a\xaf\xb9\x94\x82\x30\x1f\x06\x03\x55\x1d\x23\x04\x18\x30\x16\x80\x14\x73\x71\x5b\xbf\xd9\xbc\x2b\x82\x41\x12\xf8\x58\x58\x6f\x16\x6a\xaf\xb9\x94\x82\x30\x0c\x06\x03\x55\x1d\x13\x04\x05\x30\x03\x01\x01\xff\x30\x0d\x06\x09\x2a\x86\x48\x86\xf7\x0d\x01\x01\x05\x05\x00\x03\x81\x81\x00\x55\x50\xf1\xd6\x41\x39\xab\x0e\x86\xc5\xb3\x03\xfc\x69\x01\x5d\x16\x76\xca\x95\x93\x10\x71\xae\x41\x88\x46\x56\xc7\x1c\x11\x6a\x38\x13\x8e\xcf\x63\x05\x4b\x35\x0d\xc7\x89\x83\xcb\x4a\x83\x28\x8d\xbc\x81\xc5\xa6\x59\xa5\x6c\xc6\x84\x3d\x54\x52\xc3\xe9\x84\x49\xb9\x4a\x0c\xf0\xc0\xcd\x71\x90\xc9\x6c\xaa\x5f\x0e\xe9\xa3\xbe\xf7\xe7\x50\x02\xbe\x4a\x23\x36\x73\x85\x2b\xdf\x1a\x5f\xd3\x06\xa7\x08\x0e\xb4\xfe\xad\x9b\x3a\xd1\x62\x07\x4b\x5f\x00\x7e\x91\x56\xe2\x20\x30\x2d\xea\x8c\x70\x08\x68\xa1\x25\x77\xe7\xc4"))
+     (CASE_S(QType::TLSA, "2 1 0 30819f300d06092a864886f70d010101050003818d0030818902818100d282bb968dfdec0e5d13dfcc0a36ed73178581424e10a37c89d3014204933b3a8c1159fdecb221afe4168883d2d00ac1f15fca4614fbd5e05de2e37ad0fbad8b7748dddbcf30b39e80466c61c733415e72b9f42d5fad0bf35f041eb5631eded00314c66c4878b351416e5c6b9096f2a7088a24387e5d0149c523739f84f502c70203010001", "\x02\x01\x00\x30\x81\x9f\x30\x0d\x06\x09\x2a\x86\x48\x86\xf7\x0d\x01\x01\x01\x05\x00\x03\x81\x8d\x00\x30\x81\x89\x02\x81\x81\x00\xd2\x82\xbb\x96\x8d\xfd\xec\x0e\x5d\x13\xdf\xcc\x0a\x36\xed\x73\x17\x85\x81\x42\x4e\x10\xa3\x7c\x89\xd3\x01\x42\x04\x93\x3b\x3a\x8c\x11\x59\xfd\xec\xb2\x21\xaf\xe4\x16\x88\x83\xd2\xd0\x0a\xc1\xf1\x5f\xca\x46\x14\xfb\xd5\xe0\x5d\xe2\xe3\x7a\xd0\xfb\xad\x8b\x77\x48\xdd\xdb\xcf\x30\xb3\x9e\x80\x46\x6c\x61\xc7\x33\x41\x5e\x72\xb9\xf4\x2d\x5f\xad\x0b\xf3\x5f\x04\x1e\xb5\x63\x1e\xde\xd0\x03\x14\xc6\x6c\x48\x78\xb3\x51\x41\x6e\x5c\x6b\x90\x96\xf2\xa7\x08\x8a\x24\x38\x7e\x5d\x01\x49\xc5\x23\x73\x9f\x84\xf5\x02\xc7\x02\x03\x01\x00\x01"))
+
+     (CASE_S(QType::TLSA, "3 0 0 308201f43082015da003020102020900ac547c5557870ec7300d06092a864886f70d010105050030133111300f06035504030c087265632e74657374301e170d3133303531323139343830395a170d3133303631313139343830395a30133111300f06035504030c087265632e7465737430819f300d06092a864886f70d010101050003818d0030818902818100d282bb968dfdec0e5d13dfcc0a36ed73178581424e10a37c89d3014204933b3a8c1159fdecb221afe4168883d2d00ac1f15fca4614fbd5e05de2e37ad0fbad8b7748dddbcf30b39e80466c61c733415e72b9f42d5fad0bf35f041eb5631eded00314c66c4878b351416e5c6b9096f2a7088a24387e5d0149c523739f84f502c70203010001a350304e301d0603551d0e0416041473715bbfd9bc2b824112f858586f166aafb99482301f0603551d2304183016801473715bbfd9bc2b824112f858586f166aafb99482300c0603551d13040530030101ff300d06092a864886f70d0101050500038181005550f1d64139ab0e86c5b303fc69015d1676ca95931071ae41884656c71c116a38138ecf63054b350dc78983cb4a83288dbc81c5a659a56cc6843d5452c3e98449b94a0cf0c0cd7190c96caa5f0ee9a3bef7e75002be4a233673852bdf1a5fd306a7080eb4fead9b3ad162074b5f007e9156e220302dea8c700868a12577e7c4", "\x03\x00\x00\x30\x82\x01\xf4\x30\x82\x01\x5d\xa0\x03\x02\x01\x02\x02\x09\x00\xac\x54\x7c\x55\x57\x87\x0e\xc7\x30\x0d\x06\x09\x2a\x86\x48\x86\xf7\x0d\x01\x01\x05\x05\x00\x30\x13\x31\x11\x30\x0f\x06\x03\x55\x04\x03\x0c\x08\x72\x65\x63\x2e\x74\x65\x73\x74\x30\x1e\x17\x0d\x31\x33\x30\x35\x31\x32\x31\x39\x34\x38\x30\x39\x5a\x17\x0d\x31\x33\x30\x36\x31\x31\x31\x39\x34\x38\x30\x39\x5a\x30\x13\x31\x11\x30\x0f\x06\x03\x55\x04\x03\x0c\x08\x72\x65\x63\x2e\x74\x65\x73\x74\x30\x81\x9f\x30\x0d\x06\x09\x2a\x86\x48\x86\xf7\x0d\x01\x01\x01\x05\x00\x03\x81\x8d\x00\x30\x81\x89\x02\x81\x81\x00\xd2\x82\xbb\x96\x8d\xfd\xec\x0e\x5d\x13\xdf\xcc\x0a\x36\xed\x73\x17\x85\x81\x42\x4e\x10\xa3\x7c\x89\xd3\x01\x42\x04\x93\x3b\x3a\x8c\x11\x59\xfd\xec\xb2\x21\xaf\xe4\x16\x88\x83\xd2\xd0\x0a\xc1\xf1\x5f\xca\x46\x14\xfb\xd5\xe0\x5d\xe2\xe3\x7a\xd0\xfb\xad\x8b\x77\x48\xdd\xdb\xcf\x30\xb3\x9e\x80\x46\x6c\x61\xc7\x33\x41\x5e\x72\xb9\xf4\x2d\x5f\xad\x0b\xf3\x5f\x04\x1e\xb5\x63\x1e\xde\xd0\x03\x14\xc6\x6c\x48\x78\xb3\x51\x41\x6e\x5c\x6b\x90\x96\xf2\xa7\x08\x8a\x24\x38\x7e\x5d\x01\x49\xc5\x23\x73\x9f\x84\xf5\x02\xc7\x02\x03\x01\x00\x01\xa3\x50\x30\x4e\x30\x1d\x06\x03\x55\x1d\x0e\x04\x16\x04\x14\x73\x71\x5b\xbf\xd9\xbc\x2b\x82\x41\x12\xf8\x58\x58\x6f\x16\x6a\xaf\xb9\x94\x82\x30\x1f\x06\x03\x55\x1d\x23\x04\x18\x30\x16\x80\x14\x73\x71\x5b\xbf\xd9\xbc\x2b\x82\x41\x12\xf8\x58\x58\x6f\x16\x6a\xaf\xb9\x94\x82\x30\x0c\x06\x03\x55\x1d\x13\x04\x05\x30\x03\x01\x01\xff\x30\x0d\x06\x09\x2a\x86\x48\x86\xf7\x0d\x01\x01\x05\x05\x00\x03\x81\x81\x00\x55\x50\xf1\xd6\x41\x39\xab\x0e\x86\xc5\xb3\x03\xfc\x69\x01\x5d\x16\x76\xca\x95\x93\x10\x71\xae\x41\x88\x46\x56\xc7\x1c\x11\x6a\x38\x13\x8e\xcf\x63\x05\x4b\x35\x0d\xc7\x89\x83\xcb\x4a\x83\x28\x8d\xbc\x81\xc5\xa6\x59\xa5\x6c\xc6\x84\x3d\x54\x52\xc3\xe9\x84\x49\xb9\x4a\x0c\xf0\xc0\xcd\x71\x90\xc9\x6c\xaa\x5f\x0e\xe9\xa3\xbe\xf7\xe7\x50\x02\xbe\x4a\x23\x36\x73\x85\x2b\xdf\x1a\x5f\xd3\x06\xa7\x08\x0e\xb4\xfe\xad\x9b\x3a\xd1\x62\x07\x4b\x5f\x00\x7e\x91\x56\xe2\x20\x30\x2d\xea\x8c\x70\x08\x68\xa1\x25\x77\xe7\xc4"))
+     (CASE_S(QType::TLSA, "3 1 0 30819f300d06092a864886f70d010101050003818d0030818902818100d282bb968dfdec0e5d13dfcc0a36ed73178581424e10a37c89d3014204933b3a8c1159fdecb221afe4168883d2d00ac1f15fca4614fbd5e05de2e37ad0fbad8b7748dddbcf30b39e80466c61c733415e72b9f42d5fad0bf35f041eb5631eded00314c66c4878b351416e5c6b9096f2a7088a24387e5d0149c523739f84f502c70203010001", "\x03\x01\x00\x30\x81\x9f\x30\x0d\x06\x09\x2a\x86\x48\x86\xf7\x0d\x01\x01\x01\x05\x00\x03\x81\x8d\x00\x30\x81\x89\x02\x81\x81\x00\xd2\x82\xbb\x96\x8d\xfd\xec\x0e\x5d\x13\xdf\xcc\x0a\x36\xed\x73\x17\x85\x81\x42\x4e\x10\xa3\x7c\x89\xd3\x01\x42\x04\x93\x3b\x3a\x8c\x11\x59\xfd\xec\xb2\x21\xaf\xe4\x16\x88\x83\xd2\xd0\x0a\xc1\xf1\x5f\xca\x46\x14\xfb\xd5\xe0\x5d\xe2\xe3\x7a\xd0\xfb\xad\x8b\x77\x48\xdd\xdb\xcf\x30\xb3\x9e\x80\x46\x6c\x61\xc7\x33\x41\x5e\x72\xb9\xf4\x2d\x5f\xad\x0b\xf3\x5f\x04\x1e\xb5\x63\x1e\xde\xd0\x03\x14\xc6\x6c\x48\x78\xb3\x51\x41\x6e\x5c\x6b\x90\x96\xf2\xa7\x08\x8a\x24\x38\x7e\x5d\x01\x49\xc5\x23\x73\x9f\x84\xf5\x02\xc7\x02\x03\x01\x00\x01"))
+
+     (CASE_S(QType::SMIMEA, "0 0 0 308201f43082015da003020102020900ac547c5557870ec7300d06092a864886f70d010105050030133111300f06035504030c087265632e74657374301e170d3133303531323139343830395a170d3133303631313139343830395a30133111300f06035504030c087265632e7465737430819f300d06092a864886f70d010101050003818d0030818902818100d282bb968dfdec0e5d13dfcc0a36ed73178581424e10a37c89d3014204933b3a8c1159fdecb221afe4168883d2d00ac1f15fca4614fbd5e05de2e37ad0fbad8b7748dddbcf30b39e80466c61c733415e72b9f42d5fad0bf35f041eb5631eded00314c66c4878b351416e5c6b9096f2a7088a24387e5d0149c523739f84f502c70203010001a350304e301d0603551d0e0416041473715bbfd9bc2b824112f858586f166aafb99482301f0603551d2304183016801473715bbfd9bc2b824112f858586f166aafb99482300c0603551d13040530030101ff300d06092a864886f70d0101050500038181005550f1d64139ab0e86c5b303fc69015d1676ca95931071ae41884656c71c116a38138ecf63054b350dc78983cb4a83288dbc81c5a659a56cc6843d5452c3e98449b94a0cf0c0cd7190c96caa5f0ee9a3bef7e75002be4a233673852bdf1a5fd306a7080eb4fead9b3ad162074b5f007e9156e220302dea8c700868a12577e7c4", "\x00\x00\x00\x30\x82\x01\xf4\x30\x82\x01\x5d\xa0\x03\x02\x01\x02\x02\x09\x00\xac\x54\x7c\x55\x57\x87\x0e\xc7\x30\x0d\x06\x09\x2a\x86\x48\x86\xf7\x0d\x01\x01\x05\x05\x00\x30\x13\x31\x11\x30\x0f\x06\x03\x55\x04\x03\x0c\x08\x72\x65\x63\x2e\x74\x65\x73\x74\x30\x1e\x17\x0d\x31\x33\x30\x35\x31\x32\x31\x39\x34\x38\x30\x39\x5a\x17\x0d\x31\x33\x30\x36\x31\x31\x31\x39\x34\x38\x30\x39\x5a\x30\x13\x31\x11\x30\x0f\x06\x03\x55\x04\x03\x0c\x08\x72\x65\x63\x2e\x74\x65\x73\x74\x30\x81\x9f\x30\x0d\x06\x09\x2a\x86\x48\x86\xf7\x0d\x01\x01\x01\x05\x00\x03\x81\x8d\x00\x30\x81\x89\x02\x81\x81\x00\xd2\x82\xbb\x96\x8d\xfd\xec\x0e\x5d\x13\xdf\xcc\x0a\x36\xed\x73\x17\x85\x81\x42\x4e\x10\xa3\x7c\x89\xd3\x01\x42\x04\x93\x3b\x3a\x8c\x11\x59\xfd\xec\xb2\x21\xaf\xe4\x16\x88\x83\xd2\xd0\x0a\xc1\xf1\x5f\xca\x46\x14\xfb\xd5\xe0\x5d\xe2\xe3\x7a\xd0\xfb\xad\x8b\x77\x48\xdd\xdb\xcf\x30\xb3\x9e\x80\x46\x6c\x61\xc7\x33\x41\x5e\x72\xb9\xf4\x2d\x5f\xad\x0b\xf3\x5f\x04\x1e\xb5\x63\x1e\xde\xd0\x03\x14\xc6\x6c\x48\x78\xb3\x51\x41\x6e\x5c\x6b\x90\x96\xf2\xa7\x08\x8a\x24\x38\x7e\x5d\x01\x49\xc5\x23\x73\x9f\x84\xf5\x02\xc7\x02\x03\x01\x00\x01\xa3\x50\x30\x4e\x30\x1d\x06\x03\x55\x1d\x0e\x04\x16\x04\x14\x73\x71\x5b\xbf\xd9\xbc\x2b\x82\x41\x12\xf8\x58\x58\x6f\x16\x6a\xaf\xb9\x94\x82\x30\x1f\x06\x03\x55\x1d\x23\x04\x18\x30\x16\x80\x14\x73\x71\x5b\xbf\xd9\xbc\x2b\x82\x41\x12\xf8\x58\x58\x6f\x16\x6a\xaf\xb9\x94\x82\x30\x0c\x06\x03\x55\x1d\x13\x04\x05\x30\x03\x01\x01\xff\x30\x0d\x06\x09\x2a\x86\x48\x86\xf7\x0d\x01\x01\x05\x05\x00\x03\x81\x81\x00\x55\x50\xf1\xd6\x41\x39\xab\x0e\x86\xc5\xb3\x03\xfc\x69\x01\x5d\x16\x76\xca\x95\x93\x10\x71\xae\x41\x88\x46\x56\xc7\x1c\x11\x6a\x38\x13\x8e\xcf\x63\x05\x4b\x35\x0d\xc7\x89\x83\xcb\x4a\x83\x28\x8d\xbc\x81\xc5\xa6\x59\xa5\x6c\xc6\x84\x3d\x54\x52\xc3\xe9\x84\x49\xb9\x4a\x0c\xf0\xc0\xcd\x71\x90\xc9\x6c\xaa\x5f\x0e\xe9\xa3\xbe\xf7\xe7\x50\x02\xbe\x4a\x23\x36\x73\x85\x2b\xdf\x1a\x5f\xd3\x06\xa7\x08\x0e\xb4\xfe\xad\x9b\x3a\xd1\x62\x07\x4b\x5f\x00\x7e\x91\x56\xe2\x20\x30\x2d\xea\x8c\x70\x08\x68\xa1\x25\x77\xe7\xc4"))
+     (CASE_S(QType::SMIMEA, "0 1 0 30819f300d06092a864886f70d010101050003818d0030818902818100d282bb968dfdec0e5d13dfcc0a36ed73178581424e10a37c89d3014204933b3a8c1159fdecb221afe4168883d2d00ac1f15fca4614fbd5e05de2e37ad0fbad8b7748dddbcf30b39e80466c61c733415e72b9f42d5fad0bf35f041eb5631eded00314c66c4878b351416e5c6b9096f2a7088a24387e5d0149c523739f84f502c70203010001", "\x00\x01\x00\x30\x81\x9f\x30\x0d\x06\x09\x2a\x86\x48\x86\xf7\x0d\x01\x01\x01\x05\x00\x03\x81\x8d\x00\x30\x81\x89\x02\x81\x81\x00\xd2\x82\xbb\x96\x8d\xfd\xec\x0e\x5d\x13\xdf\xcc\x0a\x36\xed\x73\x17\x85\x81\x42\x4e\x10\xa3\x7c\x89\xd3\x01\x42\x04\x93\x3b\x3a\x8c\x11\x59\xfd\xec\xb2\x21\xaf\xe4\x16\x88\x83\xd2\xd0\x0a\xc1\xf1\x5f\xca\x46\x14\xfb\xd5\xe0\x5d\xe2\xe3\x7a\xd0\xfb\xad\x8b\x77\x48\xdd\xdb\xcf\x30\xb3\x9e\x80\x46\x6c\x61\xc7\x33\x41\x5e\x72\xb9\xf4\x2d\x5f\xad\x0b\xf3\x5f\x04\x1e\xb5\x63\x1e\xde\xd0\x03\x14\xc6\x6c\x48\x78\xb3\x51\x41\x6e\x5c\x6b\x90\x96\xf2\xa7\x08\x8a\x24\x38\x7e\x5d\x01\x49\xc5\x23\x73\x9f\x84\xf5\x02\xc7\x02\x03\x01\x00\x01"))
+     (CASE_S(QType::SMIMEA, "1 0 0 308201f43082015da003020102020900ac547c5557870ec7300d06092a864886f70d010105050030133111300f06035504030c087265632e74657374301e170d3133303531323139343830395a170d3133303631313139343830395a30133111300f06035504030c087265632e7465737430819f300d06092a864886f70d010101050003818d0030818902818100d282bb968dfdec0e5d13dfcc0a36ed73178581424e10a37c89d3014204933b3a8c1159fdecb221afe4168883d2d00ac1f15fca4614fbd5e05de2e37ad0fbad8b7748dddbcf30b39e80466c61c733415e72b9f42d5fad0bf35f041eb5631eded00314c66c4878b351416e5c6b9096f2a7088a24387e5d0149c523739f84f502c70203010001a350304e301d0603551d0e0416041473715bbfd9bc2b824112f858586f166aafb99482301f0603551d2304183016801473715bbfd9bc2b824112f858586f166aafb99482300c0603551d13040530030101ff300d06092a864886f70d0101050500038181005550f1d64139ab0e86c5b303fc69015d1676ca95931071ae41884656c71c116a38138ecf63054b350dc78983cb4a83288dbc81c5a659a56cc6843d5452c3e98449b94a0cf0c0cd7190c96caa5f0ee9a3bef7e75002be4a233673852bdf1a5fd306a7080eb4fead9b3ad162074b5f007e9156e220302dea8c700868a12577e7c4", "\x01\x00\x00\x30\x82\x01\xf4\x30\x82\x01\x5d\xa0\x03\x02\x01\x02\x02\x09\x00\xac\x54\x7c\x55\x57\x87\x0e\xc7\x30\x0d\x06\x09\x2a\x86\x48\x86\xf7\x0d\x01\x01\x05\x05\x00\x30\x13\x31\x11\x30\x0f\x06\x03\x55\x04\x03\x0c\x08\x72\x65\x63\x2e\x74\x65\x73\x74\x30\x1e\x17\x0d\x31\x33\x30\x35\x31\x32\x31\x39\x34\x38\x30\x39\x5a\x17\x0d\x31\x33\x30\x36\x31\x31\x31\x39\x34\x38\x30\x39\x5a\x30\x13\x31\x11\x30\x0f\x06\x03\x55\x04\x03\x0c\x08\x72\x65\x63\x2e\x74\x65\x73\x74\x30\x81\x9f\x30\x0d\x06\x09\x2a\x86\x48\x86\xf7\x0d\x01\x01\x01\x05\x00\x03\x81\x8d\x00\x30\x81\x89\x02\x81\x81\x00\xd2\x82\xbb\x96\x8d\xfd\xec\x0e\x5d\x13\xdf\xcc\x0a\x36\xed\x73\x17\x85\x81\x42\x4e\x10\xa3\x7c\x89\xd3\x01\x42\x04\x93\x3b\x3a\x8c\x11\x59\xfd\xec\xb2\x21\xaf\xe4\x16\x88\x83\xd2\xd0\x0a\xc1\xf1\x5f\xca\x46\x14\xfb\xd5\xe0\x5d\xe2\xe3\x7a\xd0\xfb\xad\x8b\x77\x48\xdd\xdb\xcf\x30\xb3\x9e\x80\x46\x6c\x61\xc7\x33\x41\x5e\x72\xb9\xf4\x2d\x5f\xad\x0b\xf3\x5f\x04\x1e\xb5\x63\x1e\xde\xd0\x03\x14\xc6\x6c\x48\x78\xb3\x51\x41\x6e\x5c\x6b\x90\x96\xf2\xa7\x08\x8a\x24\x38\x7e\x5d\x01\x49\xc5\x23\x73\x9f\x84\xf5\x02\xc7\x02\x03\x01\x00\x01\xa3\x50\x30\x4e\x30\x1d\x06\x03\x55\x1d\x0e\x04\x16\x04\x14\x73\x71\x5b\xbf\xd9\xbc\x2b\x82\x41\x12\xf8\x58\x58\x6f\x16\x6a\xaf\xb9\x94\x82\x30\x1f\x06\x03\x55\x1d\x23\x04\x18\x30\x16\x80\x14\x73\x71\x5b\xbf\xd9\xbc\x2b\x82\x41\x12\xf8\x58\x58\x6f\x16\x6a\xaf\xb9\x94\x82\x30\x0c\x06\x03\x55\x1d\x13\x04\x05\x30\x03\x01\x01\xff\x30\x0d\x06\x09\x2a\x86\x48\x86\xf7\x0d\x01\x01\x05\x05\x00\x03\x81\x81\x00\x55\x50\xf1\xd6\x41\x39\xab\x0e\x86\xc5\xb3\x03\xfc\x69\x01\x5d\x16\x76\xca\x95\x93\x10\x71\xae\x41\x88\x46\x56\xc7\x1c\x11\x6a\x38\x13\x8e\xcf\x63\x05\x4b\x35\x0d\xc7\x89\x83\xcb\x4a\x83\x28\x8d\xbc\x81\xc5\xa6\x59\xa5\x6c\xc6\x84\x3d\x54\x52\xc3\xe9\x84\x49\xb9\x4a\x0c\xf0\xc0\xcd\x71\x90\xc9\x6c\xaa\x5f\x0e\xe9\xa3\xbe\xf7\xe7\x50\x02\xbe\x4a\x23\x36\x73\x85\x2b\xdf\x1a\x5f\xd3\x06\xa7\x08\x0e\xb4\xfe\xad\x9b\x3a\xd1\x62\x07\x4b\x5f\x00\x7e\x91\x56\xe2\x20\x30\x2d\xea\x8c\x70\x08\x68\xa1\x25\x77\xe7\xc4"))
+     (CASE_S(QType::SMIMEA, "1 1 0 30819f300d06092a864886f70d010101050003818d0030818902818100d282bb968dfdec0e5d13dfcc0a36ed73178581424e10a37c89d3014204933b3a8c1159fdecb221afe4168883d2d00ac1f15fca4614fbd5e05de2e37ad0fbad8b7748dddbcf30b39e80466c61c733415e72b9f42d5fad0bf35f041eb5631eded00314c66c4878b351416e5c6b9096f2a7088a24387e5d0149c523739f84f502c70203010001", "\x01\x01\x00\x30\x81\x9f\x30\x0d\x06\x09\x2a\x86\x48\x86\xf7\x0d\x01\x01\x01\x05\x00\x03\x81\x8d\x00\x30\x81\x89\x02\x81\x81\x00\xd2\x82\xbb\x96\x8d\xfd\xec\x0e\x5d\x13\xdf\xcc\x0a\x36\xed\x73\x17\x85\x81\x42\x4e\x10\xa3\x7c\x89\xd3\x01\x42\x04\x93\x3b\x3a\x8c\x11\x59\xfd\xec\xb2\x21\xaf\xe4\x16\x88\x83\xd2\xd0\x0a\xc1\xf1\x5f\xca\x46\x14\xfb\xd5\xe0\x5d\xe2\xe3\x7a\xd0\xfb\xad\x8b\x77\x48\xdd\xdb\xcf\x30\xb3\x9e\x80\x46\x6c\x61\xc7\x33\x41\x5e\x72\xb9\xf4\x2d\x5f\xad\x0b\xf3\x5f\x04\x1e\xb5\x63\x1e\xde\xd0\x03\x14\xc6\x6c\x48\x78\xb3\x51\x41\x6e\x5c\x6b\x90\x96\xf2\xa7\x08\x8a\x24\x38\x7e\x5d\x01\x49\xc5\x23\x73\x9f\x84\xf5\x02\xc7\x02\x03\x01\x00\x01"))
+     (CASE_S(QType::SMIMEA, "1 0 1 6acea2f68b03d9efe97a967e137aca6ac3a89490d532d87806d9e9c257668453", "\x01\x00\x01\x6a\xce\xa2\xf6\x8b\x03\xd9\xef\xe9\x7a\x96\x7e\x13\x7a\xca\x6a\xc3\xa8\x94\x90\xd5\x32\xd8\x78\x06\xd9\xe9\xc2\x57\x66\x84\x53"))
+     (CASE_S(QType::SMIMEA, "1 0 2 e6dce237992803488d11d828b7728deddd4577de73d7d078338c8a45880beddff98e076a28bf8e3068da8e73667b802a721c95d7323b038c60200a430cb6fbd4", "\x01\x00\x02\xe6\xdc\xe2\x37\x99\x28\x03\x48\x8d\x11\xd8\x28\xb7\x72\x8d\xed\xdd\x45\x77\xde\x73\xd7\xd0\x78\x33\x8c\x8a\x45\x88\x0b\xed\xdf\xf9\x8e\x07\x6a\x28\xbf\x8e\x30\x68\xda\x8e\x73\x66\x7b\x80\x2a\x72\x1c\x95\xd7\x32\x3b\x03\x8c\x60\x20\x0a\x43\x0c\xb6\xfb\xd4"))
+     (CASE_S(QType::SMIMEA, "2 0 0 308201f43082015da003020102020900ac547c5557870ec7300d06092a864886f70d010105050030133111300f06035504030c087265632e74657374301e170d3133303531323139343830395a170d3133303631313139343830395a30133111300f06035504030c087265632e7465737430819f300d06092a864886f70d010101050003818d0030818902818100d282bb968dfdec0e5d13dfcc0a36ed73178581424e10a37c89d3014204933b3a8c1159fdecb221afe4168883d2d00ac1f15fca4614fbd5e05de2e37ad0fbad8b7748dddbcf30b39e80466c61c733415e72b9f42d5fad0bf35f041eb5631eded00314c66c4878b351416e5c6b9096f2a7088a24387e5d0149c523739f84f502c70203010001a350304e301d0603551d0e0416041473715bbfd9bc2b824112f858586f166aafb99482301f0603551d2304183016801473715bbfd9bc2b824112f858586f166aafb99482300c0603551d13040530030101ff300d06092a864886f70d0101050500038181005550f1d64139ab0e86c5b303fc69015d1676ca95931071ae41884656c71c116a38138ecf63054b350dc78983cb4a83288dbc81c5a659a56cc6843d5452c3e98449b94a0cf0c0cd7190c96caa5f0ee9a3bef7e75002be4a233673852bdf1a5fd306a7080eb4fead9b3ad162074b5f007e9156e220302dea8c700868a12577e7c4", "\x02\x00\x00\x30\x82\x01\xf4\x30\x82\x01\x5d\xa0\x03\x02\x01\x02\x02\x09\x00\xac\x54\x7c\x55\x57\x87\x0e\xc7\x30\x0d\x06\x09\x2a\x86\x48\x86\xf7\x0d\x01\x01\x05\x05\x00\x30\x13\x31\x11\x30\x0f\x06\x03\x55\x04\x03\x0c\x08\x72\x65\x63\x2e\x74\x65\x73\x74\x30\x1e\x17\x0d\x31\x33\x30\x35\x31\x32\x31\x39\x34\x38\x30\x39\x5a\x17\x0d\x31\x33\x30\x36\x31\x31\x31\x39\x34\x38\x30\x39\x5a\x30\x13\x31\x11\x30\x0f\x06\x03\x55\x04\x03\x0c\x08\x72\x65\x63\x2e\x74\x65\x73\x74\x30\x81\x9f\x30\x0d\x06\x09\x2a\x86\x48\x86\xf7\x0d\x01\x01\x01\x05\x00\x03\x81\x8d\x00\x30\x81\x89\x02\x81\x81\x00\xd2\x82\xbb\x96\x8d\xfd\xec\x0e\x5d\x13\xdf\xcc\x0a\x36\xed\x73\x17\x85\x81\x42\x4e\x10\xa3\x7c\x89\xd3\x01\x42\x04\x93\x3b\x3a\x8c\x11\x59\xfd\xec\xb2\x21\xaf\xe4\x16\x88\x83\xd2\xd0\x0a\xc1\xf1\x5f\xca\x46\x14\xfb\xd5\xe0\x5d\xe2\xe3\x7a\xd0\xfb\xad\x8b\x77\x48\xdd\xdb\xcf\x30\xb3\x9e\x80\x46\x6c\x61\xc7\x33\x41\x5e\x72\xb9\xf4\x2d\x5f\xad\x0b\xf3\x5f\x04\x1e\xb5\x63\x1e\xde\xd0\x03\x14\xc6\x6c\x48\x78\xb3\x51\x41\x6e\x5c\x6b\x90\x96\xf2\xa7\x08\x8a\x24\x38\x7e\x5d\x01\x49\xc5\x23\x73\x9f\x84\xf5\x02\xc7\x02\x03\x01\x00\x01\xa3\x50\x30\x4e\x30\x1d\x06\x03\x55\x1d\x0e\x04\x16\x04\x14\x73\x71\x5b\xbf\xd9\xbc\x2b\x82\x41\x12\xf8\x58\x58\x6f\x16\x6a\xaf\xb9\x94\x82\x30\x1f\x06\x03\x55\x1d\x23\x04\x18\x30\x16\x80\x14\x73\x71\x5b\xbf\xd9\xbc\x2b\x82\x41\x12\xf8\x58\x58\x6f\x16\x6a\xaf\xb9\x94\x82\x30\x0c\x06\x03\x55\x1d\x13\x04\x05\x30\x03\x01\x01\xff\x30\x0d\x06\x09\x2a\x86\x48\x86\xf7\x0d\x01\x01\x05\x05\x00\x03\x81\x81\x00\x55\x50\xf1\xd6\x41\x39\xab\x0e\x86\xc5\xb3\x03\xfc\x69\x01\x5d\x16\x76\xca\x95\x93\x10\x71\xae\x41\x88\x46\x56\xc7\x1c\x11\x6a\x38\x13\x8e\xcf\x63\x05\x4b\x35\x0d\xc7\x89\x83\xcb\x4a\x83\x28\x8d\xbc\x81\xc5\xa6\x59\xa5\x6c\xc6\x84\x3d\x54\x52\xc3\xe9\x84\x49\xb9\x4a\x0c\xf0\xc0\xcd\x71\x90\xc9\x6c\xaa\x5f\x0e\xe9\xa3\xbe\xf7\xe7\x50\x02\xbe\x4a\x23\x36\x73\x85\x2b\xdf\x1a\x5f\xd3\x06\xa7\x08\x0e\xb4\xfe\xad\x9b\x3a\xd1\x62\x07\x4b\x5f\x00\x7e\x91\x56\xe2\x20\x30\x2d\xea\x8c\x70\x08\x68\xa1\x25\x77\xe7\xc4"))
+     (CASE_S(QType::SMIMEA, "2 1 0 30819f300d06092a864886f70d010101050003818d0030818902818100d282bb968dfdec0e5d13dfcc0a36ed73178581424e10a37c89d3014204933b3a8c1159fdecb221afe4168883d2d00ac1f15fca4614fbd5e05de2e37ad0fbad8b7748dddbcf30b39e80466c61c733415e72b9f42d5fad0bf35f041eb5631eded00314c66c4878b351416e5c6b9096f2a7088a24387e5d0149c523739f84f502c70203010001", "\x02\x01\x00\x30\x81\x9f\x30\x0d\x06\x09\x2a\x86\x48\x86\xf7\x0d\x01\x01\x01\x05\x00\x03\x81\x8d\x00\x30\x81\x89\x02\x81\x81\x00\xd2\x82\xbb\x96\x8d\xfd\xec\x0e\x5d\x13\xdf\xcc\x0a\x36\xed\x73\x17\x85\x81\x42\x4e\x10\xa3\x7c\x89\xd3\x01\x42\x04\x93\x3b\x3a\x8c\x11\x59\xfd\xec\xb2\x21\xaf\xe4\x16\x88\x83\xd2\xd0\x0a\xc1\xf1\x5f\xca\x46\x14\xfb\xd5\xe0\x5d\xe2\xe3\x7a\xd0\xfb\xad\x8b\x77\x48\xdd\xdb\xcf\x30\xb3\x9e\x80\x46\x6c\x61\xc7\x33\x41\x5e\x72\xb9\xf4\x2d\x5f\xad\x0b\xf3\x5f\x04\x1e\xb5\x63\x1e\xde\xd0\x03\x14\xc6\x6c\x48\x78\xb3\x51\x41\x6e\x5c\x6b\x90\x96\xf2\xa7\x08\x8a\x24\x38\x7e\x5d\x01\x49\xc5\x23\x73\x9f\x84\xf5\x02\xc7\x02\x03\x01\x00\x01"))
+     (CASE_S(QType::SMIMEA, "3 0 0 308201f43082015da003020102020900ac547c5557870ec7300d06092a864886f70d010105050030133111300f06035504030c087265632e74657374301e170d3133303531323139343830395a170d3133303631313139343830395a30133111300f06035504030c087265632e7465737430819f300d06092a864886f70d010101050003818d0030818902818100d282bb968dfdec0e5d13dfcc0a36ed73178581424e10a37c89d3014204933b3a8c1159fdecb221afe4168883d2d00ac1f15fca4614fbd5e05de2e37ad0fbad8b7748dddbcf30b39e80466c61c733415e72b9f42d5fad0bf35f041eb5631eded00314c66c4878b351416e5c6b9096f2a7088a24387e5d0149c523739f84f502c70203010001a350304e301d0603551d0e0416041473715bbfd9bc2b824112f858586f166aafb99482301f0603551d2304183016801473715bbfd9bc2b824112f858586f166aafb99482300c0603551d13040530030101ff300d06092a864886f70d0101050500038181005550f1d64139ab0e86c5b303fc69015d1676ca95931071ae41884656c71c116a38138ecf63054b350dc78983cb4a83288dbc81c5a659a56cc6843d5452c3e98449b94a0cf0c0cd7190c96caa5f0ee9a3bef7e75002be4a233673852bdf1a5fd306a7080eb4fead9b3ad162074b5f007e9156e220302dea8c700868a12577e7c4", "\x03\x00\x00\x30\x82\x01\xf4\x30\x82\x01\x5d\xa0\x03\x02\x01\x02\x02\x09\x00\xac\x54\x7c\x55\x57\x87\x0e\xc7\x30\x0d\x06\x09\x2a\x86\x48\x86\xf7\x0d\x01\x01\x05\x05\x00\x30\x13\x31\x11\x30\x0f\x06\x03\x55\x04\x03\x0c\x08\x72\x65\x63\x2e\x74\x65\x73\x74\x30\x1e\x17\x0d\x31\x33\x30\x35\x31\x32\x31\x39\x34\x38\x30\x39\x5a\x17\x0d\x31\x33\x30\x36\x31\x31\x31\x39\x34\x38\x30\x39\x5a\x30\x13\x31\x11\x30\x0f\x06\x03\x55\x04\x03\x0c\x08\x72\x65\x63\x2e\x74\x65\x73\x74\x30\x81\x9f\x30\x0d\x06\x09\x2a\x86\x48\x86\xf7\x0d\x01\x01\x01\x05\x00\x03\x81\x8d\x00\x30\x81\x89\x02\x81\x81\x00\xd2\x82\xbb\x96\x8d\xfd\xec\x0e\x5d\x13\xdf\xcc\x0a\x36\xed\x73\x17\x85\x81\x42\x4e\x10\xa3\x7c\x89\xd3\x01\x42\x04\x93\x3b\x3a\x8c\x11\x59\xfd\xec\xb2\x21\xaf\xe4\x16\x88\x83\xd2\xd0\x0a\xc1\xf1\x5f\xca\x46\x14\xfb\xd5\xe0\x5d\xe2\xe3\x7a\xd0\xfb\xad\x8b\x77\x48\xdd\xdb\xcf\x30\xb3\x9e\x80\x46\x6c\x61\xc7\x33\x41\x5e\x72\xb9\xf4\x2d\x5f\xad\x0b\xf3\x5f\x04\x1e\xb5\x63\x1e\xde\xd0\x03\x14\xc6\x6c\x48\x78\xb3\x51\x41\x6e\x5c\x6b\x90\x96\xf2\xa7\x08\x8a\x24\x38\x7e\x5d\x01\x49\xc5\x23\x73\x9f\x84\xf5\x02\xc7\x02\x03\x01\x00\x01\xa3\x50\x30\x4e\x30\x1d\x06\x03\x55\x1d\x0e\x04\x16\x04\x14\x73\x71\x5b\xbf\xd9\xbc\x2b\x82\x41\x12\xf8\x58\x58\x6f\x16\x6a\xaf\xb9\x94\x82\x30\x1f\x06\x03\x55\x1d\x23\x04\x18\x30\x16\x80\x14\x73\x71\x5b\xbf\xd9\xbc\x2b\x82\x41\x12\xf8\x58\x58\x6f\x16\x6a\xaf\xb9\x94\x82\x30\x0c\x06\x03\x55\x1d\x13\x04\x05\x30\x03\x01\x01\xff\x30\x0d\x06\x09\x2a\x86\x48\x86\xf7\x0d\x01\x01\x05\x05\x00\x03\x81\x81\x00\x55\x50\xf1\xd6\x41\x39\xab\x0e\x86\xc5\xb3\x03\xfc\x69\x01\x5d\x16\x76\xca\x95\x93\x10\x71\xae\x41\x88\x46\x56\xc7\x1c\x11\x6a\x38\x13\x8e\xcf\x63\x05\x4b\x35\x0d\xc7\x89\x83\xcb\x4a\x83\x28\x8d\xbc\x81\xc5\xa6\x59\xa5\x6c\xc6\x84\x3d\x54\x52\xc3\xe9\x84\x49\xb9\x4a\x0c\xf0\xc0\xcd\x71\x90\xc9\x6c\xaa\x5f\x0e\xe9\xa3\xbe\xf7\xe7\x50\x02\xbe\x4a\x23\x36\x73\x85\x2b\xdf\x1a\x5f\xd3\x06\xa7\x08\x0e\xb4\xfe\xad\x9b\x3a\xd1\x62\x07\x4b\x5f\x00\x7e\x91\x56\xe2\x20\x30\x2d\xea\x8c\x70\x08\x68\xa1\x25\x77\xe7\xc4"))
+     (CASE_S(QType::SMIMEA, "3 1 0 30819f300d06092a864886f70d010101050003818d0030818902818100d282bb968dfdec0e5d13dfcc0a36ed73178581424e10a37c89d3014204933b3a8c1159fdecb221afe4168883d2d00ac1f15fca4614fbd5e05de2e37ad0fbad8b7748dddbcf30b39e80466c61c733415e72b9f42d5fad0bf35f041eb5631eded00314c66c4878b351416e5c6b9096f2a7088a24387e5d0149c523739f84f502c70203010001", "\x03\x01\x00\x30\x81\x9f\x30\x0d\x06\x09\x2a\x86\x48\x86\xf7\x0d\x01\x01\x01\x05\x00\x03\x81\x8d\x00\x30\x81\x89\x02\x81\x81\x00\xd2\x82\xbb\x96\x8d\xfd\xec\x0e\x5d\x13\xdf\xcc\x0a\x36\xed\x73\x17\x85\x81\x42\x4e\x10\xa3\x7c\x89\xd3\x01\x42\x04\x93\x3b\x3a\x8c\x11\x59\xfd\xec\xb2\x21\xaf\xe4\x16\x88\x83\xd2\xd0\x0a\xc1\xf1\x5f\xca\x46\x14\xfb\xd5\xe0\x5d\xe2\xe3\x7a\xd0\xfb\xad\x8b\x77\x48\xdd\xdb\xcf\x30\xb3\x9e\x80\x46\x6c\x61\xc7\x33\x41\x5e\x72\xb9\xf4\x2d\x5f\xad\x0b\xf3\x5f\x04\x1e\xb5\x63\x1e\xde\xd0\x03\x14\xc6\x6c\x48\x78\xb3\x51\x41\x6e\x5c\x6b\x90\x96\xf2\xa7\x08\x8a\x24\x38\x7e\x5d\x01\x49\xc5\x23\x73\x9f\x84\xf5\x02\xc7\x02\x03\x01\x00\x01"))
+
+     (CASE_S(QType::OPENPGPKEY, "mQINBFUIXh0BEADNPlL6NpWEaR2KJx6p19scIVpsBIo7UqzCIzeFbRJaGDhn/HlQgcwAalcVNmWUX0ZQsrdn9CEfLWuFu9ON2o1TslYiwn+oSAlH2raFm2eyJTp/iM7IUUCte5jmf3d+L9rjVI7JjmMnbVo6SVY2KDDD72dULcg7IqYcCAN4CT+tPZP5y4cYf+DxRlpxhxvqqiGyAi6lAcJ24/8fJ4hsG0lS1vU12LWeWTHa5aRMM+x9kmv3GYdXG+FxFqZw52kZEnAscpC2ymbX+1YFCr8sjGYGde/D+5cLvuu4PGNZ4fkSeS+0yXve/s6u1mX6RkkF6SOGWuJfBJOGdWzYwber9kqgqpHTjpr8HOybzVroBijtTlB/tommIUd4BTk9Jv4fv2gA4UkC13UM9KBF1NnzUnKC+Js49O3mj0HZDoCrkWMnZyDsEmhMyQPU6YRFHWmB6OTKeD/Znk+b1uz+HIBgrbNuiG/A0c00Vnj7lR4p94oOuypI00XusLsJwPsjI4EgFGKdoRtM0spJhi+3gf88Vq0NENBaFVHLBGWVFaVrffurGcDZYUAdnvm8jSPCgBPfFxpZutexNkLjyaaXjDtga5/n5gSd/3RpWCvp9u3W5jcTNDZF4TORnOXUWHcot/+XmyH8/+cn8ydt0prOLGQ+FtdI+AWyMCXHen6aaZ1jeSLZqwARAQABtFpwZG5zIHJlZ3Jlc3Npb24gdGVzdGluZyBrZXkgKG9ubHkgZm9yIHRlc3RpbmcgdGhlIG9wZW5wZ3BrZXkgcnIpIDxyZWdyZXNzaW9uQHBvd2VyZG5zLm9yZz6JAjcEEwEIACEFAlUIXh0CGwMFCwkIBwIGFQgJCgsCBBYCAwECHgECF4AACgkQPr/KqoyK2Z7A6Q//YOBu8nwt+fguSo/vyCln0PqnTiBm4RvE2gPDUnsKuoXoP5F56XHBXKl9kEgmycht/nc7c7NRHzUhacM2RQau6CgNZE8KLaqDTKlEuc/ANtrnGGYG8gMId4TlzU5taLEA8yrHIHnwnMuDDpx1a0ETkbYCrj7CynqdhXCABqFjgRL7Qb37UnLPE7YdFt7fRGwZVLnb3GVZLKHurZ0TANvLdRVDST3f0lCcYMppPbHAvi2MIU71FPGkms++tj4gTltq0VRvrMNm1e5v4+hHZ++QN4sm4+DJGlo7l850gnMXc7c7GkRGtg8gV0h5k5jX5icdgxyvENTuBQ+QprkYTRh9uYzpoTQ+NYRZlgaJxxaDIv1K/kb3oPtnAEKJBC02IZbB0EiS3R5pxYXhUNoWV7ez2A4hX1L+tfvlgCAbbQ/cBLvqXgpgsf9x4ygSi52vQBy3twZyrtLsogxacxADfPcyleHtju/+lSku+Z6+W6OojA0kY2HlaMyQATJLIXd+6NE/tYy70RsU9Oq0OyVTjxh21SPLsExeSwSfciVSLn7IuKGIOV82MEHFhpo1Uhv+G52J8T1fI730sS4Tl5DekLaCz1pg/FmI/EQeAsYqm98uDAaFQcs6gDse8VYGmp2XYsoCW72as8ElKmMIbQ/xD7qxDORLmjCtVoyKH19+s6Pp61a5Ag0EVQheHQEQAMN6pcLJUhw9bfO5kqhLv4prt0AqVBUok6U8tIaEc9vDaasBcFHXgPsoOG97DXB6BdvsHuK/5uMVH5PNe58MLp08iCoIt0C0CbN3+D9Qbeg37AyKyFanB/CXq1tPKVCJc6BMNkO/BswnUsTTmlcd4GovpaJUOOZzblGUQBbhzRohhmOGfdsScGeeYME/yNFqzt1ZArV4va1hOLOUpNFv9TOy0ZVi/yDi+sYA9fCSZU9alWI/cbBct5I+3bh1l26umlZsYQm8uqnSgiQWpRm6UJO6xQbmUN9GzCYyKCmpzZRduqqjjtiF10W1yzioTfTtq2cvU6PdINYY8w2UuOjRd9gChtvGuduOIwqlRTYSaXX1dDoFe1vWqZzRm+pIDumO9eX5jMmzFXLDG2pD2l97zoSjVFf/pYoBasgX43e3V/aEk9PUgXbYFm2QxFMcIYSO9GEDMoE+QxoMXf1UjLxMCK5gD5iHL3Ff2zyXLzlTZE+fHPMLcAkzcp2u6pJ9xpAGekqqeqnISXZ2o8yXsqv8NVvl1zaSiSqU+kak9mIg/2+WC9W1qO2PeSLW2tiis980QnmyDOBg2oL01ITh/u+GTodEGwfRYJoNAJgUjcUMpWl0LuoG8lG6wukhA4QYFWpf2QPVgTR63VbpFgwCnUcSEPqHB0BRCsDHsd0k+/YSuPolABEBAAGJAh8EGAEIAAkFAlUIXh0CGwwACgkQPr/KqoyK2Z6zPA/+PkJTzP8kQw4GW0x2ZxXfOmkRVYpSEoHehf6y9YFN00+T8pb71RGItvuX5v6oPKPClOnIVg2WVHOq6Q3HsXEzl7oIbOtPE98WXHiVXud/djc54uHz9WjSPfy/idP7SMslo29BHR/K9nQkiGtayD57wdxgbLXObE3fA0gl4AsWl1EZzNcWVL4SIrvnBGpYIUGBcsTIiP3p09bu4Qf6HjJRXZlBuizigIgeO39l/G6tb6GA1cnbq4y6aCtQeXHLrnvak1jRqznlJWUqS1mQgOPF1MuOduHAvQbfMBQXAEfgOTzuH9PuKoGm7MePwTrU5GsOpNgS4LbvIRODJxYD+vIwA5BniijgfN9aj9KQVMURrd4Np7i0EVmj8P9FtNgYsEaDt7laGpNB9+9Y9heb6kNEulF7KI7y8CKikgvFGHHCyX2BCCbQBqi6wbEGq16qkTJmesYu9ig4v4xD/Q/cLJFziJLjEcWsL7hq7q2o6e7NL6hf5aTH0/bdeMXMqRzDCAFQ5Z+x0QUCgVonxzj+CuTD/LeOs/QHu/9emvm9EOMYY/X9vidLf58PT/AMqMiYbNWty6qY6k2LMw74Yd4+hO+Tjrk8MrqbCUs9h6ih9IOCo68JTWQQbgWSk2TAyd3U4OqTyBnHWr0HhHDRTOxyDbZUtXbk/r4Q4gTcAt+qjpswPyk=", "\x99\x02\x0d\x04\x55\x08\x5e\x1d\x01\x10\x00\xcd\x3e\x52\xfa\x36\x95\x84\x69\x1d\x8a\x27\x1e\xa9\xd7\xdb\x1c\x21\x5a\x6c\x04\x8a\x3b\x52\xac\xc2\x23\x37\x85\x6d\x12\x5a\x18\x38\x67\xfc\x79\x50\x81\xcc\x00\x6a\x57\x15\x36\x65\x94\x5f\x46\x50\xb2\xb7\x67\xf4\x21\x1f\x2d\x6b\x85\xbb\xd3\x8d\xda\x8d\x53\xb2\x56\x22\xc2\x7f\xa8\x48\x09\x47\xda\xb6\x85\x9b\x67\xb2\x25\x3a\x7f\x88\xce\xc8\x51\x40\xad\x7b\x98\xe6\x7f\x77\x7e\x2f\xda\xe3\x54\x8e\xc9\x8e\x63\x27\x6d\x5a\x3a\x49\x56\x36\x28\x30\xc3\xef\x67\x54\x2d\xc8\x3b\x22\xa6\x1c\x08\x03\x78\x09\x3f\xad\x3d\x93\xf9\xcb\x87\x18\x7f\xe0\xf1\x46\x5a\x71\x87\x1b\xea\xaa\x21\xb2\x02\x2e\xa5\x01\xc2\x76\xe3\xff\x1f\x27\x88\x6c\x1b\x49\x52\xd6\xf5\x35\xd8\xb5\x9e\x59\x31\xda\xe5\xa4\x4c\x33\xec\x7d\x92\x6b\xf7\x19\x87\x57\x1b\xe1\x71\x16\xa6\x70\xe7\x69\x19\x12\x70\x2c\x72\x90\xb6\xca\x66\xd7\xfb\x56\x05\x0a\xbf\x2c\x8c\x66\x06\x75\xef\xc3\xfb\x97\x0b\xbe\xeb\xb8\x3c\x63\x59\xe1\xf9\x12\x79\x2f\xb4\xc9\x7b\xde\xfe\xce\xae\xd6\x65\xfa\x46\x49\x05\xe9\x23\x86\x5a\xe2\x5f\x04\x93\x86\x75\x6c\xd8\xc1\xb7\xab\xf6\x4a\xa0\xaa\x91\xd3\x8e\x9a\xfc\x1c\xec\x9b\xcd\x5a\xe8\x06\x28\xed\x4e\x50\x7f\xb6\x89\xa6\x21\x47\x78\x05\x39\x3d\x26\xfe\x1f\xbf\x68\x00\xe1\x49\x02\xd7\x75\x0c\xf4\xa0\x45\xd4\xd9\xf3\x52\x72\x82\xf8\x9b\x38\xf4\xed\xe6\x8f\x41\xd9\x0e\x80\xab\x91\x63\x27\x67\x20\xec\x12\x68\x4c\xc9\x03\xd4\xe9\x84\x45\x1d\x69\x81\xe8\xe4\xca\x78\x3f\xd9\x9e\x4f\x9b\xd6\xec\xfe\x1c\x80\x60\xad\xb3\x6e\x88\x6f\xc0\xd1\xcd\x34\x56\x78\xfb\x95\x1e\x29\xf7\x8a\x0e\xbb\x2a\x48\xd3\x45\xee\xb0\xbb\x09\xc0\xfb\x23\x23\x81\x20\x14\x62\x9d\xa1\x1b\x4c\xd2\xca\x49\x86\x2f\xb7\x81\xff\x3c\x56\xad\x0d\x10\xd0\x5a\x15\x51\xcb\x04\x65\x95\x15\xa5\x6b\x7d\xfb\xab\x19\xc0\xd9\x61\x40\x1d\x9e\xf9\xbc\x8d\x23\xc2\x80\x13\xdf\x17\x1a\x59\xba\xd7\xb1\x36\x42\xe3\xc9\xa6\x97\x8c\x3b\x60\x6b\x9f\xe7\xe6\x04\x9d\xff\x74\x69\x58\x2b\xe9\xf6\xed\xd6\xe6\x37\x13\x34\x36\x45\xe1\x33\x91\x9c\xe5\xd4\x58\x77\x28\xb7\xff\x97\x9b\x21\xfc\xff\xe7\x27\xf3\x27\x6d\xd2\x9a\xce\x2c\x64\x3e\x16\xd7\x48\xf8\x05\xb2\x30\x25\xc7\x7a\x7e\x9a\x69\x9d\x63\x79\x22\xd9\xab\x00\x11\x01\x00\x01\xb4\x5a\x70\x64\x6e\x73\x20\x72\x65\x67\x72\x65\x73\x73\x69\x6f\x6e\x20\x74\x65\x73\x74\x69\x6e\x67\x20\x6b\x65\x79\x20\x28\x6f\x6e\x6c\x79\x20\x66\x6f\x72\x20\x74\x65\x73\x74\x69\x6e\x67\x20\x74\x68\x65\x20\x6f\x70\x65\x6e\x70\x67\x70\x6b\x65\x79\x20\x72\x72\x29\x20\x3c\x72\x65\x67\x72\x65\x73\x73\x69\x6f\x6e\x40\x70\x6f\x77\x65\x72\x64\x6e\x73\x2e\x6f\x72\x67\x3e\x89\x02\x37\x04\x13\x01\x08\x00\x21\x05\x02\x55\x08\x5e\x1d\x02\x1b\x03\x05\x0b\x09\x08\x07\x02\x06\x15\x08\x09\x0a\x0b\x02\x04\x16\x02\x03\x01\x02\x1e\x01\x02\x17\x80\x00\x0a\x09\x10\x3e\xbf\xca\xaa\x8c\x8a\xd9\x9e\xc0\xe9\x0f\xff\x60\xe0\x6e\xf2\x7c\x2d\xf9\xf8\x2e\x4a\x8f\xef\xc8\x29\x67\xd0\xfa\xa7\x4e\x20\x66\xe1\x1b\xc4\xda\x03\xc3\x52\x7b\x0a\xba\x85\xe8\x3f\x91\x79\xe9\x71\xc1\x5c\xa9\x7d\x90\x48\x26\xc9\xc8\x6d\xfe\x77\x3b\x73\xb3\x51\x1f\x35\x21\x69\xc3\x36\x45\x06\xae\xe8\x28\x0d\x64\x4f\x0a\x2d\xaa\x83\x4c\xa9\x44\xb9\xcf\xc0\x36\xda\xe7\x18\x66\x06\xf2\x03\x08\x77\x84\xe5\xcd\x4e\x6d\x68\xb1\x00\xf3\x2a\xc7\x20\x79\xf0\x9c\xcb\x83\x0e\x9c\x75\x6b\x41\x13\x91\xb6\x02\xae\x3e\xc2\xca\x7a\x9d\x85\x70\x80\x06\xa1\x63\x81\x12\xfb\x41\xbd\xfb\x52\x72\xcf\x13\xb6\x1d\x16\xde\xdf\x44\x6c\x19\x54\xb9\xdb\xdc\x65\x59\x2c\xa1\xee\xad\x9d\x13\x00\xdb\xcb\x75\x15\x43\x49\x3d\xdf\xd2\x50\x9c\x60\xca\x69\x3d\xb1\xc0\xbe\x2d\x8c\x21\x4e\xf5\x14\xf1\xa4\x9a\xcf\xbe\xb6\x3e\x20\x4e\x5b\x6a\xd1\x54\x6f\xac\xc3\x66\xd5\xee\x6f\xe3\xe8\x47\x67\xef\x90\x37\x8b\x26\xe3\xe0\xc9\x1a\x5a\x3b\x97\xce\x74\x82\x73\x17\x73\xb7\x3b\x1a\x44\x46\xb6\x0f\x20\x57\x48\x79\x93\x98\xd7\xe6\x27\x1d\x83\x1c\xaf\x10\xd4\xee\x05\x0f\x90\xa6\xb9\x18\x4d\x18\x7d\xb9\x8c\xe9\xa1\x34\x3e\x35\x84\x59\x96\x06\x89\xc7\x16\x83\x22\xfd\x4a\xfe\x46\xf7\xa0\xfb\x67\x00\x42\x89\x04\x2d\x36\x21\x96\xc1\xd0\x48\x92\xdd\x1e\x69\xc5\x85\xe1\x50\xda\x16\x57\xb7\xb3\xd8\x0e\x21\x5f\x52\xfe\xb5\xfb\xe5\x80\x20\x1b\x6d\x0f\xdc\x04\xbb\xea\x5e\x0a\x60\xb1\xff\x71\xe3\x28\x12\x8b\x9d\xaf\x40\x1c\xb7\xb7\x06\x72\xae\xd2\xec\xa2\x0c\x5a\x73\x10\x03\x7c\xf7\x32\x95\xe1\xed\x8e\xef\xfe\x95\x29\x2e\xf9\x9e\xbe\x5b\xa3\xa8\x8c\x0d\x24\x63\x61\xe5\x68\xcc\x90\x01\x32\x4b\x21\x77\x7e\xe8\xd1\x3f\xb5\x8c\xbb\xd1\x1b\x14\xf4\xea\xb4\x3b\x25\x53\x8f\x18\x76\xd5\x23\xcb\xb0\x4c\x5e\x4b\x04\x9f\x72\x25\x52\x2e\x7e\xc8\xb8\xa1\x88\x39\x5f\x36\x30\x41\xc5\x86\x9a\x35\x52\x1b\xfe\x1b\x9d\x89\xf1\x3d\x5f\x23\xbd\xf4\xb1\x2e\x13\x97\x90\xde\x90\xb6\x82\xcf\x5a\x60\xfc\x59\x88\xfc\x44\x1e\x02\xc6\x2a\x9b\xdf\x2e\x0c\x06\x85\x41\xcb\x3a\x80\x3b\x1e\xf1\x56\x06\x9a\x9d\x97\x62\xca\x02\x5b\xbd\x9a\xb3\xc1\x25\x2a\x63\x08\x6d\x0f\xf1\x0f\xba\xb1\x0c\xe4\x4b\x9a\x30\xad\x56\x8c\x8a\x1f\x5f\x7e\xb3\xa3\xe9\xeb\x56\xb9\x02\x0d\x04\x55\x08\x5e\x1d\x01\x10\x00\xc3\x7a\xa5\xc2\xc9\x52\x1c\x3d\x6d\xf3\xb9\x92\xa8\x4b\xbf\x8a\x6b\xb7\x40\x2a\x54\x15\x28\x93\xa5\x3c\xb4\x86\x84\x73\xdb\xc3\x69\xab\x01\x70\x51\xd7\x80\xfb\x28\x38\x6f\x7b\x0d\x70\x7a\x05\xdb\xec\x1e\xe2\xbf\xe6\xe3\x15\x1f\x93\xcd\x7b\x9f\x0c\x2e\x9d\x3c\x88\x2a\x08\xb7\x40\xb4\x09\xb3\x77\xf8\x3f\x50\x6d\xe8\x37\xec\x0c\x8a\xc8\x56\xa7\x07\xf0\x97\xab\x5b\x4f\x29\x50\x89\x73\xa0\x4c\x36\x43\xbf\x06\xcc\x27\x52\xc4\xd3\x9a\x57\x1d\xe0\x6a\x2f\xa5\xa2\x54\x38\xe6\x73\x6e\x51\x94\x40\x16\xe1\xcd\x1a\x21\x86\x63\x86\x7d\xdb\x12\x70\x67\x9e\x60\xc1\x3f\xc8\xd1\x6a\xce\xdd\x59\x02\xb5\x78\xbd\xad\x61\x38\xb3\x94\xa4\xd1\x6f\xf5\x33\xb2\xd1\x95\x62\xff\x20\xe2\xfa\xc6\x00\xf5\xf0\x92\x65\x4f\x5a\x95\x62\x3f\x71\xb0\x5c\xb7\x92\x3e\xdd\xb8\x75\x97\x6e\xae\x9a\x56\x6c\x61\x09\xbc\xba\xa9\xd2\x82\x24\x16\xa5\x19\xba\x50\x93\xba\xc5\x06\xe6\x50\xdf\x46\xcc\x26\x32\x28\x29\xa9\xcd\x94\x5d\xba\xaa\xa3\x8e\xd8\x85\xd7\x45\xb5\xcb\x38\xa8\x4d\xf4\xed\xab\x67\x2f\x53\xa3\xdd\x20\xd6\x18\xf3\x0d\x94\xb8\xe8\xd1\x77\xd8\x02\x86\xdb\xc6\xb9\xdb\x8e\x23\x0a\xa5\x45\x36\x12\x69\x75\xf5\x74\x3a\x05\x7b\x5b\xd6\xa9\x9c\xd1\x9b\xea\x48\x0e\xe9\x8e\xf5\xe5\xf9\x8c\xc9\xb3\x15\x72\xc3\x1b\x6a\x43\xda\x5f\x7b\xce\x84\xa3\x54\x57\xff\xa5\x8a\x01\x6a\xc8\x17\xe3\x77\xb7\x57\xf6\x84\x93\xd3\xd4\x81\x76\xd8\x16\x6d\x90\xc4\x53\x1c\x21\x84\x8e\xf4\x61\x03\x32\x81\x3e\x43\x1a\x0c\x5d\xfd\x54\x8c\xbc\x4c\x08\xae\x60\x0f\x98\x87\x2f\x71\x5f\xdb\x3c\x97\x2f\x39\x53\x64\x4f\x9f\x1c\xf3\x0b\x70\x09\x33\x72\x9d\xae\xea\x92\x7d\xc6\x90\x06\x7a\x4a\xaa\x7a\xa9\xc8\x49\x76\x76\xa3\xcc\x97\xb2\xab\xfc\x35\x5b\xe5\xd7\x36\x92\x89\x2a\x94\xfa\x46\xa4\xf6\x62\x20\xff\x6f\x96\x0b\xd5\xb5\xa8\xed\x8f\x79\x22\xd6\xda\xd8\xa2\xb3\xdf\x34\x42\x79\xb2\x0c\xe0\x60\xda\x82\xf4\xd4\x84\xe1\xfe\xef\x86\x4e\x87\x44\x1b\x07\xd1\x60\x9a\x0d\x00\x98\x14\x8d\xc5\x0c\xa5\x69\x74\x2e\xea\x06\xf2\x51\xba\xc2\xe9\x21\x03\x84\x18\x15\x6a\x5f\xd9\x03\xd5\x81\x34\x7a\xdd\x56\xe9\x16\x0c\x02\x9d\x47\x12\x10\xfa\x87\x07\x40\x51\x0a\xc0\xc7\xb1\xdd\x24\xfb\xf6\x12\xb8\xfa\x25\x00\x11\x01\x00\x01\x89\x02\x1f\x04\x18\x01\x08\x00\x09\x05\x02\x55\x08\x5e\x1d\x02\x1b\x0c\x00\x0a\x09\x10\x3e\xbf\xca\xaa\x8c\x8a\xd9\x9e\xb3\x3c\x0f\xfe\x3e\x42\x53\xcc\xff\x24\x43\x0e\x06\x5b\x4c\x76\x67\x15\xdf\x3a\x69\x11\x55\x8a\x52\x12\x81\xde\x85\xfe\xb2\xf5\x81\x4d\xd3\x4f\x93\xf2\x96\xfb\xd5\x11\x88\xb6\xfb\x97\xe6\xfe\xa8\x3c\xa3\xc2\x94\xe9\xc8\x56\x0d\x96\x54\x73\xaa\xe9\x0d\xc7\xb1\x71\x33\x97\xba\x08\x6c\xeb\x4f\x13\xdf\x16\x5c\x78\x95\x5e\xe7\x7f\x76\x37\x39\xe2\xe1\xf3\xf5\x68\xd2\x3d\xfc\xbf\x89\xd3\xfb\x48\xcb\x25\xa3\x6f\x41\x1d\x1f\xca\xf6\x74\x24\x88\x6b\x5a\xc8\x3e\x7b\xc1\xdc\x60\x6c\xb5\xce\x6c\x4d\xdf\x03\x48\x25\xe0\x0b\x16\x97\x51\x19\xcc\xd7\x16\x54\xbe\x12\x22\xbb\xe7\x04\x6a\x58\x21\x41\x81\x72\xc4\xc8\x88\xfd\xe9\xd3\xd6\xee\xe1\x07\xfa\x1e\x32\x51\x5d\x99\x41\xba\x2c\xe2\x80\x88\x1e\x3b\x7f\x65\xfc\x6e\xad\x6f\xa1\x80\xd5\xc9\xdb\xab\x8c\xba\x68\x2b\x50\x79\x71\xcb\xae\x7b\xda\x93\x58\xd1\xab\x39\xe5\x25\x65\x2a\x4b\x59\x90\x80\xe3\xc5\xd4\xcb\x8e\x76\xe1\xc0\xbd\x06\xdf\x30\x14\x17\x00\x47\xe0\x39\x3c\xee\x1f\xd3\xee\x2a\x81\xa6\xec\xc7\x8f\xc1\x3a\xd4\xe4\x6b\x0e\xa4\xd8\x12\xe0\xb6\xef\x21\x13\x83\x27\x16\x03\xfa\xf2\x30\x03\x90\x67\x8a\x28\xe0\x7c\xdf\x5a\x8f\xd2\x90\x54\xc5\x11\xad\xde\x0d\xa7\xb8\xb4\x11\x59\xa3\xf0\xff\x45\xb4\xd8\x18\xb0\x46\x83\xb7\xb9\x5a\x1a\x93\x41\xf7\xef\x58\xf6\x17\x9b\xea\x43\x44\xba\x51\x7b\x28\x8e\xf2\xf0\x22\xa2\x92\x0b\xc5\x18\x71\xc2\xc9\x7d\x81\x08\x26\xd0\x06\xa8\xba\xc1\xb1\x06\xab\x5e\xaa\x91\x32\x66\x7a\xc6\x2e\xf6\x28\x38\xbf\x8c\x43\xfd\x0f\xdc\x2c\x91\x73\x88\x92\xe3\x11\xc5\xac\x2f\xb8\x6a\xee\xad\xa8\xe9\xee\xcd\x2f\xa8\x5f\xe5\xa4\xc7\xd3\xf6\xdd\x78\xc5\xcc\xa9\x1c\xc3\x08\x01\x50\xe5\x9f\xb1\xd1\x05\x02\x81\x5a\x27\xc7\x38\xfe\x0a\xe4\xc3\xfc\xb7\x8e\xb3\xf4\x07\xbb\xff\x5e\x9a\xf9\xbd\x10\xe3\x18\x63\xf5\xfd\xbe\x27\x4b\x7f\x9f\x0f\x4f\xf0\x0c\xa8\xc8\x98\x6c\xd5\xad\xcb\xaa\x98\xea\x4d\x8b\x33\x0e\xf8\x61\xde\x3e\x84\xef\x93\x8e\xb9\x3c\x32\xba\x9b\x09\x4b\x3d\x87\xa8\xa1\xf4\x83\x82\xa3\xaf\x09\x4d\x64\x10\x6e\x05\x92\x93\x64\xc0\xc9\xdd\xd4\xe0\xea\x93\xc8\x19\xc7\x5a\xbd\x07\x84\x70\xd1\x4c\xec\x72\x0d\xb6\x54\xb5\x76\xe4\xfe\xbe\x10\xe2\x04\xdc\x02\xdf\xaa\x8e\x9b\x30\x3f\x29"))
+
+     (CASE_S(QType::SPF, "\"v=spf1 a:mail.rec.test ~all\"", "\x1bv=spf1 a:mail.rec.test ~all"))
+     (CASE_S(QType::EUI48, "00-11-22-33-44-55", "\x00\x11\x22\x33\x44\x55"))
+     (CASE_S(QType::EUI64, "00-11-22-33-44-55-66-77", "\x00\x11\x22\x33\x44\x55\x66\x77"))
+     (CASE_S(QType::TKEY, "gss-tsig. 12345 12345 3 21 4 dGVzdA== 4 dGVzdA==", "\x08gss-tsig\x00\x00\x00\x30\x39\x00\x00\x30\x39\x00\x03\x00\x15\x00\x04test\x00\x04test"))
+     (CASE_S(QType::TSIG, "HMAC-MD5.SIG-ALG.REG.INT. 1368386956 60 16 TkbpD66/Mtgo8GUEFZIwhg== 12345 0 0", "\x08HMAC-MD5\x07SIG-ALG\x03REG\x03INT\x00\x00\x00\x51\x8f\xed\x8c\x00\x3c\x00\x10\x4e\x46\xe9\x0f\xae\xbf\x32\xd8\x28\xf0\x65\x04\x15\x92\x30\x86\x30\x39\x00\x00\x00\x00"))
+     (CASE_S(QType::TSIG, "HMAC-MD5.SIG-ALG.REG.INT. 1368386956 60 16 TkbpD66/Mtgo8GUEFZIwhg== 12345 18 16 TkbpD66/Mtgo8GUEFZIwhg==", "\x08HMAC-MD5\x07SIG-ALG\x03REG\x03INT\x00\x00\x00\x51\x8f\xed\x8c\x00\x3c\x00\x10\x4e\x46\xe9\x0f\xae\xbf\x32\xd8\x28\xf0\x65\x04\x15\x92\x30\x86\x30\x39\x00\x12\x00\x10\x4e\x46\xe9\x0f\xae\xbf\x32\xd8\x28\xf0\x65\x04\x15\x92\x30\x86"))
+     (CASE_S(QType::URI, "10000 1 \"ftp://ftp1.example.com/public\"", "\x27\x10\x00\x01\x66\x74\x70\x3a\x2f\x2f\x66\x74\x70\x31\x2e\x65\x78\x61\x6d\x70\x6c\x65\x2e\x63\x6f\x6d\x2f\x70\x75\x62\x6c\x69\x63"))
+     (CASE_S(QType::URI, "10 1 \"ftp://ftp1.example.com/public/with/a/very/very/very/very/very/very/very/very/very/very/very/very/very/very/very/very/very/very/very/very/very/very/very/very/very/very/very/very/very/very/very/very/very/very/very/very/very/very/very/very/very/very/very/long/url\"", "\x00\x0a\x00\x01\x66\x74\x70\x3a\x2f\x2f\x66\x74\x70\x31\x2e\x65\x78\x61\x6d\x70\x6c\x65\x2e\x63\x6f\x6d\x2f\x70\x75\x62\x6c\x69\x63\x2f\x77\x69\x74\x68\x2f\x61\x2f\x76\x65\x72\x79\x2f\x76\x65\x72\x79\x2f\x76\x65\x72\x79\x2f\x76\x65\x72\x79\x2f\x76\x65\x72\x79\x2f\x76\x65\x72\x79\x2f\x76\x65\x72\x79\x2f\x76\x65\x72\x79\x2f\x76\x65\x72\x79\x2f\x76\x65\x72\x79\x2f\x76\x65\x72\x79\x2f\x76\x65\x72\x79\x2f\x76\x65\x72\x79\x2f\x76\x65\x72\x79\x2f\x76\x65\x72\x79\x2f\x76\x65\x72\x79\x2f\x76\x65\x72\x79\x2f\x76\x65\x72\x79\x2f\x76\x65\x72\x79\x2f\x76\x65\x72\x79\x2f\x76\x65\x72\x79\x2f\x76\x65\x72\x79\x2f\x76\x65\x72\x79\x2f\x76\x65\x72\x79\x2f\x76\x65\x72\x79\x2f\x76\x65\x72\x79\x2f\x76\x65\x72\x79\x2f\x76\x65\x72\x79\x2f\x76\x65\x72\x79\x2f\x76\x65\x72\x79\x2f\x76\x65\x72\x79\x2f\x76\x65\x72\x79\x2f\x76\x65\x72\x79\x2f\x76\x65\x72\x79\x2f\x76\x65\x72\x79\x2f\x76\x65\x72\x79\x2f\x76\x65\x72\x79\x2f\x76\x65\x72\x79\x2f\x76\x65\x72\x79\x2f\x76\x65\x72\x79\x2f\x76\x65\x72\x79\x2f\x76\x65\x72\x79\x2f\x76\x65\x72\x79\x2f\x6c\x6f\x6e\x67\x2f\x75\x72\x6c"))
+     (CASE_S(QType::CAA, "0 issue \"example.net\"", "\x00\x05\x69\x73\x73\x75\x65\x65\x78\x61\x6d\x70\x6c\x65\x2e\x6e\x65\x74"))
+     (CASE_S(QType::DLV, "20642 8 2 04443abe7e94c3985196beae5d548c727b044dda5151e60d7cd76a9fd931d00e", "\x50\xa2\x08\x02\x04\x44\x3a\xbe\x7e\x94\xc3\x98\x51\x96\xbe\xae\x5d\x54\x8c\x72\x7b\x04\x4d\xda\x51\x51\xe6\x0d\x7c\xd7\x6a\x9f\xd9\x31\xd0\x0e"))
+     (CASE_S((QType::typeenum)65226,"\\# 3 414243","\x41\x42\x43"))
+
+/*   (CASE_S(QType::NAME, "zone format", "line format")) */
+/*   (CASE_L(QType::NAME, "zone format", "canonic zone format", "line format")) */
 ;
 
   int n=0;
   int lq=-1;
   for(const cases_t::value_type& val :  cases) {
-   QType q(val.get<0>());
+   const QType q(val.get<0>());
+   const std::string& inval = val.get<1>();
+   const std::string& zoneval = val.get<2>();
+   const std::string& lineval = val.get<3>();
+   const broken_marker broken = val.get<4>();
+
    if (lq != q.getCode()) n = 0;
+   BOOST_CHECK_MESSAGE(q.getCode() >= lq, "record types not sorted correctly: " << q.getCode() << " < " << lq);
    lq = q.getCode();
    n++;
    BOOST_TEST_CHECKPOINT("Checking record type " << q.getName() << " test #" << n);
    BOOST_TEST_MESSAGE("Checking record type " << q.getName() << " test #" << n);
    try {
       std::string recData;
-      auto rec = DNSRecordContent::mastermake(q.getCode(), 1, val.get<1>());
-      BOOST_CHECK_MESSAGE(rec != NULL, "mastermake( " << q.getCode() << ", 1, " << val.get<1>() << ") returned NULL");
+      auto rec = DNSRecordContent::mastermake(q.getCode(), 1, inval);
+      BOOST_CHECK_MESSAGE(rec != NULL, "mastermake( " << q.getCode() << ", 1, " << inval << ") returned NULL");
       if (rec == NULL) continue;
       // now verify the record (note that this will be same as *zone* value (except for certain QTypes)
 
@@ -207,10 +221,10 @@ BOOST_AUTO_TEST_CASE(test_record_types) {
       case QType::SOA:
       case QType::TXT:
           // check *input* value instead
-          REC_CHECK_EQUAL(rec->getZoneRepresentation(), val.get<1>());
+          REC_CHECK_EQUAL(rec->getZoneRepresentation(), inval);
           break;
       default:
-          REC_CHECK_EQUAL(rec->getZoneRepresentation(), val.get<2>());
+          REC_CHECK_EQUAL(rec->getZoneRepresentation(), zoneval);
       }
       recData = rec->serialize(DNSName("rec.test"));
 
@@ -218,9 +232,9 @@ BOOST_AUTO_TEST_CASE(test_record_types) {
       BOOST_CHECK_MESSAGE(rec2 != NULL, "unserialize(rec.test, " << q.getCode() << ", recData) returned NULL");
       if (rec2 == NULL) continue;
       // now verify the zone representation (here it can be different!)
-      REC_CHECK_EQUAL(rec2->getZoneRepresentation(), val.get<2>());
+      REC_CHECK_EQUAL(rec2->getZoneRepresentation(), zoneval);
       // and last, check the wire format (using hex format for error readability)
-      string cmpData = makeHexDump(val.get<3>());
+      string cmpData = makeHexDump(lineval);
       recData = makeHexDump(recData);
       REC_CHECK_EQUAL(recData, cmpData);
    } catch (std::runtime_error &err) {
@@ -235,60 +249,86 @@ bool test_dnsrecords_cc_predicate( std::exception const &ex ) { return true; }
 
 // these *MUST NOT* parse properly!
 BOOST_AUTO_TEST_CASE(test_record_types_bad_values) {
+  enum class case_type_t { zone, wire };
+
   // qtype, value, zone/wire format, broken
-  typedef boost::tuple<const QType::typeenum, const std::string, case_type_enum_t, bool> case_t;
+  typedef boost::tuple<const QType::typeenum, const std::string, case_type_t, broken_marker> case_t;
   typedef std::list<case_t> cases_t;
 
-  cases_t cases = boost::assign::list_of
-     (case_t(QType::A, "932.521.256.42", zone, false)) // hollywood IP
-     (case_t(QType::A, "932.521", zone, false)) // truncated hollywood IP
-     (case_t(QType::A, "10.0", zone, false)) // truncated IP
-     (case_t(QType::A, "10.0.0.1.", zone, false)) // trailing dot
-     (case_t(QType::A, "10.0.0.", zone, false)) // trailing dot
-     (case_t(QType::A, ".0.0.1", zone, false)) // empty octet
-     (case_t(QType::A, "10..0.1", zone, false)) // empty octet
-     (case_t(QType::A, "\xca\xec\x00", wire, false)) // truncated wire value
-     (case_t(QType::A, "127.0.0.1 evil data", zone, false)) // trailing garbage
-     (case_t(QType::AAAA, "23:00", zone, false)) // time when this test was written 
-     (case_t(QType::AAAA, "23:00::15::43", zone, false)) // double compression
-     (case_t(QType::AAAA, "2a23:00::15::", zone, false)) // ditto 
-     (case_t(QType::AAAA, "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff", wire, false)) // truncated wire value
+#define ZONE_CASE(type, input) case_t(type, BINARY(input), case_type_t::zone, broken_marker::WORKING)
+#define WIRE_CASE(type, input) case_t(type, BINARY(input), case_type_t::wire, broken_marker::WORKING)
+#define BROKEN_ZONE_CASE(type, input) case_t(type, BINARY(input), case_type_t::zone, broken_marker::BROKEN)
+#define BROKEN_WIRE_CASE(type, input) case_t(type, BINARY(input), case_type_t::wire, broken_marker::BROKEN)
+
+  const cases_t cases = boost::assign::list_of
+     (ZONE_CASE(QType::A, "932.521.256.42")) // hollywood IP
+     (ZONE_CASE(QType::A, "932.521")) // truncated hollywood IP
+     (ZONE_CASE(QType::A, "10.0")) // truncated IP
+     (ZONE_CASE(QType::A, "10.0.0.1.")) // trailing dot
+     (ZONE_CASE(QType::A, "10.0.0.")) // trailing dot
+     (ZONE_CASE(QType::A, ".0.0.1")) // empty octet
+     (ZONE_CASE(QType::A, "10..0.1")) // empty octet
+     (WIRE_CASE(QType::A, "\xca\xec\x00")) // truncated wire value
+     (ZONE_CASE(QType::A, "127.0.0.1 evil data")) // trailing garbage
+     (ZONE_CASE(QType::AAAA, "23:00")) // time when this test was written
+     (ZONE_CASE(QType::AAAA, "23:00::15::43")) // double compression
+     (ZONE_CASE(QType::AAAA, "2a23:00::15::")) // ditto
+     (WIRE_CASE(QType::AAAA, "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff")) // truncated wire value
 // empty label, must be broken
-     (case_t(QType::CNAME, "name..example.com.", zone, false))
+     (ZONE_CASE(QType::CNAME, "name..example.com."))
 // overly large label (64), must be broken
-     (case_t(QType::CNAME, "1234567890123456789012345678901234567890123456789012345678901234.example.com.", zone, false))
+     (ZONE_CASE(QType::CNAME, "1234567890123456789012345678901234567890123456789012345678901234.example.com."))
 // local overly large name (256), must be broken
-     (case_t(QType::CNAME, "123456789012345678901234567890123456789012345678901234567890123.123456789012345678901234567890123456789012345678901234567890123.123456789012345678901234567890123456789012345678901234567890123.12345678901234567890123456789012345678901234567890123.rec.test.", zone, false))
+     (ZONE_CASE(QType::CNAME, "123456789012345678901234567890123456789012345678901234567890123.123456789012345678901234567890123456789012345678901234567890123.123456789012345678901234567890123456789012345678901234567890123.12345678901234567890123456789012345678901234567890123.rec.test."))
 // non-local overly large name (256), must be broken
-     (case_t(QType::CNAME, "123456789012345678901234567890123456789012345678901234567890123.123456789012345678901234567890123456789012345678901234567890123.123456789012345678901234567890123456789012345678901234567890123.12345678901234567890123456789012345678901234567890123456789012.", zone, false))
-     (case_t(QType::SOA, "ns.rec.test hostmaster.test.rec 20130512010 3600 3600 604800 120", zone, false)) // too long serial
+     (ZONE_CASE(QType::CNAME, "123456789012345678901234567890123456789012345678901234567890123.123456789012345678901234567890123456789012345678901234567890123.123456789012345678901234567890123456789012345678901234567890123.12345678901234567890123456789012345678901234567890123456789012."))
+     (ZONE_CASE(QType::SOA, "ns.rec.test hostmaster.test.rec 20130512010 3600 3600 604800 120")) // too long serial
 ;
 
   int n=0;
   int lq=-1;
 
   for(const cases_t::value_type& val :  cases) {
-    QType q(val.get<0>());
+    const QType q(val.get<0>());
+    const std::string& input = val.get<1>();
+    const case_type_t case_type = val.get<2>();
+    const broken_marker broken = val.get<3>();
+
     if (lq != q.getCode()) n = 0;
     lq = q.getCode();
     n++;
     BOOST_TEST_CHECKPOINT("Checking bad value for record type " << q.getName() << " test #" << n);
     BOOST_TEST_MESSAGE("Checking bad value for record type " << q.getName() << " test #" << n);
+
     vector<uint8_t> packet;
     DNSPacketWriter pw(packet, DNSName("unit.test"), q.getCode());
 
-    if (val.get<2>() == wire) {
+    if (case_type == case_type_t::wire) {
       BOOST_WARN_MESSAGE(false, "wire checks not supported");
       continue;
     }
 
-    if (val.get<3>()) {
+    if (broken_marker::BROKEN == broken) {
       bool success=true;
-      BOOST_WARN_EXCEPTION( { auto drc = DNSRecordContent::mastermake(q.getCode(), 1, val.get<1>()); pw.startRecord(DNSName("unit.test"), q.getCode()); drc->toPacket(pw); success=false; }, std::exception, test_dnsrecords_cc_predicate );
-      if (success) REC_FAIL_XSUCCESS2(q.getName() << " test #" << n << " has unexpectedly passed"); // a bad record was detected when it was supposed not to be detected
+      BOOST_WARN_EXCEPTION(
+        {
+          auto drc = DNSRecordContent::mastermake(q.getCode(), 1, input);
+          pw.startRecord(DNSName("unit.test"), q.getCode());
+          drc->toPacket(pw);
+          success=false;
+        },
+        std::exception, test_dnsrecords_cc_predicate
+      );
+      if (success) REC_FAIL_XSUCCESS(q.getName() << " test #" << n << " has unexpectedly passed"); // a bad record was detected when it was supposed not to be detected
     } else {
-      BOOST_CHECK_EXCEPTION( { auto drc = DNSRecordContent::mastermake(q.getCode(), 1, val.get<1>()); pw.startRecord(DNSName("unit.test"), q.getCode()); drc->toPacket(pw); }, std::exception, test_dnsrecords_cc_predicate );
+      BOOST_CHECK_EXCEPTION(
+        {
+          auto drc = DNSRecordContent::mastermake(q.getCode(), 1, input);
+          pw.startRecord(DNSName("unit.test"), q.getCode());
+          drc->toPacket(pw);
+        },
+        std::exception, test_dnsrecords_cc_predicate
+      );
     }
   };
 }
@@ -307,7 +347,7 @@ BOOST_AUTO_TEST_CASE(test_opt_record_in) {
 
   // this should contain NSID now
   BOOST_CHECK_EQUAL(eo.d_packetsize, 1280);
-   
+
   // it should contain NSID option with value 'powerdns', and nothing else
   BOOST_CHECK_EQUAL(eo.d_options.size(), 1);
   BOOST_CHECK_EQUAL(eo.d_options[0].first, 3); // nsid
@@ -323,7 +363,7 @@ BOOST_AUTO_TEST_CASE(test_opt_record_out) {
   pw.xfrIP(htonl(0x7f000001));
   opts.push_back(pair<uint16_t,string>(3, "powerdns"));
   pw.addOpt(1280, 0, 0, opts);
-  pw.getHeader()->id = htons(0xf001); 
+  pw.getHeader()->id = htons(0xf001);
   pw.getHeader()->rd = 1;
   pw.commit();
 
index cf2c7809b6da49253a01a9bcb3563b51b2af72c9..22563d0a48d183357b39b5c0d3d2d76de5215f96 100644 (file)
@@ -60,7 +60,7 @@ try
   Socket sock(AF_INET, SOCK_DGRAM);
   ComboAddress dest(argv[1] + (*argv[1]=='@'), atoi(argv[2]));
   seedRandom("/dev/urandom");
-  cerr<<"Keyname: '"<<keyname.toString()<<"', algo: '"<<trc.d_algoName.toString()<<"', key: '"<<Base64Encode(key)<<"'\n";
+  cerr<<"Keyname: '"<<keyname<<"', algo: '"<<trc.d_algoName<<"', key: '"<<Base64Encode(key)<<"'\n";
   TSIGTriplet tt;
   tt.name=keyname;
   tt.algo=DNSName("hmac-md5");
index ce01c434b44ea8ef4bad99b04ba5aea00188a7de..de0471b73dde899b853c1ac6a9e4bdbda3ff7e78 100644 (file)
@@ -46,7 +46,7 @@ bool TSIGTCPVerifier::check(const string& data, const MOADNSParser& mdp)
 
   if (checkTSIG) {
     if (theirMac.empty()) {
-      throw std::runtime_error("No TSIG on AXFR response from "+d_remote.toStringWithPort()+" , should be signed with TSIG key '"+d_tt.name.toString()+"'");
+      throw std::runtime_error("No TSIG on AXFR response from "+d_remote.toStringWithPort()+" , should be signed with TSIG key '"+d_tt.name.toLogString()+"'");
     }
 
     try {
index b30783d8bb697eef04c60b8c88018bb28b60e5d1..9c299be55633abd04227f9c51582005595f19468 100644 (file)
@@ -378,18 +378,18 @@ bool UeberBackend::getSOA(const DNSName &domain, SOAData &sd)
   }
 
   // not found in neg. or pos. cache, look it up
-  return getSOAUncached(domain, sd);
+  return getSOAUncached(domain, sd, false);
 }
 
-bool UeberBackend::getSOAUncached(const DNSName &domain, SOAData &sd)
+bool UeberBackend::getSOAUncached(const DNSName &domain, SOAData &sd, bool unmodifiedSerial)
 {
   d_question.qtype=QType::SOA;
   d_question.qname=domain;
   d_question.zoneId=-1;
 
   for(vector<DNSBackend *>::const_iterator i=backends.begin();i!=backends.end();++i)
-    if((*i)->getSOA(domain, sd)) {
-      if( d_cache_ttl ) {
+    if((*i)->getSOA(domain, sd, unmodifiedSerial)) {
+      if(d_cache_ttl && !unmodifiedSerial) {
         DNSZoneRecord rr;
         rr.dr.d_name = sd.qname;
         rr.dr.d_type = QType::SOA;
index 63729d4a6ce2afd52ce3829856452740b45deccf..9ce54b0c1aa1dd2f6af03a96d3bf777af82ecbdd 100644 (file)
@@ -101,7 +101,9 @@ public:
   /** Determines if we are authoritative for a zone, and at what level */
   bool getAuth(const DNSName &target, const QType &qtype, SOAData* sd, bool cachedOk=true);
   bool getSOA(const DNSName &domain, SOAData &sd);
-  bool getSOAUncached(const DNSName &domain, SOAData &sd);  // same, but ignores cache
+  /** Load SOA info from backends, ignoring the cache. Callers that will write to the backend should use this
+   * function, possibly setting unmodifiedSerial=true when editing the SOA Serial. */
+  bool getSOAUncached(const DNSName &domain, SOAData &sd, bool unmodifiedSerial=false);
   bool get(DNSZoneRecord &r);
   void getAllDomains(vector<DomainInfo> *domains, bool include_disabled=false);
 
index 810bc8dd3a862c85d09eecebdce932dd228b2f25..2aa29748d5212006d969dccf9615d93713206364 100644 (file)
@@ -155,6 +155,20 @@ static DNSName getNSECOwnerName(const DNSName& initialOwner, const std::vector<s
   return result;
 }
 
+static bool isNSECAncestorDelegation(const DNSName& signer, const DNSName& owner, const std::shared_ptr<NSECRecordContent> nsec)
+{
+  return nsec->d_set.count(QType::NS) &&
+    !nsec->d_set.count(QType::SOA) &&
+    signer.countLabels() < owner.countLabels();
+}
+
+static bool isNSEC3AncestorDelegation(const DNSName& signer, const DNSName& owner, const std::shared_ptr<NSEC3RecordContent> nsec3)
+{
+  return nsec3->d_set.count(QType::NS) &&
+    !nsec3->d_set.count(QType::SOA) &&
+    signer.countLabels() < owner.countLabels();
+}
+
 static bool provesNoDataWildCard(const DNSName& qname, const uint16_t qtype, const cspmap_t& validrrsets)
 {
   LOG("Trying to prove that there is no data in wildcard for "<<qname<<"/"<<QType(qtype).getName()<<endl);
@@ -334,21 +348,18 @@ dState getDenial(const cspmap_t &validrrsets, const DNSName& qname, const uint16
            that (original) owner name other than DS RRs, and all RRs below that
            owner name regardless of type.
         */
-        if (nsec->d_set.count(QType::NS) && !nsec->d_set.count(QType::SOA) &&
-            signer.countLabels() < owner.countLabels()) {
-          LOG("type is "<<QType(qtype).getName()<<", NS is "<<std::to_string(nsec->d_set.count(QType::NS))<<", SOA is "<<std::to_string(nsec->d_set.count(QType::SOA))<<", signer is "<<signer.toString()<<", owner name is "<<owner.toString()<<endl);
+        if (qtype != QType::DS && (qname == owner || qname.isPartOf(owner)) && isNSECAncestorDelegation(signer, owner, nsec)) {
+          LOG("type is "<<QType(qtype).getName()<<", NS is "<<std::to_string(nsec->d_set.count(QType::NS))<<", SOA is "<<std::to_string(nsec->d_set.count(QType::SOA))<<", signer is "<<signer<<", owner name is "<<owner<<endl);
           /* this is an "ancestor delegation" NSEC RR */
-          if (qname == owner && qtype != QType::DS) {
-            LOG("An ancestor delegation NSEC RR can only deny the existence of a DS"<<endl);
-            continue;
-          }
+          LOG("An ancestor delegation NSEC RR can only deny the existence of a DS"<<endl);
+          return NODATA;
         }
 
         /* check if the type is denied */
         if(qname == owner) {
           if (nsec->d_set.count(qtype)) {
             LOG("Does _not_ deny existence of type "<<QType(qtype).getName()<<endl);
-            continue;
+            return NODATA;
           }
 
           LOG("Denies existence of type "<<QType(qtype).getName()<<endl);
@@ -446,21 +457,18 @@ dState getDenial(const cspmap_t &validrrsets, const DNSName& qname, const uint16
            that (original) owner name other than DS RRs, and all RRs below that
            owner name regardless of type.
         */
-        if (nsec3->d_set.count(QType::NS) && !nsec3->d_set.count(QType::SOA) &&
-            signer.countLabels() < v.first.first.countLabels()) {
-          LOG("type is "<<QType(qtype).getName()<<", NS is "<<std::to_string(nsec3->d_set.count(QType::NS))<<", SOA is "<<std::to_string(nsec3->d_set.count(QType::SOA))<<", signer is "<<signer.toString()<<", owner name is "<<v.first.first.toString()<<endl);
+        if (qtype != QType::DS && beginHash == h && isNSEC3AncestorDelegation(signer, v.first.first, nsec3)) {
+          LOG("type is "<<QType(qtype).getName()<<", NS is "<<std::to_string(nsec3->d_set.count(QType::NS))<<", SOA is "<<std::to_string(nsec3->d_set.count(QType::SOA))<<", signer is "<<signer<<", owner name is "<<v.first.first<<endl);
           /* this is an "ancestor delegation" NSEC3 RR */
-          if (beginHash == h && qtype != QType::DS) {
-            LOG("An ancestor delegation NSEC3 RR can only deny the existence of a DS"<<endl);
-            continue;
-          }
+          LOG("An ancestor delegation NSEC3 RR can only deny the existence of a DS"<<endl);
+          return NODATA;
         }
 
         // If the name exists, check if the qtype is denied
         if(beginHash == h) {
           if (nsec3->d_set.count(qtype)) {
             LOG("Does _not_ deny existence of type "<<QType(qtype).getName()<<" for name "<<qname<<" (not opt-out)."<<endl);
-            continue;
+            return NODATA;
           }
 
           LOG("Denies existence of type "<<QType(qtype).getName()<<" for name "<<qname<<" (not opt-out)."<<endl);
@@ -513,6 +521,12 @@ dState getDenial(const cspmap_t &validrrsets, const DNSName& qname, const uint16
             if(!nsec3)
               continue;
 
+            const DNSName signer = getSigner(v.second.signatures);
+            if (!v.first.first.isPartOf(signer)) {
+              LOG("Owner "<<v.first.first<<" is not part of the signer "<<signer<<", ignoring"<<endl);
+              continue;
+            }
+
             string h = getHashFromNSEC3(closestEncloser, nsec3);
             if (h.empty()) {
               return INSECURE;
@@ -522,6 +536,11 @@ dState getDenial(const cspmap_t &validrrsets, const DNSName& qname, const uint16
 
             LOG("Comparing "<<toBase32Hex(h)<<" ("<<closestEncloser<<") against "<<toBase32Hex(beginHash)<<endl);
             if(beginHash == h) {
+              if (qtype != QType::DS && isNSEC3AncestorDelegation(signer, v.first.first, nsec3)) {
+                LOG("An ancestor delegation NSEC3 RR can only deny the existence of a DS"<<endl);
+                continue;
+              }
+
               LOG("Closest encloser for "<<qname<<" is "<<closestEncloser<<endl);
               found = true;
               break;
@@ -632,7 +651,7 @@ static const vector<DNSName> getZoneCuts(const DNSName& begin, const DNSName& en
 {
   vector<DNSName> ret;
   if(!begin.isPartOf(end))
-    throw PDNSException(end.toLogString() + "is not part of " + begin.toString());
+    throw PDNSException(end.toLogString() + "is not part of " + begin.toLogString());
 
   DNSName qname(end);
   vector<string> labelsToAdd = begin.makeRelative(end).getRawLabels();
@@ -916,7 +935,7 @@ vState getKeysFor(DNSRecordOracle& dro, const DNSName& zone, skeyset_t& keyset)
         lowestNTA = negAnchor.first;
 
     if(!lowestNTA.empty()) {
-      LOG("Found a Negative Trust Anchor for "<<lowestNTA.toStringRootDot()<<", which was added with reason '"<<negAnchors.at(lowestNTA)<<"', ");
+      LOG("Found a Negative Trust Anchor for "<<lowestNTA<<", which was added with reason '"<<negAnchors.at(lowestNTA)<<"', ");
 
       /* RFC 7646 section 2.1 tells us that we SHOULD still validate if there
        * is a Trust Anchor below the Negative Trust Anchor for the name we
@@ -927,7 +946,7 @@ vState getKeysFor(DNSRecordOracle& dro, const DNSName& zone, skeyset_t& keyset)
         LOG("marking answer Insecure"<<endl);
         return NTA; // Not Insecure, this way validateRecords() can shortcut
       }
-      LOG("but a Trust Anchor for "<<lowestTA.toStringRootDot()<<" is configured, continuing validation."<<endl);
+      LOG("but a Trust Anchor for "<<lowestTA<<" is configured, continuing validation."<<endl);
     }
   }
 
index f6b56eb84d1a2d3491acdeb1cfc698e4e5b4d6a5..9f8b9e7ac2f09779a279a5550d8dbe4cd221fcea 100644 (file)
@@ -1255,8 +1255,6 @@ static void apiServerZones(HttpRequest* req, HttpResponse* resp) {
       if (rr.qtype.getCode() == QType::SOA && rr.qname==zonename) {
         have_soa = true;
         increaseSOARecord(rr, soa_edit_api_kind, soa_edit_kind);
-        // fixup dots after serializeSOAData/increaseSOARecord
-        rr.content = makeBackendRecordContent(rr.qtype, rr.content);
       }
       if (rr.qtype.getCode() == QType::NS && rr.qname==zonename) {
         have_zone_ns = true;
@@ -1271,18 +1269,16 @@ static void apiServerZones(HttpRequest* req, HttpResponse* resp) {
 
     if (!have_soa && zonekind != DomainInfo::Slave) {
       // synthesize a SOA record so the zone "really" exists
-      string soa = (boost::format("%s %s %lu")
+      string soa = (boost::format("%s %s %ul")
         % ::arg()["default-soa-name"]
         % (::arg().isEmpty("default-soa-mail") ? (DNSName("hostmaster.") + zonename).toString() : ::arg()["default-soa-mail"])
         % document["serial"].int_value()
       ).str();
       SOAData sd;
       fillSOAData(soa, sd);  // fills out default values for us
-      autorr.qtype = "SOA";
-      autorr.content = serializeSOAData(sd);
+      autorr.qtype = QType::SOA;
+      autorr.content = makeSOAContent(sd)->getZoneRepresentation(true);
       increaseSOARecord(autorr, soa_edit_api_kind, soa_edit_kind);
-      // fixup dots after serializeSOAData/increaseSOARecord
-      autorr.content = makeBackendRecordContent(autorr.qtype, autorr.content);
       new_records.push_back(autorr);
     }
 
@@ -1299,7 +1295,7 @@ static void apiServerZones(HttpRequest* req, HttpResponse* resp) {
       } catch (...) {
         throw ApiException("Unable to parse DNS Name for NS '" + nameserver + "'");
       }
-      autorr.qtype = "NS";
+      autorr.qtype = QType::NS;
       new_records.push_back(autorr);
       if (have_zone_ns) {
         throw ApiException("Nameservers list MUST NOT be mixed with zone-level NS in rrsets");
@@ -1557,16 +1553,7 @@ static void storeChangedPTRs(UeberBackend& B, vector<DNSResourceRecord>& new_ptr
     sd.db->getDomainMetadataOne(sd.qname, "SOA-EDIT-API", soa_edit_api_kind);
     sd.db->getDomainMetadataOne(sd.qname, "SOA-EDIT", soa_edit_kind);
     if (!soa_edit_api_kind.empty()) {
-      soarr.qname = sd.qname;
-      soarr.content = serializeSOAData(sd);
-      soarr.qtype = "SOA";
-      soarr.domain_id = sd.domain_id;
-      soarr.auth = 1;
-      soarr.ttl = sd.ttl;
-      increaseSOARecord(soarr, soa_edit_api_kind, soa_edit_kind);
-      // fixup dots after serializeSOAData/increaseSOARecord
-      soarr.content = makeBackendRecordContent(soarr.qtype, soarr.content);
-      soa_changed = true;
+      soa_changed = makeIncreasedSOARecord(sd, soa_edit_api_kind, soa_edit_kind, soarr);
     }
 
     sd.db->startTransaction(sd.qname);
@@ -1652,7 +1639,6 @@ static void patchZone(HttpRequest* req, HttpResponse* resp) {
             rr.domain_id = di.id;
             if (rr.qtype.getCode() == QType::SOA && rr.qname==zonename) {
               soa_edit_done = increaseSOARecord(rr, soa_edit_api_kind, soa_edit_kind);
-              rr.content = makeBackendRecordContent(rr.qtype, rr.content);
             }
           }
           checkDuplicateRecords(new_records);
@@ -1694,22 +1680,14 @@ static void patchZone(HttpRequest* req, HttpResponse* resp) {
     // edit SOA (if needed)
     if (!soa_edit_api_kind.empty() && !soa_edit_done) {
       SOAData sd;
-      if (!B.getSOA(zonename, sd))
+      if (!B.getSOAUncached(zonename, sd, true))
         throw ApiException("No SOA found for domain '"+zonename.toString()+"'");
 
       DNSResourceRecord rr;
-      rr.qname = zonename;
-      rr.content = serializeSOAData(sd);
-      rr.qtype = "SOA";
-      rr.domain_id = di.id;
-      rr.auth = 1;
-      rr.ttl = sd.ttl;
-      increaseSOARecord(rr, soa_edit_api_kind, soa_edit_kind);
-      // fixup dots after serializeSOAData/increaseSOARecord
-      rr.content = makeBackendRecordContent(rr.qtype, rr.content);
-
-      if (!di.backend->replaceRRSet(di.id, rr.qname, rr.qtype, vector<DNSResourceRecord>(1, rr))) {
-        throw ApiException("Hosting backend does not support editing records.");
+      if (makeIncreasedSOARecord(sd, soa_edit_api_kind, soa_edit_kind, rr)) {
+        if (!di.backend->replaceRRSet(di.id, rr.qname, rr.qtype, vector<DNSResourceRecord>(1, rr))) {
+          throw ApiException("Hosting backend does not support editing records.");
+        }
       }
 
       // return old and new serials in headers
index fb48f431811e8572c7c22b040aee291eac384eeb..b8f10c7c228a84211683855a6e3e4d133b0fbc93 100644 (file)
@@ -117,7 +117,7 @@ static void fillZone(const DNSName& zonename, HttpResponse* resp)
 {
   auto iter = SyncRes::t_sstorage.domainmap->find(zonename);
   if (iter == SyncRes::t_sstorage.domainmap->end())
-    throw ApiException("Could not find domain '"+zonename.toString()+"'");
+    throw ApiException("Could not find domain '"+zonename.toLogString()+"'");
 
   const SyncRes::AuthDomain& zone = iter->second;
 
@@ -296,7 +296,7 @@ static void apiServerZoneDetail(HttpRequest* req, HttpResponse* resp)
 
   SyncRes::domainmap_t::const_iterator iter = SyncRes::t_sstorage.domainmap->find(zonename);
   if (iter == SyncRes::t_sstorage.domainmap->end())
-    throw ApiException("Could not find domain '"+zonename.toString()+"'");
+    throw ApiException("Could not find domain '"+zonename.toLogString()+"'");
 
   if(req->method == "PUT" && !::arg().mustDo("api-readonly")) {
     Json document = req->json();
diff --git a/pdns/xpf.cc b/pdns/xpf.cc
new file mode 100644 (file)
index 0000000..270428b
--- /dev/null
@@ -0,0 +1,116 @@
+/*
+ * 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.
+ */
+
+#include "xpf.hh"
+
+std::string generateXPFPayload(bool tcp, const ComboAddress& source, const ComboAddress& destination)
+{
+  if (source.sin4.sin_family != destination.sin4.sin_family) {
+    throw std::runtime_error("The XPF destination and source addresses must be of the same family");
+  }
+
+  std::string ret;
+  const uint8_t version = source.isIPv4() ? 4 : 6;
+  const uint8_t protocol = tcp ? 6 : 17;
+  const size_t addrSize = source.isIPv4() ? sizeof(source.sin4.sin_addr.s_addr) : sizeof(source.sin6.sin6_addr.s6_addr);
+  const uint16_t sourcePort = source.sin4.sin_port;
+  const uint16_t destinationPort = destination.sin4.sin_port;
+
+  ret.reserve(sizeof(version) + sizeof(protocol) + (addrSize * 2) + sizeof(sourcePort) + sizeof(destinationPort));
+
+  ret.append(reinterpret_cast<const char*>(&version), sizeof(version));
+  ret.append(reinterpret_cast<const char*>(&protocol), sizeof(protocol));
+
+  // We already established source and destination sin_family equivalence
+  if (source.isIPv4()) {
+    assert(addrSize == sizeof(source.sin4.sin_addr.s_addr));
+    ret.append(reinterpret_cast<const char*>(&source.sin4.sin_addr.s_addr), addrSize);
+    assert(addrSize == sizeof(destination.sin4.sin_addr.s_addr));
+    ret.append(reinterpret_cast<const char*>(&destination.sin4.sin_addr.s_addr), addrSize);
+  }
+  else {
+    assert(addrSize == sizeof(source.sin6.sin6_addr.s6_addr));
+    ret.append(reinterpret_cast<const char*>(&source.sin6.sin6_addr.s6_addr), addrSize);
+    assert(addrSize == sizeof(destination.sin6.sin6_addr.s6_addr));
+    ret.append(reinterpret_cast<const char*>(&destination.sin6.sin6_addr.s6_addr), addrSize);
+  }
+
+  ret.append(reinterpret_cast<const char*>(&sourcePort), sizeof(sourcePort));
+  ret.append(reinterpret_cast<const char*>(&destinationPort), sizeof(destinationPort));
+
+  return ret;
+}
+
+bool parseXPFPayload(const char* payload, size_t len, ComboAddress& source, ComboAddress* destination)
+{
+  static const size_t addr4Size = sizeof(source.sin4.sin_addr.s_addr);
+  static const size_t addr6Size = sizeof(source.sin6.sin6_addr.s6_addr);
+  uint8_t version;
+  uint8_t protocol;
+  uint16_t sourcePort;
+  uint16_t destinationPort;
+
+  if (len != (sizeof(version) + sizeof(protocol) + (addr4Size * 2) + sizeof(sourcePort) + sizeof(destinationPort)) &&
+      len != (sizeof(version) + sizeof(protocol) + (addr6Size * 2) + sizeof(sourcePort) + sizeof(destinationPort))) {
+    return false;
+  }
+
+  size_t pos = 0;
+
+  memcpy(&version, payload + pos, sizeof(version));
+  pos += sizeof(version);
+
+  if (version != 4 && version != 6) {
+    return false;
+  }
+
+  memcpy(&protocol, payload + pos, sizeof(protocol));
+  pos += sizeof(protocol);
+
+  if (protocol != 6 && protocol != 17) {
+    return false;
+  }
+
+  const size_t addrSize = version == 4 ? sizeof(source.sin4.sin_addr.s_addr) : sizeof(source.sin6.sin6_addr.s6_addr);
+  if (len - pos != ((addrSize * 2) + sizeof(sourcePort) + sizeof(destinationPort))) {
+    return false;
+  }
+
+  source = makeComboAddressFromRaw(version, payload + pos, addrSize);
+  pos += addrSize;
+  if (destination != nullptr) {
+    *destination = makeComboAddressFromRaw(version, payload + pos, addrSize);
+  }
+  pos += addrSize;
+
+  memcpy(&sourcePort, payload + pos, sizeof(sourcePort));
+  pos += sizeof(sourcePort);
+  source.sin4.sin_port = sourcePort;
+
+  memcpy(&destinationPort, payload + pos, sizeof(destinationPort));
+  pos += sizeof(destinationPort);
+  if (destination != nullptr) {
+    destination->sin4.sin_port = destinationPort;
+  }
+
+  return true;
+}
diff --git a/pdns/xpf.hh b/pdns/xpf.hh
new file mode 100644 (file)
index 0000000..4e8f466
--- /dev/null
@@ -0,0 +1,29 @@
+/*
+ * 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 <iputils.hh>
+
+std::string generateXPFPayload(bool tcp, const ComboAddress& source, const ComboAddress& destination);
+bool parseXPFPayload(const char* payload, size_t len, ComboAddress& source, ComboAddress* destination);
+
index 913063c59aebf366e33b34430875e5b30bdac5d5..ad7039531b956efa482dec280df4934aa428edeb 100644 (file)
@@ -426,7 +426,7 @@ bool ZoneParserTNG::get(DNSResourceRecord& rr, std::string* comment)
         try {
           recparts[1] = toCanonic(d_zonename, recparts[1]).toStringRootDot();
         } catch (std::exception &e) {
-          throw PDNSException("Error in record '" + rr.qname.toString() + " " + rr.qtype.getName() + "': " + e.what());
+          throw PDNSException("Error in record '" + rr.qname.toLogString() + " " + rr.qtype.getName() + "': " + e.what());
         }
       }
       rr.content=recparts[0]+" "+recparts[1];
@@ -449,7 +449,7 @@ bool ZoneParserTNG::get(DNSResourceRecord& rr, std::string* comment)
         try {
           recparts[3] = toCanonic(d_zonename, recparts[3]).toStringRootDot();
         } catch (std::exception &e) {
-          throw PDNSException("Error in record '" + rr.qname.toString() + " " + rr.qtype.getName() + "': " + e.what());
+          throw PDNSException("Error in record '" + rr.qname.toLogString() + " " + rr.qtype.getName() + "': " + e.what());
         }
       }
       rr.content=recparts[0]+" "+recparts[1]+" "+recparts[2]+" "+recparts[3];
@@ -464,7 +464,7 @@ bool ZoneParserTNG::get(DNSResourceRecord& rr, std::string* comment)
     try {
       rr.content = toCanonic(d_zonename, rr.content).toStringRootDot();
     } catch (std::exception &e) {
-      throw PDNSException("Error in record '" + rr.qname.toString() + " " + rr.qtype.getName() + "': " + e.what());
+      throw PDNSException("Error in record '" + rr.qname.toLogString() + " " + rr.qtype.getName() + "': " + e.what());
     }
     break;
   case QType::AFSDB:
@@ -473,10 +473,10 @@ bool ZoneParserTNG::get(DNSResourceRecord& rr, std::string* comment)
       try {
         recparts[1]=toCanonic(d_zonename, recparts[1]).toStringRootDot();
       } catch (std::exception &e) {
-        throw PDNSException("Error in record '" + rr.qname.toString() + " " + rr.qtype.getName() + "': " + e.what());
+        throw PDNSException("Error in record '" + rr.qname.toLogString() + " " + rr.qtype.getName() + "': " + e.what());
       }
     } else {
-      throw PDNSException("AFSDB record for "+rr.qname.toString()+" invalid");
+      throw PDNSException("AFSDB record for "+rr.qname.toLogString()+" invalid");
     }
     rr.content.clear();
     for(string::size_type n = 0; n < recparts.size(); ++n) {
@@ -489,13 +489,13 @@ bool ZoneParserTNG::get(DNSResourceRecord& rr, std::string* comment)
   case QType::SOA:
     stringtok(recparts, rr.content);
     if(recparts.size() > 7)
-      throw PDNSException("SOA record contents for "+rr.qname.toString()+" contains too many parts");
+      throw PDNSException("SOA record contents for "+rr.qname.toLogString()+" contains too many parts");
     if(recparts.size() > 1) {
       try {
         recparts[0]=toCanonic(d_zonename, recparts[0]).toStringRootDot();
         recparts[1]=toCanonic(d_zonename, recparts[1]).toStringRootDot();
       } catch (std::exception &e) {
-        throw PDNSException("Error in record '" + rr.qname.toString() + " " + rr.qtype.getName() + "': " + e.what());
+        throw PDNSException("Error in record '" + rr.qname.toLogString() + " " + rr.qtype.getName() + "': " + e.what());
       }
     }
     rr.content.clear();
index a3d8bd19d5a7906f3126396237d2d0e5ad90e7aa..ece4a0df23c2a36a609e2b6bc97e48ec85cab3c9 100644 (file)
@@ -140,6 +140,36 @@ class AuthZones(ApiTestCase, AuthZonesHelperMixin):
         print data
         self.assertEquals(data['soa_edit_api'], 'DEFAULT')
 
+    def test_create_zone_with_soa_edit(self):
+        name, payload, data = self.create_zone(soa_edit='INCEPTION-INCREMENT', soa_edit_api='SOA-EDIT-INCREASE')
+        print data
+        self.assertEquals(data['soa_edit'], 'INCEPTION-INCREMENT')
+        self.assertEquals(data['soa_edit_api'], 'SOA-EDIT-INCREASE')
+        soa_serial = get_first_rec(data, name, 'SOA')['content'].split(' ')[2]
+        # These particular settings lead to the first serial set to YYYYMMDD01.
+        self.assertEquals(soa_serial[-2:], '01')
+        rrset = {
+            'changetype': 'replace',
+            'name': name,
+            'type': 'A',
+            'ttl': 3600,
+            'records': [
+                {
+                    "content": "127.0.0.1",
+                    "disabled": False
+                }
+            ]
+        }
+        payload = {'rrsets': [rrset]}
+        self.session.patch(
+            self.url("/api/v1/servers/localhost/zones/" + data['id']),
+            data=json.dumps(payload),
+            headers={'content-type': 'application/json'})
+        r = self.session.get(self.url("/api/v1/servers/localhost/zones/" + data['id']))
+        data = r.json()
+        soa_serial = get_first_rec(data, name, 'SOA')['content'].split(' ')[2]
+        self.assertEquals(soa_serial[-2:], '02')
+
     def test_create_zone_with_records(self):
         name = unique_zone_name()
         rrset = {
diff --git a/regression-tests.dnsdist/configCA.conf b/regression-tests.dnsdist/configCA.conf
new file mode 100644 (file)
index 0000000..fa5d736
--- /dev/null
@@ -0,0 +1,20 @@
+[req]
+default_bits = 2048
+encrypt_key = no
+x509_extensions = custom_extensions
+prompt = no
+distinguished_name = distinguished_name
+
+[v3_ca]
+subjectKeyIdentifier = hash
+authorityKeyIdentifier = keyid:always,issuer:always
+basicConstraints = critical, CA:true
+
+[distinguished_name]
+CN = DNSDist TLS regression tests CA
+OU = PowerDNS.com BV
+countryName = NL
+
+[custom_extensions]
+basicConstraints = CA:true
+keyUsage = cRLSign, keyCertSign
diff --git a/regression-tests.dnsdist/configServer.conf b/regression-tests.dnsdist/configServer.conf
new file mode 100644 (file)
index 0000000..030cd59
--- /dev/null
@@ -0,0 +1,11 @@
+[req]
+default_bits = 2048
+encrypt_key = no
+prompt = no
+distinguished_name = server_distinguished_name
+
+[server_distinguished_name]
+CN = tls.tests.dnsdist.org
+OU = PowerDNS.com BV
+countryName = NL
+
index bbb6d117a9d05c013514b224bd49bd41f357aa43..95a7d1374ff22cac043c4406b185776290139549 100644 (file)
@@ -4,6 +4,7 @@ import copy
 import Queue
 import os
 import socket
+import ssl
 import struct
 import subprocess
 import sys
@@ -251,17 +252,36 @@ class DNSDistTest(unittest.TestCase):
         return sock
 
     @classmethod
-    def sendTCPQueryOverConnection(cls, sock, query, rawQuery=False):
+    def openTLSConnection(cls, port, serverName, caCert=None, timeout=None):
+        sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+        if timeout:
+            sock.settimeout(timeout)
+
+        # 2.7.9+
+        if hasattr(ssl, 'create_default_context'):
+            sslctx = ssl.create_default_context(cafile=caCert)
+            sslsock = sslctx.wrap_socket(sock, server_hostname=serverName)
+        else:
+            sslsock = ssl.wrap_socket(sock, ca_certs=caCert, cert_reqs=ssl.CERT_REQUIRED)
+
+        sslsock.connect(("127.0.0.1", port))
+        return sslsock
+
+    @classmethod
+    def sendTCPQueryOverConnection(cls, sock, query, rawQuery=False, response=None, timeout=2.0):
         if not rawQuery:
             wire = query.to_wire()
         else:
             wire = query
 
+        if response:
+            cls._toResponderQueue.put(response, True, timeout)
+
         sock.send(struct.pack("!H", len(wire)))
         sock.send(wire)
 
     @classmethod
-    def recvTCPResponseOverConnection(cls, sock):
+    def recvTCPResponseOverConnection(cls, sock, useQueue=False, timeout=2.0):
         message = None
         data = sock.recv(2)
         if data:
@@ -269,7 +289,12 @@ class DNSDistTest(unittest.TestCase):
             data = sock.recv(datalen)
             if data:
                 message = dns.message.from_wire(data)
-        return message
+
+        if useQueue and not cls._fromResponderQueue.empty():
+            receivedQuery = cls._fromResponderQueue.get(True, timeout)
+            return (receivedQuery, message)
+        else:
+            return message
 
     @classmethod
     def sendTCPQuery(cls, query, response, useQueue=True, timeout=2.0, rawQuery=False):
index 6bf1b906abd7cf60f313d4660f7306b069349d8f..f6803132a32e6789c75a1308cea9bc8bd41aec23 100755 (executable)
@@ -20,4 +20,16 @@ set -e
 if [ "${PDNS_DEBUG}" = "YES" ]; then
   set -x
 fi
+
+# Generate a new CA
+openssl req -new -x509 -days 1 -extensions v3_ca -keyout ca.key -out ca.pem -nodes -config configCA.conf
+# Generate a new server certificate request
+openssl req -new -newkey rsa:2048 -nodes -keyout server.key -out server.csr -config configServer.conf
+# Sign the server cert
+openssl x509 -req -days 1 -CA ca.pem -CAkey ca.key -CAcreateserial -in server.csr -out server.pem
+# Generate a chain
+cat server.pem ca.pem >> server.chain
+
 nosetests --with-xunit $@
+
+rm ca.key ca.pem ca.srl server.csr server.key server.pem server.chain
index c1d43b07695cd2cccaa6172724756470661ff92d..caf2ff6ad7cb3616e5f706905187f1595a0fa867 100644 (file)
@@ -78,26 +78,22 @@ class TestAPIBasics(DNSDistTest):
 
         self.assertEquals(content['daemon_type'], 'dnsdist')
 
-        for key in ['version', 'acl', 'local', 'rules', 'response-rules', 'cache-hit-response-rules', 'servers', 'frontends', 'pools']:
+        rule_groups = ['response-rules', 'cache-hit-response-rules', 'self-answered-response-rules']
+        for key in ['version', 'acl', 'local', 'rules', 'servers', 'frontends', 'pools'] + rule_groups:
             self.assertIn(key, content)
 
         for rule in content['rules']:
-            for key in ['id', 'matches', 'rule', 'action']:
+            for key in ['id', 'matches', 'rule', 'action', 'uuid']:
                 self.assertIn(key, rule)
             for key in ['id', 'matches']:
                 self.assertTrue(rule[key] >= 0)
 
-        for rule in content['response-rules']:
-            for key in ['id', 'matches', 'rule', 'action']:
-                self.assertIn(key, rule)
-            for key in ['id', 'matches']:
-                self.assertTrue(rule[key] >= 0)
-
-        for rule in content['cache-hit-response-rules']:
-            for key in ['id', 'matches', 'rule', 'action']:
-                self.assertIn(key, rule)
-            for key in ['id', 'matches']:
-                self.assertTrue(rule[key] >= 0)
+        for rule_group in rule_groups:
+            for rule in content[rule_group]:
+                for key in ['id', 'matches', 'rule', 'action', 'uuid']:
+                    self.assertIn(key, rule)
+                for key in ['id', 'matches']:
+                    self.assertTrue(rule[key] >= 0)
 
         for server in content['servers']:
             for key in ['id', 'latency', 'name', 'weight', 'outstanding', 'qpsLimit',
index 09763e515a4114b9f5c25edf9a73fd50274ec005..bab9e729189f8ad93176b79266b641fd4c9f2c91 100644 (file)
@@ -1516,3 +1516,45 @@ class TestAdvancedGetLocalPortOnAnyBind(DNSDistTest):
 
         (_, receivedResponse) = self.sendTCPQuery(query, response=None, useQueue=False)
         self.assertEquals(receivedResponse, response)
+
+class TestAdvancedLuaTempFailureTTL(DNSDistTest):
+
+    _config_template = """
+    function testAction(dq)
+      if dq.tempFailureTTL ~= nil then
+        return DNSAction.Spoof, "initially.not.nil.but." + dq.tempFailureTTL + ".tests.powerdns.com."
+      end
+      dq.tempFailureTTL = 30
+      if dq.tempFailureTTL ~= 30 then
+        return DNSAction.Spoof, "after.set.not.expected.value.but." + dq.tempFailureTTL + ".tests.powerdns.com."
+      end
+      dq.tempFailureTTL = nil
+      if dq.tempFailureTTL ~= nil then
+        return DNSAction.Spoof, "after.unset.not.nil.but." + dq.tempFailureTTL + ".tests.powerdns.com."
+      end
+      return DNSAction.None, ""
+    end
+    addAction(AllRule(), LuaAction(testAction))
+    newServer{address="127.0.0.1:%s"}
+    """
+
+    def testTempFailureTTLBinding(self):
+        """
+        Exercise dq.tempFailureTTL Lua binding
+        """
+        name = 'tempfailurettlbinding.advanced.tests.powerdns.com.'
+        query = dns.message.make_query(name, 'A', 'IN')
+        response = dns.message.make_response(query)
+        rrset = dns.rrset.from_text(name,
+                                    3600,
+                                    dns.rdataclass.IN,
+                                    dns.rdatatype.AAAA,
+                                    '::1')
+        response.answer.append(rrset)
+
+        (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
+        self.assertTrue(receivedQuery)
+        self.assertTrue(receivedResponse)
+        receivedQuery.id = query.id
+        self.assertEquals(query, receivedQuery)
+        self.assertEquals(receivedResponse, response)
index 175d4166c0ea64362241f01cbb7c906cdbe978d0..fab7372799414adcf0c77a34f485eabb4eb670fc 100644 (file)
@@ -354,6 +354,52 @@ class TestCaching(DNSDistTest):
         self.assertEquals(receivedResponse, differentCaseResponse)
 
 
+class TestTempFailureCacheTTLAction(DNSDistTest):
+
+    _config_template = """
+    pc = newPacketCache(100, 86400, 1)
+    getPool(""):setCache(pc)
+    addAction("servfail.cache.tests.powerdns.com.", TempFailureCacheTTLAction(1))
+    newServer{address="127.0.0.1:%s"}
+    """
+
+    def testTempFailureCacheTTLAction(self):
+        """
+        Cache: When a TempFailure TTL is set, it should be honored
+
+        dnsdist is configured to cache packets, plus a specific qname is
+        set up with a lower TempFailure Cache TTL. we are sending one request
+        (cache miss) and verify that the cache is hit for the following query,
+        but the TTL then expires before the larger "good" packetcache TTL.
+        """
+        name = 'servfail.cache.tests.powerdns.com.'
+        query = dns.message.make_query(name, 'AAAA', 'IN')
+        response = dns.message.make_response(query)
+        response.set_rcode(dns.rcode.SERVFAIL)
+
+        (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
+        self.assertTrue(receivedQuery)
+        self.assertTrue(receivedResponse)
+        receivedQuery.id = query.id
+        self.assertEquals(query, receivedQuery)
+        self.assertEquals(receivedResponse, response)
+
+        # next query should hit the cache
+        (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response=None, useQueue=False)
+        self.assertFalse(receivedQuery)
+        self.assertTrue(receivedResponse)
+        self.assertEquals(receivedResponse, response)
+
+        # now we wait a bit for the Failure-Cache TTL to expire
+        time.sleep(2)
+
+        # next query should NOT hit the cache
+        (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
+        self.assertTrue(receivedQuery)
+        self.assertTrue(receivedResponse)
+        self.assertEquals(receivedResponse, response)
+
+
 class TestCachingWithExistingEDNS(DNSDistTest):
 
     _config_template = """
index 747837f65ff5cbbc5f8f726a8af5e85c51d8e175..d2e0b9b063f7a0d47e10ac3073c615f7a0f70cae 100644 (file)
@@ -3,6 +3,7 @@ import dns
 import clientsubnetoption
 import cookiesoption
 from dnsdisttests import DNSDistTest
+from datetime import datetime, timedelta
 
 class TestEdnsClientSubnetNoOverride(DNSDistTest):
     """
index a1f9072929c01b92beb2b17ef8317f88e963c224..c3df9450749588ff9627b6ef14fa348cbf1e33b3 100644 (file)
@@ -53,6 +53,57 @@ class TestResponseRuleNXDelayed(DNSDistTest):
         self.assertEquals(response, receivedResponse)
         self.assertTrue((end - begin) < timedelta(0, 1))
 
+class TestResponseRuleERCode(DNSDistTest):
+
+    _config_template = """
+    newServer{address="127.0.0.1:%s"}
+    addResponseAction(ERCodeRule(dnsdist.BADVERS), DelayResponseAction(1000))
+    """
+
+    def testBADVERSDelayed(self):
+        """
+        Responses: Delayed on BADVERS
+
+        Send an A query to "delayed.responses.tests.powerdns.com.",
+        check that the response delay is longer than 1000 ms
+        for a BADVERS response over UDP, shorter for BADKEY and NoError.
+        """
+        name = 'delayed.responses.tests.powerdns.com.'
+        query = dns.message.make_query(name, 'A', 'IN')
+        response = dns.message.make_response(query)
+        response.use_edns(edns=True)
+
+        # BADVERS over UDP
+        # BADVERS == 16, so rcode==0, ercode==1
+        response.set_rcode(dns.rcode.BADVERS)
+        begin = datetime.now()
+        (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
+        end = datetime.now()
+        receivedQuery.id = query.id
+        self.assertEquals(query, receivedQuery)
+        self.assertEquals(response, receivedResponse)
+        self.assertTrue((end - begin) > timedelta(0, 1))
+
+        # BADKEY (17, an ERCode) over UDP
+        response.set_rcode(17)
+        begin = datetime.now()
+        (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
+        end = datetime.now()
+        receivedQuery.id = query.id
+        self.assertEquals(query, receivedQuery)
+        self.assertEquals(response, receivedResponse)
+        self.assertTrue((end - begin) < timedelta(0, 1))
+
+        # NoError (non-ERcode, basic RCode bits match BADVERS) over UDP
+        response.set_rcode(dns.rcode.NOERROR)
+        begin = datetime.now()
+        (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
+        end = datetime.now()
+        receivedQuery.id = query.id
+        self.assertEquals(query, receivedQuery)
+        self.assertEquals(response, receivedResponse)
+        self.assertTrue((end - begin) < timedelta(0, 1))
+
 class TestResponseRuleQNameDropped(DNSDistTest):
 
     _config_template = """
diff --git a/regression-tests.dnsdist/test_SelfAnsweredResponses.py b/regression-tests.dnsdist/test_SelfAnsweredResponses.py
new file mode 100644 (file)
index 0000000..843e2bd
--- /dev/null
@@ -0,0 +1,64 @@
+#!/usr/bin/env python
+import dns
+from dnsdisttests import DNSDistTest
+
+class TestSelfAnsweredResponses(DNSDistTest):
+
+    _config_template = """
+    -- this is a silly test config, please do not do this in production.
+    addAction(makeRule("udp.selfanswered.tests.powerdns.com."), SpoofAction("192.0.2.1"))
+    addSelfAnsweredResponseAction(AndRule({makeRule("udp.selfanswered.tests.powerdns.com."), NotRule(MaxQPSRule(1))}), DropResponseAction())
+    addAction(makeRule("tcp.selfanswered.tests.powerdns.com."), SpoofAction("192.0.2.1"))
+    addSelfAnsweredResponseAction(AndRule({makeRule("tcp.selfanswered.tests.powerdns.com."), NotRule(MaxQPSRule(1))}), DropResponseAction())
+    newServer{address="127.0.0.1:%s"}
+    """
+
+    def testSelfAnsweredUDP(self):
+        """
+        CacheHitResponse: Drop when served from the cache
+        """
+        ttl = 60
+        name = 'udp.selfanswered.tests.powerdns.com.'
+        query = dns.message.make_query(name, 'A', 'IN')
+        response = dns.message.make_response(query)
+        rrset = dns.rrset.from_text(name,
+                                    ttl,
+                                    dns.rdataclass.IN,
+                                    dns.rdatatype.A,
+                                    '192.0.2.1')
+        response.answer.append(rrset)
+        response.flags |= dns.flags.RA
+
+        # self-answered, but no SelfAnswered rule matches.
+        (_, receivedResponse) = self.sendUDPQuery(query, response=None, useQueue=False)
+        self.assertTrue(receivedResponse)
+        self.assertEquals(receivedResponse, response)
+
+        # self-answered, AND SelfAnswered rule matches. Should not see a reply.
+        (_, receivedResponse) = self.sendUDPQuery(query, response=None, useQueue=False)
+        self.assertIsNone(receivedResponse)
+
+    def testSelfAnsweredTCP(self):
+        """
+        testSelfAnsweredTCP: Drop after exceeding QPS
+        """
+        ttl = 60
+        name = 'tcp.selfanswered.tests.powerdns.com.'
+        query = dns.message.make_query(name, 'A', 'IN')
+        response = dns.message.make_response(query)
+        rrset = dns.rrset.from_text(name,
+                                    ttl,
+                                    dns.rdataclass.IN,
+                                    dns.rdatatype.A,
+                                    '192.0.2.1')
+        response.answer.append(rrset)
+        response.flags |= dns.flags.RA
+
+        # self-answered, but no SelfAnswered rule matches.
+        (_, receivedResponse) = self.sendTCPQuery(query, response=None, useQueue=False)
+        self.assertTrue(receivedResponse)
+        self.assertEquals(receivedResponse, response)
+
+        # self-answered, AND SelfAnswered rule matches. Should not see a reply.
+        (_, receivedResponse) = self.sendTCPQuery(query, response=None, useQueue=False)
+        self.assertIsNone(receivedResponse)
diff --git a/regression-tests.dnsdist/test_TLS.py b/regression-tests.dnsdist/test_TLS.py
new file mode 100644 (file)
index 0000000..68aaa83
--- /dev/null
@@ -0,0 +1,40 @@
+#!/usr/bin/env python
+import dns
+from dnsdisttests import DNSDistTest
+
+class TestTLS(DNSDistTest):
+
+    _serverKey = 'server.key'
+    _serverCert = 'server.chain'
+    _serverName = 'tls.tests.dnsdist.org'
+    _caCert = 'ca.pem'
+    _tlsServerPort = 8453
+    _config_template = """
+    newServer{address="127.0.0.1:%s"}
+    addTLSLocal("127.0.0.1:%s", "%s", "%s")
+    """
+    _config_params = ['_testServerPort', '_tlsServerPort', '_serverCert', '_serverKey']
+
+    def testTLSSimple(self):
+        """
+        TLS: Single query
+        """
+        name = 'single.tls.tests.powerdns.com.'
+        query = dns.message.make_query(name, 'A', 'IN', use_edns=False)
+        response = dns.message.make_response(query)
+        rrset = dns.rrset.from_text(name,
+                                    3600,
+                                    dns.rdataclass.IN,
+                                    dns.rdatatype.A,
+                                    '127.0.0.1')
+        response.answer.append(rrset)
+
+        conn = self.openTLSConnection(self._tlsServerPort, self._serverName, self._caCert)
+
+        self.sendTCPQueryOverConnection(conn, query, response=response)
+        (receivedQuery, receivedResponse) = self.recvTCPResponseOverConnection(conn, useQueue=True)
+        self.assertTrue(receivedQuery)
+        self.assertTrue(receivedResponse)
+        receivedQuery.id = query.id
+        self.assertEquals(query, receivedQuery)
+        self.assertEquals(response, receivedResponse)
diff --git a/regression-tests.dnsdist/test_XPF.py b/regression-tests.dnsdist/test_XPF.py
new file mode 100644 (file)
index 0000000..1ee8652
--- /dev/null
@@ -0,0 +1,77 @@
+#!/usr/bin/env python
+
+import dns
+from dnsdisttests import DNSDistTest
+
+class XPFTest(DNSDistTest):
+    """
+    dnsdist is configured to add XPF to the query
+    """
+
+    _xpfCode = 65422
+    _config_template = """
+    newServer{address="127.0.0.1:%d", addXPF=%d}
+    """
+    _config_params = ['_testServerPort', '_xpfCode']
+
+    def checkMessageHasXPF(self, msg, expectedValue):
+        self.assertGreaterEqual(len(msg.additional), 1)
+
+        found = False
+        for add in msg.additional:
+            if add.rdtype == self._xpfCode:
+                found = True
+                self.assertEquals(add.rdclass, dns.rdataclass.IN)
+                self.assertEquals(add.ttl, 0)
+                xpfData = add.to_rdataset()[0].to_text()
+                # skip the ports
+                self.assertEquals(xpfData[:26], expectedValue[:26])
+
+        self.assertTrue(found)
+
+    def testXPF(self):
+        """
+        XPF
+        """
+        name = 'xpf.tests.powerdns.com.'
+        query = dns.message.make_query(name, 'A', 'IN')
+
+        expectedQuery = dns.message.make_query(name, 'A', 'IN')
+        # 0x04 is IPv4, 0x11 (17) is UDP then 127.0.0.1 as source and destination
+        # and finally the ports, zeroed because we have no way to know them beforehand
+        xpfData = "\# 14 04117f0000017f00000100000000"
+        rdata = dns.rdata.from_text(dns.rdataclass.IN, self._xpfCode, xpfData)
+        rrset = dns.rrset.from_rdata(name, 60, rdata)
+        expectedQuery.additional.append(rrset)
+
+        response = dns.message.make_response(expectedQuery)
+
+        (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
+        self.assertTrue(receivedQuery)
+        self.assertTrue(receivedResponse)
+        receivedQuery.id = expectedQuery.id
+        receivedResponse.id = response.id
+
+        self.assertEquals(receivedQuery, expectedQuery)
+        self.checkMessageHasXPF(receivedQuery, xpfData)
+        self.assertEquals(response, receivedResponse)
+
+        expectedQuery = dns.message.make_query(name, 'A', 'IN')
+        # 0x04 is IPv4, 0x06 (6) is TCP then 127.0.0.1 as source and destination
+        # and finally the ports, zeroed because we have no way to know them beforehand
+        xpfData = "\# 14 04067f0000017f00000100000000"
+        rdata = dns.rdata.from_text(dns.rdataclass.IN, self._xpfCode, xpfData)
+        rrset = dns.rrset.from_rdata(name, 60, rdata)
+        expectedQuery.additional.append(rrset)
+
+        response = dns.message.make_response(expectedQuery)
+
+        (receivedQuery, receivedResponse) = self.sendTCPQuery(query, response)
+        self.assertTrue(receivedQuery)
+        self.assertTrue(receivedResponse)
+        receivedQuery.id = expectedQuery.id
+        receivedResponse.id = response.id
+
+        self.assertEquals(receivedQuery, expectedQuery)
+        self.checkMessageHasXPF(receivedQuery, xpfData)
+        self.assertEquals(response, receivedResponse)
index 720477ed2dcce3d6a8adad8d573e9db49e4e31fe..4641df3cf78545b4f85eea90ea41691928d5fdc9 100644 (file)
@@ -695,7 +695,7 @@ distributor-threads=1""".format(confdir=confdir,
         for ans in msg.answer:
             ret += "%s\n" % ans.to_text()
             if ans.match(rrset.name, rrset.rdclass, rrset.rdtype, 0, None):
-                self.assertEqual(ans, rrset)
+                self.assertEqual(ans, rrset, "'%s' != '%s'" % (ans.to_text(), rrset.to_text()))
                 found = True
 
         if not found:
index 840b045f93b4d489476e508fc5f2d4bdf6d70d07..9682f92eb63e31bb7fa610bf117f16a5b90ee141 100755 (executable)
@@ -17,13 +17,29 @@ export PDNS=${PDNS:-${PWD}/../pdns/pdns_server}
 export PDNSUTIL=${PDNSUTIL:-${PWD}/../pdns/pdnsutil}
 export PDNSRECURSOR=${PDNSRECURSOR:-${PWD}/../pdns/recursordist/pdns_recursor}
 export RECCONTROL=${RECCONTROL:-${PWD}/../pdns/recursordist/rec_control}
-export LIBFAKETIME=${LIBFAKETIME:-/usr/lib/x86_64-linux-gnu/faketime/libfaketime.so.1} # ubuntu default
+
+LIBFAKETIME_DEFAULT=/usr/lib/x86_64-linux-gnu/faketime/libfaketime.so.1 # ubuntu default
+LIBAUTHBIND_DEFAULT=/usr/lib/authbind/libauthbind.so.1
+if [ $(uname -s) = "Darwin" ]; then
+  # macOS is not /really/ supported here; it works for some tests, and then you might need sudo.
+  LIBFAKETIME_DEFAULT=/usr/local/lib/faketime/libfaketime.1.dylib
+  LIBAUTHBIND_DEFAULT=""
+fi
+
+export LIBFAKETIME=${LIBFAKETIME:-$LIBFAKETIME_DEFAULT}
+export LIBAUTHBIND=${LIBAUTHBIND:-$LIBAUTHBIND_DEFAULT}
 
 export PREFIX=127.0.0
 
+for bin in "$PDNS" "$PDNSUTIL" "$PDNSRECURSOR" "$RECCONTROL" "$LIBFAKETIME" "$LIBAUTHBIND"; do
+  if [ -n "$bin" -a ! -e "$bin" ]; then
+    echo "E: Required binary $bin not found. Please install the binary and/or edit ./vars."
+    exit 1
+  fi
+done
 
 set -e
 if [ "${PDNS_DEBUG}" = "YES" ]; then
   set -x
 fi
-LD_PRELOAD="/usr/lib/authbind/libauthbind.so.1 ${LIBFAKETIME}" nosetests -I test_WellKnown.py --with-xunit $@
+LD_PRELOAD="${LIBAUTHBIND} ${LIBFAKETIME}" nosetests -I test_WellKnown.py --with-xunit $@
index e3c9e0c616dcf462b94c3ce2038b6a648ba91684..e15f6a86a03fd45158380fb128efedd7d804b1a0 100644 (file)
@@ -19,6 +19,7 @@ class ECSTest(RecursorTest):
 daemon=no
 trace=yes
 dont-query=
+ecs-add-for=0.0.0.0/0
 local-address=127.0.0.1
 packetcache-ttl=0
 packetcache-servfail-ttl=0