BuildRequires: boost-devel
BuildRequires: lua-devel
+BuildRequires: libsodium-devel
BuildRequires: bison
Provides: powerdns = %{version}-%{release}
--with-lua \
--with-dynmodules='bind %{backends} random' \
--enable-tools \
+ --enable-libsodium \
--without-protobuf \
--enable-remotebackend-http \
--enable-unit-tests
BuildRequires: systemd-devel
BuildRequires: boost-devel
BuildRequires: lua-devel
+BuildRequires: libsodium-devel
BuildRequires: bison
BuildRequires: openssl-devel
BuildRequires: protobuf-devel
--with-lua \
--with-dynmodules='%{backends} random' \
--enable-tools \
+ --enable-libsodium \
--enable-unit-tests \
--enable-systemd
Provides: powerdns-recursor = %{version}-%{release}
BuildRequires: boost148-devel
BuildRequires: lua-devel
+BuildRequires: libsodium-devel
#BuildRequires: protobuf-devel
#BuildRequires: protobuf-compiler
--disable-silent-rules \
--with-protobuf \
--enable-unit-tests \
+ --enable-libsodium \
--with-boost=/usr/include/boost148 LIBRARY_PATH=/usr/lib64/boost148
make %{?_smp_mflags} LIBRARY_PATH=/usr/lib64/boost148
BuildRequires: lua-devel
BuildRequires: systemd-units
BuildRequires: systemd-devel
+BuildRequires: libsodium-devel
BuildRequires: hostname
BuildRequires: protobuf-devel
BuildRequires: protobuf-compiler
--disable-silent-rules \
--enable-unit-tests \
--with-protobuf \
+ --enable-libsodium \
--enable-systemd
make %{?_smp_mflags}
Standards-Version: 3.9.8
Maintainer: PowerDNS Autobuilder <powerdns.support@powerdns.com>
Origin: PowerDNS
-Build-Depends: debhelper (>= 9~), dh-autoreconf, dh-systemd, po-debconf, libtool, flex, bison, libmysqlclient-dev, libpq-dev, libssl-dev, libgdbm-dev, libldap2-dev, libsqlite3-dev, dpkg-dev (>= 1.17.0~), libboost-dev, libboost-serialization-dev, libboost-program-options-dev, libboost-test-dev, autotools-dev, automake, autoconf, liblua5.2-dev, pkg-config, ragel, libgmp-dev, libbotan1.10-dev, libcurl4-openssl-dev, libzmq-dev, libyaml-cpp-dev (>= 0.5), libgeoip-dev, libopendbx1-dev, libcdb-dev, unixodbc-dev (>= 2.3.1), libprotobuf-dev, protobuf-compiler @LIBSYSTEMDDEV@
+Build-Depends: debhelper (>= 9~), dh-autoreconf, dh-systemd, po-debconf, libtool, flex, bison, libmysqlclient-dev, libpq-dev, libssl-dev, libgdbm-dev, libldap2-dev, libsqlite3-dev, dpkg-dev (>= 1.17.0~), libboost-dev, libboost-serialization-dev, libboost-program-options-dev, libboost-test-dev, autotools-dev, automake, autoconf, liblua5.2-dev, pkg-config, ragel, libgmp-dev, libbotan1.10-dev, libcurl4-openssl-dev, libzmq-dev, libyaml-cpp-dev (>= 0.5), libgeoip-dev, libopendbx1-dev, libcdb-dev, unixodbc-dev (>= 2.3.1), libprotobuf-dev, protobuf-compiler @LIBSYSTEMDDEV@ @LIBSODIUMDEV@
Homepage: http://www.powerdns.com/
Package: pdns-server
LIBSYSTEMD_DEV := , libsystemd-dev
DEBHELPER_WITH_SYSTEMD := --with systemd
+ENABLE_LIBSODIUM := --enable-libsodium
+LIBSODIUM_DEV := , libsodium-dev
+
# $(ID) and $(VERSION_ID) come from the environment, source this from /etc/os-release
ifeq ($(ID), ubuntu)
ifeq ($(VERSION_ID), 14.04)
ENABLE_SYSTEMD=
LIBSYSTEMD_DEV=
DEBHELPER_WITH_SYSTEMD=
+
+ # Also disable libsodium
+ ENABLE_LIBSODIUM=
+ LIBSODIUM_DEV=
endif
endif
debian/control: debian/control.in
- sed -e "s!@LIBSYSTEMDDEV@!$(LIBSYSTEMD_DEV)!" $< > $@
+ sed -e "s!@LIBSYSTEMDDEV@!$(LIBSYSTEMD_DEV)!" \
+ -e "s!@LIBSODIUMDEV@!$(LIBSODIUM_DEV)!" $< > $@
# Use new build system
%:
--libexecdir='$${prefix}/lib' \
--with-dynmodules="$(backends)" \
--with-modules="" \
- --with-pgsql-includes=`pg_config --includedir` \
--enable-botan1.10 \
--enable-tools \
--enable-unit-tests \
- $(ENABLE_SYSTEMD)
+ $(ENABLE_SYSTEMD) \
+ $(ENABLE_LIBSODIUM)
# pdns-server has a debug package
override_dh_strip:
Priority: optional
Maintainer: PowerDNS Autobuilder <powerdns.support@powerdns.com>
Origin: PowerDNS
-Build-Depends: debhelper (>= 9), dh-autoreconf, dh-systemd (>= 1.5), libboost-dev, libedit-dev, liblua5.2-dev, protobuf-compiler, libprotobuf-def, pkg-config @LIBRE2DEV@ @LIBSODIUMDEV@ @LIBSYSTEMDDEV@
+Build-Depends: debhelper (>= 9), dh-autoreconf, dh-systemd (>= 1.5), libboost-dev, libedit-dev, liblua5.2-dev, protobuf-compiler, libprotobuf-dev, pkg-config @LIBRE2DEV@ @LIBSODIUMDEV@ @LIBSYSTEMDDEV@
Standards-Version: 3.9.7
Homepage: http://dnsdist.org
Standards-Version: 3.9.6
Maintainer: PowerDNS Autobuilder <powerdns.support@powerdns.com>
Origin: PowerDNS
-Build-Depends: debhelper (>= 9~), dh-systemd, quilt, dpkg-dev (>= 1.17.0~), libboost-dev, libboost-serialization-dev, liblua5.2-dev, libprotobuf-dev, protobuf-compiler, pkg-config @LIBSYSTEMDDEV@
+Build-Depends: debhelper (>= 9~), dh-systemd, quilt, dpkg-dev (>= 1.17.0~), libboost-dev, libboost-serialization-dev, liblua5.2-dev, libprotobuf-dev, protobuf-compiler, pkg-config @LIBSYSTEMDDEV@ @LIBSODIUMDEV@
Homepage: http://www.powerdns.com/
Package: pdns-recursor
LIBSYSTEMD_DEV := , libsystemd-dev
DEBHELPER_WITH_SYSTEMD := --with systemd
+ENABLE_LIBSODIUM := --enable-libsodium
+LIBSODIUM_DEV := , libsodium-dev
+
# $(ID) and $(VERSION_ID) come from the environment, source this from /etc/os-release
ifeq ($(ID), ubuntu)
ifeq ($(VERSION_ID), 14.04)
ENABLE_SYSTEMD=
LIBSYSTEMD_DEV=
DEBHELPER_WITH_SYSTEMD=
+
+ # Also disable libsodium
+ ENABLE_LIBSODIUM=
+ LIBSODIUM_DEV=
endif
endif
debian/control: debian/control.in
- sed -e "s!@LIBSYSTEMDDEV@!$(LIBSYSTEMD_DEV)!" $< > $@
+ sed -e "s!@LIBSYSTEMDDEV@!$(LIBSYSTEMD_DEV)!" \
+ -e "s!@LIBSODIUMDEV@!$(LIBSODIUM_DEV)!" $< > $@
# Use new build system
%:
--libexecdir='$${prefix}/lib' \
--with-lua \
--with-protobuf=yes \
- $(ENABLE_SYSTEMD)
+ $(ENABLE_SYSTEMD) \
+ $(ENABLE_LIBSODIUM)
override_dh_auto_install:
./pdns_recursor --config | sed \
[AC_MSG_NOTICE([OpenSSL ecdsa: yes])],
[AC_MSG_NOTICE([OpenSSL ecdsa: no])]
)
+AS_IF([test "x$LIBSODIUM_LIBS" != "x"],
+ [AC_MSG_NOTICE([libsodium ed25519: yes])],
+ [AC_MSG_NOTICE([libsodium ed25519: no])]
+)
AS_IF([test "x$needsqlite3" != "x"],
[AC_MSG_NOTICE([SQLite3: yes])],
[AC_MSG_NOTICE([SQLite3: no])]
#
# example usage:
# docs/$ docker build -t pdns-mkdocs .
-# docs/$ cd ../.. # to pdns checkout root
-# pdns/$ docker run -t -i -v $(pwd):/pdns pdns-mkdocs bash
+# docs/$ docker run -t -i -v $(pwd)/..:/pdns pdns-mkdocs bash
# root@07d38c7f88a5:/# useradd -u 1000 peter
# root@07d38c7f88a5:/# su - peter
# No directory, logging in with HOME=/
|DNSSEC|Yes (set `gpgsql-dnssec`)|
|Disabled data|Yes (v3.4.0 and up)|
|Comments|Yes (v3.4.0 and up)|
-|Module name | pgsql|
-|Launch name| pgsql|
+|Module name | gpgsql|
+|Launch name| gpgsql|
This PostgreSQL backend is based on the [generic SQL backend](backend-generic-sql.md).
The default setup conforms to the schema at the bottom of this page, note that
In case the used LDAP client library doesn't support LDAP URIs as connection parameter, use plain host names or IP addresses instead (both may optionally be followed by a colon and the port).
## `ldap-starttls`
-(default "no") : Use TLS encrypted connections to the LDAP server.
-This is only allowed if `ldap-host` is a `ldap://` URI or a host name / IP address.
+(default "no") : Use TLS encrypted connections to the LDAP server. This is only allowed if ldap-host is a <ldap://> URI or a host name / IP address.
-## `ldap-basedn`
-(default "") : The PowerDNS LDAP DNS backend searches below this path for objects containing the specified DNS information.
-The retrieval of attributes is limited to this subtree.
-This option must be set to the path according to the layout of the LDAP tree, e.g. `ou=hosts,o=example,c=net` is the DN to my objects containing the DNS information.
+## `ldap-timeout`
+(default: "5") : The number of seconds to wait for LDAP operations to complete.
+
+## `ldap-reconnect-attempts`
+(default: "5") : The number of attempts to make to re-establish a lost connection to the LDAP server.
+
+## `ldap-authmethod`
+(default: "simple") : How to authenticate to the LDAP server. Actually only two methods are supported: "simple", which uses the classical DN / password, or "gssapi", which requires a Kerberos keytab.
## `ldap-binddn`
-(default "") : Path to the object to authenticate against.
-Should only be used if the LDAP server doesn't support anonymous binds.
+(default "") : Path to the object to authenticate against. Should only be used, if the LDAP server doesn't support anonymous binds and with the "simple" authmethod.
## `ldap-secret`
-(default "") : Password for authentication against the object specified by `ldap-binddn`.
+(default "") : Password for authentication against the object specified by ldap-binddn. Only used when "authmethod" is "simple".
+
+## `ldap-krb5-keytab`
+(default: "") : Full path to the keytab file to use to authenticate. This is only used when "authmethod" is set to "gssapi". The keytab must, ideally, contain only one principal (or to put it otherwise, only the first principal found in the keytab will be used).
+
+## `ldap-krb5-ccache`
+(default: "") : Full path to the Kerberos credential cache file to use. Actually only files are supported, and the "FILE:" prefix must not be set. The PowerDNS process must be able to write to this file and it *must* be the only one able to read it.
+
+## `ldap-basedn`
+(default "") : The PowerDNS LDAP DNS backend searches below this path for objects containing the specified DNS information. The retrieval of attributes is limited to this subtree. This option must be set to the path according to the layout of your LDAP tree, e.g. ou=hosts,o=linuxnetworks,c=de is the DN to my objects containing the DNS information.
## `ldap-method`
(default "simple") :
## `ldap-filter-lookup`
(default "(:target:)" ) : LDAP filter for limiting IP or name lookups, e.g. (&(:target:)(active=yes)) for returning only entries whose attribute "active" is set to "yes".
+# Master Mode
+
+Schema update
+-------------
+
+First off adding master support to the LDAP backend needs
+a schema update. This is required as some metadata must
+be stored by PowerDNS, such as the last successful transfer
+to slaves. The new schema is available in
+schema/pdns-domaininfo.schema.
+
+Once the schema is loaded the zones for which you want to
+be a master must be modified. The dn of the SOA record
+*must* have the object class `PdnsDomain`, and thus the
+`PdnsDomainId` attribute. This attribute is an integer
+that *must* be unique across all zones served by the
+backend. Furthermore the `PdnsDomainType` must be equal
+to 'master' (lower case).
+
+Example
+-------
+
+Here is an example LDIF of a zone that's ready for master
+operation (assuming the 'tree' style):
+
+```
+dn: dc=example,dc=com,ou=dns,dc=mycompany,dc=com
+objectClass: top
+objectClass: domainRelatedObject
+objectClass: dNSDomain2
+objectClass: PdnsDomain
+dc: example
+associatedDomain: example.com
+nSRecord: ns1.example.com
+sOARecord: ns1.example.com. hostmaster.example.com. 2013031101 1800 600 1209600 600
+mXRecord: 10 mx1.example.com
+PdnsDomainId: 1
+PdnsDomainType: master
+PdnsDomainMaster: 192.168.0.2
+```
+
+You should have one attribute `PdnsDomainMaster` per
+master serving this zone.
+
# Example
## Tree design
The DNS LDAP tree should be designed carefully to prevent mistakes, which are hard to correct afterwards.
# Using ALIAS records
The ALIAS record provides a way to have CNAME-like behaviour on the zone apex.
-In order to correctly serve ALIAS records, set the [`resolver`](settings.md#resolver)
-setting to an existing resolver:
+In order to correctly serve ALIAS records, set the [`resolver`](settings.md#resolver) setting to an existing resolver and enable [`expand-alias`](settings.md#expand-alias):
```
resolver=[::1]:5300
+expand-alias=yes
```
+**note**: If `resolver` is unset, ALIAS expension is disabled!
+
and add the ALIAS record to your zone apex. e.g.:
```
or should not look up the addresses themselves. Note that slaves will not
automatically follow changes in those A/AAAA records unless you AXFR regularly.
+**note:** The `expand-alias` setting does not exist in PowerDNS Authoritative Server 4.0.x.
+Hence, ALIAS records are always expanded on a direct A or AAAA query.
+
## ALIAS and DNSSEC
Starting with the PowerDNS Authoritative Server 4.0.0, DNSSEC 'washing' of ALIAS
records is supported on AXFR (**not** on live-signing). Set `outgoing-axfr-expand-alias`
This is done with the [`distributor-threads`](settings.md#distributor-threads) setting which says how many distributors will be opened for each receiver thread. Of special importance is the choice between 1 or more backends. In case of only 1 thread, PowerDNS reverts to unthreaded operation which may be a lot faster, depending on your operating system and architecture.
-Another very important setting is [`cache-ttl`](settings.md#cache-ttl). PowerDNS caches entire packets it sends out so as to save the time to query backends to assemble all data. The default setting of 20 seconds may be low for high traffic sites, a value of 60 seconds rarely leads to problems. Please be aware that if any TTL in the answer is shorter than this setting, the packet cache will respect the answer's shortest TTL.
+Other very important settings are [`cache-ttl`](settings.md#cache-ttl). PowerDNS caches entire packets it sends out so as to save the time to query backends to assemble all data. The default setting of 20 seconds may be low for high traffic sites, a value of 60 seconds rarely leads to problems. Please be aware that if any TTL in the answer is shorter than this setting, the packet cache will respect the answer's shortest TTL.
Some PowerDNS operators set cache-ttl to many hours or even days, and use [`pdns_control`](running.md#pdns_control)` purge` to selectively or globally notify PowerDNS of changes made in the backend. Also look at the [Query Cache](#query-cache) described in this chapter. It may materially improve your performance.
Logging truly kills performance as answering a question from the cache is an order of magnitude less work than logging a line about it. Busy sites will prefer to turn [`log-dns-details`](settings.md#log-dns-details) off.
# Packet Cache
-PowerDNS by default uses the 'Packet Cache' to recognise identical questions and supply them with identical answers, without any further processing. The default time to live is 10 seconds. It has been observed that the utility of the packet cache increases with the load on your nameserver.
+PowerDNS by default uses the 'Packet Cache' to recognise identical questions and supply them with identical answers, without any further processing. The default time to live is 20 seconds and can be changed by setting `cache-ttl`. It has been observed that the utility of the packet cache increases with the load on your nameserver.
-Not all backends may benefit from the packetcache. If your backend is memory based and does not lead to context switches, the packetcache may actually hurt performance.
+Not all backends may benefit from the packet cache. If your backend is memory based and does not lead to context switches, the packet cache may actually hurt performance.
-The size of the packetcache can be observed with `/etc/init.d/pdns show packetcache-size`
+The maximum size of the packet cache is controlled by the `max-packet-cache-entries` entries since 4.1. Before that both the query cache and the packet cache used the `max-cache-entries` setting.
# Query Cache
Besides entire packets, PowerDNS can also cache individual backend queries. Each DNS query leads to a number of backend queries, the most obvious additional backend query is the check for a possible CNAME. So, when a query comes in for the 'A' record for 'www.powerdns.com', PowerDNS must first check for a CNAME for 'www.powerdns.com'.
-The Query Cache caches these backend queries, many of which are quite repetitive. PowerDNS only caches queries with no answer, or with exactly one. In the future this may be expanded but this lightweight solution is very simple and therefore fast.
+The Query Cache caches these backend queries, many of which are quite repetitive. The maximum number of entries in the cache is controlled by the `max-cache-entries` setting. Before 4.1 this setting also controls the maximum number of entries in the packet cache.
Most gain is made from caching negative entries, ie, queries that have no answer. As these take little memory to store and are typically not a real problem in terms of speed-of-propagation, the default TTL for negative queries is a rather high 60 seconds.
* `corrupt-packets`: Number of corrupt packets received
* `deferred-cache-inserts`: Number of cache inserts that were deferred because of maintenance
* `deferred-cache-lookup`: Number of cache lookups that were deferred because of maintenance
+* `deferred-packetcache-inserts`: Number of packet cache inserts that were deferred because of maintenance
+* `deferred-packetcache-lookup`: Number of packet cache lookups that were deferred because of maintenance
* `dnsupdate-answers`: Number of DNS update packets successfully answered
* `dnsupdate-changes`: Total number of changes to records from DNS update
* `dnsupdate-queries`: Number of DNS update packets received
* `qsize-q`: Number of packets waiting for database attention
* `query-cache-hit`: Number of hits on the [query cache](performance.md#query-cache)
* `query-cache-miss`: Number of misses on the [query cache](performance.md#query-cache)
+* `query-cache-size`: Number of entries in the query cache
* `rd-queries`: Number of packets sent by clients requesting recursion (regardless of if we'll be providing them with recursion). Since 3.4.0.
* `recursing-answers`: Number of packets we supplied an answer to after recursive processing
* `recursing-questions`: Number of packets we performed recursive processing for
Entropy source file to use.
+## `expand-alias`
+* Boolean
+* Default: no
+* Since: 4.1.0
+
+If this is enabled, ALIAS records are expanded (synthesised to their A/AAAA).
+
+If this is disabled (the default), ALIAS records will not expanded and the server will will return NODATA for A/AAAA queries for such names.
+
+**note**: [`resolver`](#resolver) must also be set for ALIAS expansion to work!
+
+**note**: In PowerDNS Authoritative Server 4.0.x, this setting did not exist and ALIAS was always expanded.
+
## `forward-dnsupdate`
* Boolean
* Default: no
* Integer
* Default: 1000000
-Maximum number of cache entries. 1 million (the default) will generally suffice
-for most installations.
+Maximum number of entries in the query cache. 1 million (the default) will generally suffice
+for most installations. Starting with 4.1, the packet and query caches are distinct so you might
+also want to see `max-packet-cache-entries`.
## `max-ent-entries`
* Integer
Limit the number of NSEC3 hash iterations
+## `max-packet-cache-entries`
+* Integer
+* Default: 1000000
+
+Maximum number of entries in the packet cache. 1 million (the default) will generally suffice
+for most installations. This setting has been introduced in 4.1, previous used the `max-cache-entries`
+setting for both the packet and query caches.
+
## `max-queue-length`
* Integer
* Default: 5000
["Recursion"](recursion.md).
## `resolver`
-* IP Address
+* IP Addresses with optional port, separated by commas
* Added in: 4.1.0
-Use this resolver for ALIAS and the internal stub resolver.
+Use these resolver addresses for ALIAS and the internal stub resolver.
+If this is not set, `/etc/resolv.conf` is parsed for upstream resolvers.
## `retrieval-threads`
* Integer
Password for TCP control.
+## `tcp-fast-open`
+* Integer
+* Default: 0 (Disabled)
+* Available since: 4.1
+
+Enable TCP Fast Open support, if available, on the listening sockets. The numerical
+value supplied is used as the queue size, 0 meaning disabled.
+
## `tcp-idle-timeout`
* Integer
* Default: 5
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.0.X to 4.1.0
+
+## Changed options
+
+### Changed defaults
+
+## Other changes
+
+The `--with-pgsql`, `--with-pgsql-libs`, `--with-pgsql-includes` and `--with-pgsql-config` `configure` options have been deprecated.
+`configure` now attempts to find the Postgresql client libraries via `pkg-config`, falling back to detecting `pg_config`.
+Use `--with-pg-config` to specify a path to a non-default `pg_config` if you have Postgresql installed in a non-default location.
+
# 4.0.X to 4.0.2
## Changed options
* policyAction: The action taken by the engine
* policyCustom: The CNAME content for the `pdns.policyactions.Custom` response, a string
* policyTTL: The TTL in seconds for the `pdns.policyactions.Custom` response
-* wantsRPZ - A boolean that indicates the use of the Policy Engine, can be set to `false` in `preresolve` to disable RPZ for this query
-* data - a table that is persistent throughout the lifetime of the `dq` object and can be used to store custom data. All keys and values in the table must be of type `string`.
+* wantsRPZ - A boolean that indicates the use of the Policy Engine, can be set to `false` in `prerpz` to disable RPZ for this query
+* data - a Lua object reference that is persistent throughout the lifetime of the `dq` object for a single query. It can be used to store custom data. Most scripts initialise this to an empty table early on so they can store multiple items.
It also supports the following methods:
the answer too, which defaults to the name of the question
* `addPolicyTag(tag)`: add a policy tag.
* `discardPolicy(policyname)`: skip the filtering policy (for example RPZ) named `policyname` for this query. This is mostly useful in the `prerpz` hook.
-* `getDH()` - Returns the DNS Header of the query or nil. A DNS header offers the following methods:
- * `getRD()`, `getAA()`, `getAD()`, `getCD()`, `getTC()`: query these bits from the DNS Header
- * `getRCODE()`: get the RCODE of the query
- * `getOPCODE()`: get the OPCODE of the query
- * `getID()`: get the ID of the query
+* `getDH()` - Returns the DNS Header of the query or nil.
* `getPolicyTags()`: get the current policy tags as a table of strings.
* `getRecords()`: get a table of DNS Records in this DNS Question (or answer by now)
* `setPolicyTags(tags)`: update the policy tags, taking a table of strings.
* `getPolicyTags()`: Get a list the policyTags for this message
* `setPolicyTags(tags)`: Set the policyTags for this message to `tags` (a list)
+A DNS header as returned by `getDH()` offers the following methods:
+* `getRD()`, `getAA()`, `getAD()`, `getCD()`, `getTC()`: query these bits from the DNS Header
+* `getRCODE()`: get the RCODE of the query
+* `getOPCODE()`: get the OPCODE of the query
+* `getID()`: get the ID of the query
+
## `function ipfilter ( remoteip, localip, dh )`
This hook gets queried immediately after consulting the packet cache, but before
parsing the DNS packet. If this hook returns something else than false, the packet is dropped.
This hook does not get the full DNSQuestion object, since filling out the fields
would require packet parsing, which is what we are trying to prevent with `ipfilter`.
-### `function gettag(remote, ednssubnet, local, qname, qtype)`
+### `function gettag(remote, ednssubnet, local, qname, qtype, ednsoptions)`
The `gettag` function is invoked when the Recursor attempts to discover in which
packetcache an answer is available.
setting dq.variable to `true`. In the latter case, repeated queries will pass
through the entire Lua script.
+`ednsoptions` is a table whose keys are EDNS option codes and values are
+`EDNSOptionView` objects, with the EDNS option content size in the `size` member
+and the content accessible as a NULL-safe string object via `getContent()`.
+This table is empty unless the `gettag-needs-edns-options` parameter is set.
+
### `function prerpz(dq)`
This hook is called before any filtering policy have been applied, making it
if dq.qname:equal('example.com') then
dq:discardPolicy('malware')
end
+ return false
end
```
The DNSSEC notes from [`forward-zones`](#forward-zones) apply here as well.
+## `gettag-needs-edns-options`
+* Boolean
+* Default: no
+* Available since: 4.1.0
+
+If set, EDNS options in incoming queries are extracted and passed to the `gettag()`
+hook in the `ednsoptions` table.
+
## `hint-file`
* Path
Number of entries in the remotes ringbuffer, which keeps statistics on who is
querying your server. Can be read out using `rec_control top-remotes`.
+## `tcp-fast-open`
+* Integer
+* Default: 0 (Disabled)
+* Available since: 4.1
+
+Enable TCP Fast Open support, if available, on the listening sockets. The numerical
+value supplied is used as the queue size, 0 meaning disabled.
+
## `threads`
* Integer
* Default: 2
#include <boost/type_traits.hpp>
#include <lua.hpp>
-#ifdef _MSC_VER
+#if defined(_MSC_VER) && _MSC_VER < 1900
# include "misc/exception.hpp"
#endif
template<typename TFunctionType>
class LuaFunctionCaller;
+ /**
+ * Opaque type that identifies a Lua object
+ */
+ struct LuaObject {
+ LuaObject() = default;
+ LuaObject(lua_State* state, int index=-1) {
+ this->objectInRegistry = std::make_shared<LuaContext::ValueInRegistry>(state, index);
+ }
+ std::shared_ptr<LuaContext::ValueInRegistry> objectInRegistry;
+ };
+
/**
* Opaque type that identifies a Lua thread
*/
* @tparam TType Type whose function belongs to
*/
template<typename TType>
- void unregisterFunction(const std::string& functionName)
+ void unregisterFunction(const std::string& /*functionName*/)
{
lua_pushlightuserdata(mState, const_cast<std::type_info*>(&typeid(TType)));
lua_pushnil(mState);
result.threadInRegistry = std::unique_ptr<ValueInRegistry>(new ValueInRegistry(mState));
lua_pop(mState, 1);
- return std::move(result);
+ return result;
}
/**
PushedObject& operator=(PushedObject&& other) { std::swap(state, other.state); std::swap(num, other.num); return *this; }
PushedObject(PushedObject&& other) : state(other.state), num(other.num) { other.num = 0; }
- PushedObject operator+(PushedObject&& other) && { PushedObject obj(state, num + other.num); num = 0; other.num = 0; return std::move(obj); }
+ PushedObject operator+(PushedObject&& other) && { PushedObject obj(state, num + other.num); num = 0; other.num = 0; return obj; }
void operator+=(PushedObject&& other) { assert(state == other.state); num += other.num; other.num = 0; }
auto getState() const -> lua_State* { return state; }
std::array<char,512> buffer;
// read function ; "data" must be an instance of Reader
- static const char* read(lua_State* l, void* data, size_t* size) {
+ static const char* read(lua_State* /*l*/, void* data, size_t* size) {
assert(size != nullptr);
assert(data != nullptr);
Reader& me = *static_cast<Reader*>(data);
RealReturnType;
// we push the parameters on the stack
- auto inArguments = Pusher<std::tuple<TParameters...>>::push(state, std::forward_as_tuple((input)...));
+ auto inArguments = Pusher<std::tuple<TParameters&&...>>::push(state, std::forward_as_tuple(std::forward<TParameters>(input)...));
//
const int outArgumentsCount = std::tuple_size<RealReturnType>::value;
lua_setmetatable(state, -2);
pushedTable.release();
- return std::move(obj);
+ return obj;
}
};
* This functions reads multiple values starting at "index" and passes them to the callback
*/
template<typename TRetValue, typename TCallback>
- static auto readIntoFunction(lua_State* state, tag<TRetValue>, TCallback&& callback, int index)
+ static auto readIntoFunction(lua_State* /*state*/, tag<TRetValue>, TCallback&& callback, int /*index*/)
-> TRetValue
{
return callback();
const auto& firstElem = Reader<typename std::decay<TFirstType>::type>::read(state, index);
if (!firstElem)
- throw WrongTypeException(lua_typename(state, index), typeid(TFirstType));
+ throw WrongTypeException(lua_typename(state, lua_type(state, index)), typeid(TFirstType));
Binder<TCallback, const TFirstType&> binder{ callback, *firstElem };
return readIntoFunction(state, retValueTag, binder, index + 1, othersTags...);
const auto& firstElem = Reader<typename std::decay<TFirstType>::type>::read(state, index);
if (!firstElem)
- throw WrongTypeException(lua_typename(state, index), typeid(TFirstType));
+ throw WrongTypeException(lua_typename(state, lua_type(state, index)), typeid(TFirstType));
Binder<TCallback, const TFirstType&> binder{ callback, *firstElem };
return readIntoFunction(state, retValueTag, binder, index + 1, othersTags...);
/* PARTIAL IMPLEMENTATIONS */
/**************************************************/
template<>
-inline auto LuaContext::readTopAndPop<void>(lua_State* state, PushedObject obj)
+inline auto LuaContext::readTopAndPop<void>(lua_State* /*state*/, PushedObject /*obj*/)
-> void
{
}
/**************************************************/
// specializations of the Pusher structure
+// opaque Lua references
+template<>
+struct LuaContext::Pusher<LuaContext::LuaObject> {
+ static const int minSize = 1;
+ static const int maxSize = 1;
+
+ static PushedObject push(lua_State* state, const LuaContext::LuaObject& value) noexcept {
+ if (value.objectInRegistry.get()) {
+ PushedObject obj = value.objectInRegistry->pop();
+ return obj;
+ } else {
+ lua_pushnil(state);
+ return PushedObject{state, 1};
+ }
+ }
+};
+
// boolean
template<>
struct LuaContext::Pusher<bool> {
static const int minSize = 1;
static const int maxSize = 1;
- static PushedObject push(lua_State* state, std::nullptr_t value) noexcept {
- assert(value == nullptr);
+ static PushedObject push(lua_State* state, std::nullptr_t) noexcept {
lua_pushnil(state);
return PushedObject{state, 1};
}
for (auto i = value.begin(), e = value.end(); i != e; ++i)
setTable<TValue>(state, obj, i->first, i->second);
- return std::move(obj);
+ return obj;
}
};
for (auto i = value.begin(), e = value.end(); i != e; ++i)
setTable<TValue>(state, obj, i->first, i->second);
- return std::move(obj);
+ return obj;
}
};
for (auto i = value.begin(), e = value.end(); i != e; ++i)
setTable<TType2>(state, obj, i->first, i->second);
- return std::move(obj);
+ return obj;
}
};
for (unsigned int i = 0; i < value.size(); ++i)
setTable<TType>(state, obj, i + 1, value[i]);
- return std::move(obj);
+ return obj;
}
};
PushedObject obj{state, 0};
VariantWriter writer{state, obj};
value.apply_visitor(writer);
- return std::move(obj);
+ return obj;
}
private:
push2(state, std::move(value), std::integral_constant<int,N+1>{});
}
- static int push2(lua_State* state, const std::tuple<TTypes...>&, std::integral_constant<int,sizeof...(TTypes)>) noexcept {
+ static int push2(lua_State* /*state*/, const std::tuple<TTypes...>&, std::integral_constant<int,sizeof...(TTypes)>) noexcept {
return 0;
}
- static int push2(lua_State* state, std::tuple<TTypes...>&&, std::integral_constant<int,sizeof...(TTypes)>) noexcept {
+ static int push2(lua_State* /*state*/, std::tuple<TTypes...>&&, std::integral_constant<int,sizeof...(TTypes)>) noexcept {
return 0;
}
};
/**************************************************/
// specializations of the Reader structures
+// opaque Lua references
+template<>
+struct LuaContext::Reader<LuaContext::LuaObject>
+{
+ static auto read(lua_State* state, int index)
+ -> boost::optional<LuaContext::LuaObject>
+ {
+ LuaContext::LuaObject obj(state, index);
+ return obj;
+ }
+};
+
// reading null
template<>
struct LuaContext::Reader<std::nullptr_t>
template<typename TIterBegin, typename TIterEnd>
struct VariantReader<TIterBegin, TIterEnd, typename std::enable_if<boost::mpl::distance<TIterBegin, TIterEnd>::type::value == 0>::type>
{
- static auto read(lua_State* state, int index)
+ static auto read(lua_State* /*state*/, int /*index*/)
-> boost::optional<ReturnType>
{
return boost::none;
template<>
struct LuaContext::Reader<std::tuple<>>
{
- static auto read(lua_State* state, int index, int maxSize = 0)
+ static auto read(lua_State* /*state*/, int /*index*/, int /*maxSize*/ = 0)
-> boost::optional<std::tuple<>>
{
return std::tuple<>{};
_BOOST_gcc_test(6, 1) \
_BOOST_mingw_test(6, 0) \
_BOOST_gcc_test(6, 0) \
+ _BOOST_mingw_test(5, 4) \
+ _BOOST_gcc_test(5, 4) \
_BOOST_mingw_test(5, 3) \
_BOOST_gcc_test(5, 3) \
_BOOST_mingw_test(5, 2) \
)
AC_ARG_VAR([LDAP_LIBS], [linker flags for openldap])
+
+ AC_CHECK_HEADERS([krb5.h],
+ [],
+ [AC_MSG_ERROR([Kerberos header (krb5.h) not found])]
+ )
+
+ AC_ARG_VAR([KRB5_LIBS], [linker flag to add Kerberos 5 libraries])
+
+ AC_CHECK_LIB([krb5], [krb5_init_context],
+ [
+ KRB5_LIBS="-lkrb5"
+ ]
+ )
+
+ AC_CHECK_FUNCS([krb5_get_init_creds_opt_set_default_flags])
])
-AC_DEFUN([PDNS_WITH_POSTGRESQL],[
- AC_ARG_WITH([pgsql],
- AS_HELP_STRING([--with-pgsql=<path>],
- [root directory path of PgSQL installation]
- ),
- [PGSQL_lib_check="$withval/lib/pgsql $withval/lib"
- PGSQL_inc_check="$withval/include/pgsql $withval/include"
- ]
- )
-
- AC_ARG_WITH([pgsql-lib],
- AS_HELP_STRING([--with-pgsql-lib=<path>],
- [directory path of PgSQL library installation]
- ),
- [PGSQL_lib_check="$withval/lib/pgsql $withval/pgsql $withval"]
- )
-
- AC_ARG_WITH([pgsql-includes],
- AS_HELP_STRING([--with-pgsql-includes=<path>],
- [directory path of PgSQL header installation]
- ),
- [PGSQL_inc_check="$withval/include/pgsql $withval/pgsql $withval"]
- )
-
- AC_ARG_WITH([pgsql-config],
- AS_HELP_STRING([--with-pgsql-config=<path>],
- [location of pg_config]
- ),
- [PGSQL_pg_config="$withval"
- if test "x$PGSQL_pg_config" = "xyes" || test ! -x "$PGSQL_pg_config"; then
- AC_MSG_ERROR([--with-pgsql-config must provide a valid path to pg_config executable])
- fi
- ],
- [AC_PATH_PROG([PGSQL_pg_config], [pg_config])]
- )
-
- if test "x$PGSQL_pg_config" != "x"; then
- if test "x$PGSQL_lib_check" = "x"; then
- PGSQL_lib_check=$($PGSQL_pg_config --libdir)
- fi
- if test "x$PGSQL_inc_check" = "x"; then
- PGSQL_inc_check=$($PGSQL_pg_config --includedir)
- fi
- PGSQL_CFLAGS=
- fi
-
- if test "x$PGSQL_lib_check" = "x"; then
- PGSQL_lib_check="/usr/local/pgsql/lib/pgsql /usr/local/lib/pgsql /opt/pgsql/lib/pgsql /usr/lib/pgsql /usr/local/pgsql/lib /usr/local/lib /opt/pgsql/lib /usr/lib /usr/lib64 $full_libdir"
- fi
-
- if test "x$PGSQL_inc_check" = "x"; then
- PGSQL_inc_check="/usr/local/pgsql/include/pgsql /usr/include /usr/local/include/postgresql/ /usr/local/include /opt/pgsql/include/pgsql /opt/pgsql/include /usr/include/pgsql/ /usr/include/postgresql"
- fi
-
- AC_SUBST([PGSQL_lib])
- AC_MSG_CHECKING([for PgSQL library directory])
- PGSQL_libdir=
- for m in $PGSQL_lib_check; do
- if test -d "$m" && (test -f "$m/libpq.a" || test -f "$m/libpq.so"); then
- PGSQL_libdir=$m
- break
- fi
- done
- if test -z "$PGSQL_libdir"; then
- AC_MSG_ERROR([Did not find the pgsql library dir in '$PGSQL_lib_check'])
- fi
- case "$PGSQL_libdir" in
- /usr/lib)
- PGSQL_lib="-lpq"
- ;;
- /usr/lib64)
- PGSQL_lib="-lpq"
- ;;
- $full_libdir)
- PGSQL_lib="-lpq"
- ;;
- /*)
- PGSQL_lib="-L$PGSQL_libdir -Wl,-rpath,$PGSQL_libdir -lpq"
- ;;
- *)
- AC_MSG_ERROR([The PgSQL library directory ($PGSQL_libdir) must be an absolute path.])
- ;;
- esac
- AC_MSG_RESULT([$PGSQL_libdir])
-
- AC_SUBST([PGSQL_inc])
- AC_MSG_CHECKING([for PgSQL include directory])
- PGSQL_incdir=
- for m in $PGSQL_inc_check; do
- if test -d "$m" && test -f "$m/libpq-fe.h"; then
- PGSQL_incdir=$m
- break
- fi
- done
- if test -z "$PGSQL_incdir"; then
- AC_MSG_ERROR([Did not find the PgSQL include dir in '$PGSQL_inc_check'])
- fi
- case "$PGSQL_incdir" in
- /*)
- PGSQL_inc="-I$PGSQL_incdir"
- ;;
- * )
- AC_MSG_ERROR([The PgSQL include directory ($PGSQL_incdir) must be an absolute path.])
- ;;
- esac
- AC_MSG_RESULT([$PGSQL_incdir])
+dnl
+dnl Attempt to detect the flags we need for the Postgresql client libraries
+dnl First, use pkg-config
+dnl If that yields no results, use (optionally find) pg_config and use it to
+dnl determine the CFLAGS and LIBS
+dnl
+AC_DEFUN([PDNS_WITH_POSTGRESQL], [
+ PG_CONFIG=""
+ AC_ARG_WITH([pg-config], [
+ AS_HELP_STRING([--with-pg-config=<path>], [path to pg_config])
+ ], [
+ PG_CONFIG="$withval"
+ AS_IF([test "x$PG_CONFIG" = "xyes" -o ! -x "$PG_CONFIG"], [
+ AC_MSG_ERROR([--with-pg-config must provide a valid path to the pg_config executable])
+ ])
+ ])
+
+ AS_IF([test "x$PG_CONFIG" = "x"], [
+ PKG_CHECK_MODULES([PGSQL], [libpq], [ : ], [ : ])
+ ])
+
+ AS_IF([test "x$PG_CONFIG" != "x" -o "x$PGSQL_LIBS" = "x"], [
+ dnl Either a path was provided, or pkg-config failed to produce a result
+ AS_IF([test "x$PG_CONFIG" == "x"], [
+ AC_PATH_PROG([PG_CONFIG], [pg_config])
+ ])
+ AS_IF([test "x$PG_CONFIG" == "x"], [
+ AC_MSG_ERROR([Can not find pg_config, use --with-pg-config to specify the path to pg_config])
+ ])
+ PGSQL_LIBS="-L$($PG_CONFIG --libdir) -lpq"
+ PGSQL_CFLAGS="-I$($PG_CONFIG --includedir)"
+ ])
+ AC_SUBST([PGSQL_LIBS])
+ AC_SUBST([PGSQL_CFLAGS])
])
-
-AM_CPPFLAGS += $(PGSQL_inc)
+AM_CPPFLAGS += $(PGSQL_CFLAGS)
pkglib_LTLIBRARIES = libgpgsqlbackend.la
EXTRA_DIST = \
spgsql.cc spgsql.hh
libgpgsqlbackend_la_LDFLAGS = -module -avoid-version
-libgpgsqlbackend_la_LIBADD = $(PGSQL_lib)
+libgpgsqlbackend_la_LIBADD = $(PGSQL_LIBS)
--lssl -lcrypto $(PGSQL_lib)
+-lssl -lcrypto $(PGSQL_LIBS)
libldapbackend_la_SOURCES = \
ldapbackend.cc ldapbackend.hh \
powerldap.cc powerldap.hh \
- utils.hh
+ utils.hh exceptions.hh \
+ ldaputils.hh ldaputils.cc \
+ ldapauthenticator.hh ldapauthenticator_p.hh ldapauthenticator.cc
libldapbackend_la_LDFLAGS = -module -avoid-version
-libldapbackend_la_LIBADD = $(LDAP_LIBS)
+libldapbackend_la_LIBADD = $(LDAP_LIBS) $(KRB5_LIBS)
-ldapbackend.lo powerldap.lo
+ldapbackend.lo powerldap.lo ldaputils.lo ldapauthenticator.lo
-$(LDAP_LIBS)
+$(LDAP_LIBS) $(KRB5_LIBS)
--- /dev/null
+/*
+ * PowerDNS LDAP Connector
+ * By PowerDNS.COM BV
+ * By Norbert Sendetzky <norbert@linuxnetworks.de> (2003-2007)
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2
+ * as published by the Free Software Foundation.
+ *
+ * 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 St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <exception>
+#include <stdexcept>
+#include <string>
+
+#ifndef LDAPEXCEPTIONS_HH
+#define LDAPEXCEPTIONS_HH
+
+class LDAPException : public std::runtime_error
+{
+ public:
+ explicit LDAPException( const std::string &str ) : std::runtime_error( str ) {}
+};
+
+class LDAPTimeout : public LDAPException
+{
+ public:
+ explicit LDAPTimeout() : LDAPException( "Timeout" ) {}
+};
+
+class LDAPNoConnection : public LDAPException
+{
+ public:
+ explicit LDAPNoConnection() : LDAPException( "No connection to LDAP server" ) {}
+};
+
+#endif // LDAPEXCEPTIONS_HH
--- /dev/null
+/*
+ * PowerDNS LDAP Backend
+ * Copyright (C) 2011 Grégory Oestreicher <greg@kamago.net>
+ * Copyright (C) 2003-2007 Norbert Sendetzky <norbert@linuxnetworks.de>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2
+ * as published by the Free Software Foundation.
+ *
+ * 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 St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <krb5.h>
+#include <pdns/logger.hh>
+#include "ldapauthenticator_p.hh"
+#include "ldaputils.hh"
+
+/*****************************
+ *
+ * LdapSimpleAuthenticator
+ *
+ ****************************/
+
+LdapSimpleAuthenticator::LdapSimpleAuthenticator( const std::string& dn, const std::string& pw, int tmout )
+ : binddn( dn ), bindpw( pw ), timeout( tmout )
+{
+}
+
+bool LdapSimpleAuthenticator::authenticate( LDAP *conn )
+{
+ int msgid;
+
+#ifdef HAVE_LDAP_SASL_BIND
+ int rc;
+ struct berval passwd;
+
+ passwd.bv_val = (char *)bindpw.c_str();
+ passwd.bv_len = strlen( passwd.bv_val );
+
+ if( ( rc = ldap_sasl_bind( conn, binddn.c_str(), LDAP_SASL_SIMPLE, &passwd, NULL, NULL, &msgid ) ) != LDAP_SUCCESS )
+ {
+ fillLastError( conn, rc );
+ return false;
+ }
+#else
+ if( ( msgid = ldap_bind( conn, binddn.c_str(), bindpw.c_str(), LDAP_AUTH_SIMPLE ) ) == -1 )
+ {
+ fillLastError( conn, msgid );
+ return false;
+ }
+#endif
+
+ ldapWaitResult( conn, msgid, timeout, NULL );
+ return true;
+}
+
+std::string LdapSimpleAuthenticator::getError() const
+{
+ return lastError;
+}
+
+void LdapSimpleAuthenticator::fillLastError( LDAP* conn, int code )
+{
+ lastError = ldapGetError( conn, code );
+}
+
+/*****************************
+ *
+ * LdapGssapiAuthenticator
+ *
+ ****************************/
+
+static int ldapGssapiAuthenticatorSaslInteractCallback( LDAP *conn, unsigned flags, void *defaults, void *in )
+{
+ return LDAP_SUCCESS;
+}
+
+LdapGssapiAuthenticator::LdapGssapiAuthenticator( const std::string& kt, const std::string &ccache, int tmout )
+ : logPrefix( "[LDAP GSSAPI] " ), keytabFile( kt ), cCacheFile( ccache ), timeout( tmout )
+{
+}
+
+bool LdapGssapiAuthenticator::authenticate( LDAP *conn )
+{
+ int code = attemptAuth( conn );
+
+ if ( code == -1 ) {
+ return false;
+ }
+ else if ( code == -2 ) {
+ // Here it may be possible to retry after obtainting a fresh ticket
+ L<<Logger::Debug << logPrefix << "No TGT found, trying to acquire a new one" << std::endl;
+ code = updateTgt();
+
+ if ( attemptAuth( conn ) != 0 ) {
+ L<<Logger::Error << logPrefix << "Failed to acquire a TGT" << std::endl;
+ return false;
+ }
+ }
+
+ return true;
+}
+
+std::string LdapGssapiAuthenticator::getError() const
+{
+ return lastError;
+}
+
+int LdapGssapiAuthenticator::attemptAuth( LDAP *conn )
+{
+ // Create SASL defaults
+ SaslDefaults defaults;
+ char *ldapOption = 0;
+
+ ldap_get_option( conn, LDAP_OPT_X_SASL_MECH, ldapOption );
+ if ( !ldapOption )
+ defaults.mech = std::string( "GSSAPI" );
+ else
+ defaults.mech = std::string( ldapOption );
+ ldap_memfree( ldapOption );
+
+ ldap_get_option( conn, LDAP_OPT_X_SASL_REALM, ldapOption );
+ if ( ldapOption )
+ defaults.realm = std::string( ldapOption );
+ ldap_memfree( ldapOption );
+
+ ldap_get_option( conn, LDAP_OPT_X_SASL_AUTHCID, ldapOption );
+ if ( ldapOption )
+ defaults.authcid = std::string( ldapOption );
+ ldap_memfree( ldapOption );
+
+ ldap_get_option( conn, LDAP_OPT_X_SASL_AUTHZID, ldapOption );
+ if ( ldapOption )
+ defaults.authzid = std::string( ldapOption );
+ ldap_memfree( ldapOption );
+
+ // And now try to bind
+ int rc = ldap_sasl_interactive_bind_s( conn, "", defaults.mech.c_str(),
+ NULL, NULL, LDAP_SASL_QUIET,
+ ldapGssapiAuthenticatorSaslInteractCallback, &defaults );
+ L<<Logger::Debug << logPrefix << "ldap_sasl_interactive_bind_s returned " << rc << std::endl;
+
+ if ( rc == LDAP_LOCAL_ERROR ) {
+ // This may mean that the ticket has expired, so let the caller know
+ lastError = ldapGetError( conn, rc );
+ return -2;
+ }
+ else if ( rc != LDAP_SUCCESS ) {
+ lastError = ldapGetError( conn, rc );
+ return -1;
+ }
+
+ return rc;
+}
+
+int LdapGssapiAuthenticator::updateTgt()
+{
+ krb5_error_code code;
+ krb5_context context;
+ krb5_creds credentials;
+ krb5_keytab keytab;
+ krb5_principal principal;
+ krb5_ccache ccache;
+ krb5_get_init_creds_opt *options;
+
+ if ( ( code = krb5_init_context( &context ) ) != 0 ) {
+ L<<Logger::Error << logPrefix << "Failed to init krb5 context" << std::endl;
+ return code;
+ }
+
+ if ( !keytabFile.empty() ) {
+ std::string keytabStr( "FILE:" + keytabFile );
+ code = krb5_kt_resolve( context, keytabStr.c_str(), &keytab );
+ }
+ else {
+ code = krb5_kt_default( context, &keytab );
+ }
+
+ if ( code != 0 ) {
+ L<<Logger::Error << logPrefix << "krb5 error when locating the keytab file: " << std::string( krb5_get_error_message( context, code ) ) << std::endl;
+ return code;
+ }
+
+ // Extract the principal name from the keytab
+ krb5_kt_cursor cursor;
+ if ( ( code = krb5_kt_start_seq_get( context, keytab, &cursor ) ) != 0 ) {
+ L<<Logger::Error << logPrefix << "krb5 error when initiating keytab search: " << std::string( krb5_get_error_message( context, code ) ) << std::endl;
+ krb5_kt_close( context, keytab );
+ return code;
+ }
+
+ krb5_keytab_entry entry;
+ if ( ( code = krb5_kt_next_entry( context, keytab, &entry, &cursor ) ) == 0 ) {
+ code = krb5_copy_principal( context, entry.principal, &principal );
+ krb5_kt_free_entry( context, &entry );
+ }
+
+ krb5_kt_end_seq_get( context, keytab, &cursor );
+ if ( code != 0 ) {
+ L<<Logger::Error << logPrefix << "krb5 error when extracting principal information: " << std::string( krb5_get_error_message( context, code ) ) << std::endl;
+ krb5_kt_close( context, keytab );
+ krb5_free_principal( context, principal );
+ return code;
+ }
+
+ // Locate the credentials cache file
+ if ( !cCacheFile.empty() ) {
+ std::string cCacheStr( "FILE:" + cCacheFile );
+ code = krb5_cc_resolve( context, cCacheStr.c_str(), &ccache );
+ }
+ else {
+ code = krb5_cc_default( context, &ccache );
+ }
+
+ if ( code != 0 ) {
+ L<<Logger::Error << logPrefix << "krb5 error when locating the credentials cache file: " << std::string( krb5_get_error_message( context, code ) ) << std::endl;
+ krb5_kt_close( context, keytab );
+ krb5_free_principal( context, principal );
+ return code;
+ }
+
+ // Initialize the credentials cache file
+ if ( ( code = krb5_cc_initialize( context, ccache, principal ) ) != 0 ) {
+ L<<Logger::Error << logPrefix << "krb5 error when initializing the credentials cache file: " << std::string( krb5_get_error_message( context, code ) ) << std::endl;
+ krb5_kt_close( context, keytab );
+ krb5_free_principal( context, principal );
+ return code;
+ }
+
+ if ( ( code = krb5_get_init_creds_opt_alloc( context, &options ) ) != 0 ) {
+ L<<Logger::Error << logPrefix << "krb5 error when allocating credentials cache structure: " << std::string( krb5_get_error_message( context, code ) ) << std::endl;
+ krb5_kt_close( context, keytab );
+ krb5_free_principal( context, principal );
+ return code;
+ }
+ krb5_get_init_creds_opt_set_default_flags( context, "pdns", NULL, options );
+
+ // And finally get the TGT!
+ code = krb5_get_init_creds_keytab( context, &credentials, principal, keytab, 0, NULL, options );
+ krb5_get_init_creds_opt_free( context, options );
+ krb5_kt_close( context, keytab );
+ krb5_free_principal( context, principal );
+
+ if ( code == 0 ) {
+ L<<Logger::Error << logPrefix << "krb5 error when getting the TGT: " << std::string( krb5_get_error_message( context, code ) ) << std::endl;
+ code = krb5_cc_store_cred( context, ccache, &credentials );
+ krb5_free_cred_contents( context, &credentials );
+ krb5_cc_close( context, ccache );
+ }
+
+ krb5_free_context( context );
+ return code;
+}
--- /dev/null
+/*
+ * PowerDNS LDAP Backend
+ * Copyright (C) 2011 Grégory Oestreicher <greg@kamago.net>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2
+ * as published by the Free Software Foundation.
+ *
+ * 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 St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <ldap.h>
+#include <string>
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#ifndef LDAPAUTHENTICATOR_HH
+#define LDAPAUTHENTICATOR_HH
+
+class LdapAuthenticator
+{
+ public:
+ virtual ~LdapAuthenticator() {}
+ virtual bool authenticate( LDAP *connection ) = 0;
+ virtual std::string getError() const = 0;
+};
+
+#endif // LDAPAUTHENTICATOR_HH
--- /dev/null
+/*
+ * PowerDNS LDAP Backend
+ * Copyright (C) 2011 Grégory Oestreicher <greg@kamago.net>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2
+ * as published by the Free Software Foundation.
+ *
+ * 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 St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "ldapauthenticator.hh"
+
+#ifndef LDAPAUTHENTICATOR_P_HH
+#define LDAPAUTHENTICATOR_P_HH
+
+#ifndef HAVE_KRB5_GET_INIT_CREDS_OPT_SET_DEFAULT_FLAGS
+#define krb5_get_init_creds_opt_set_default_flags( a, b, c, d ) /* This does not exist with MIT Kerberos */
+#endif
+
+class LdapSimpleAuthenticator : public LdapAuthenticator
+{
+ std::string binddn;
+ std::string bindpw;
+ int timeout;
+ std::string lastError;
+
+ void fillLastError( LDAP *conn, int code );
+
+ public:
+ LdapSimpleAuthenticator( const std::string &dn, const std::string &pw, int timeout );
+ virtual bool authenticate( LDAP *conn );
+ virtual std::string getError() const;
+};
+
+class LdapGssapiAuthenticator : public LdapAuthenticator
+{
+ std::string logPrefix;
+ std::string keytabFile;
+ std::string cCacheFile;
+ int timeout;
+ std::string lastError;
+
+ struct SaslDefaults {
+ std::string mech;
+ std::string realm;
+ std::string authcid;
+ std::string authzid;
+ };
+
+ int attemptAuth( LDAP *conn );
+ int updateTgt();
+
+ public:
+ LdapGssapiAuthenticator( const std::string &keytab, const std::string &credsCache, int timeout );
+ virtual bool authenticate( LDAP *conn );
+ virtual std::string getError() const;
+};
+
+#endif // LDAPAUTHENTICATOR_P_HH
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
+#include "exceptions.hh"
+#include "ldapauthenticator_p.hh"
#include "ldapbackend.hh"
+#include <cstdlib>
unsigned int ldap_host_index = 0;
LdapBackend::LdapBackend( const string &suffix )
{
- string hoststr;
- unsigned int i, idx;
- vector<string> hosts;
+ string hoststr;
+ unsigned int i, idx;
+ vector<string> hosts;
+
+
+ try
+ {
+ m_msgid = 0;
+ m_qname.clear();
+ m_pldap = NULL;
+ m_authenticator = NULL;
+ m_ttl = 0;
+ m_axfrqlen = 0;
+ m_last_modified = 0;
+ m_qlog = arg().mustDo( "query-logging" );
+ m_default_ttl = arg().asNum( "default-ttl" );
+ m_myname = "[LdapBackend]";
+
+ setArgPrefix( "ldap" + suffix );
+
+ m_getdn = false;
+ m_reconnect_attempts = getArgAsNum( "reconnect-attempts" );
+ m_list_fcnt = &LdapBackend::list_simple;
+ m_lookup_fcnt = &LdapBackend::lookup_simple;
+ m_prepare_fcnt = &LdapBackend::prepare_simple;
+
+ if( getArg( "method" ) == "tree" )
+ {
+ m_lookup_fcnt = &LdapBackend::lookup_tree;
+ }
+
+ if( getArg( "method" ) == "strict" || mustDo( "disable-ptrrecord" ) )
+ {
+ m_list_fcnt = &LdapBackend::list_strict;
+ m_lookup_fcnt = &LdapBackend::lookup_strict;
+ m_prepare_fcnt = &LdapBackend::prepare_strict;
+ }
+
+ stringtok( hosts, getArg( "host" ), ", " );
+ idx = ldap_host_index++ % hosts.size();
+ hoststr = hosts[idx];
+
+ for( i = 1; i < hosts.size(); i++ )
+ {
+ hoststr += " " + hosts[ ( idx + i ) % hosts.size() ];
+ }
+
+ L << Logger::Info << m_myname << " LDAP servers = " << hoststr << endl;
+
+ m_pldap = new PowerLDAP( hoststr.c_str(), LDAP_PORT, mustDo( "starttls" ) );
+ m_pldap->setOption( LDAP_OPT_DEREF, LDAP_DEREF_ALWAYS );
+
+ string bindmethod = getArg( "bindmethod" );
+ if ( bindmethod == "gssapi" ) {
+ setenv( "KRB5CCNAME", getArg( "krb5-ccache" ).c_str(), 1 );
+ m_authenticator = new LdapGssapiAuthenticator( getArg( "krb5-keytab" ), getArg( "krb5-ccache" ), getArgAsNum( "timeout" ) );
+ }
+ else {
+ m_authenticator = new LdapSimpleAuthenticator( getArg( "binddn" ), getArg( "secret" ), getArgAsNum( "timeout" ) );
+ }
+ m_pldap->bind( m_authenticator );
+
+ L << Logger::Notice << m_myname << " Ldap connection succeeded" << endl;
+ return;
+ }
+ catch( LDAPTimeout < )
+ {
+ L << Logger::Error << m_myname << " Ldap connection to server failed because of timeout" << endl;
+ }
+ catch( LDAPException &le )
+ {
+ L << Logger::Error << m_myname << " Ldap connection to server failed: " << le.what() << endl;
+ }
+ catch( std::exception &e )
+ {
+ L << Logger::Error << m_myname << " Caught STL exception: " << e.what() << endl;
+ }
+
+ if( m_pldap != NULL ) { delete( m_pldap ); }
+ throw( PDNSException( "Unable to connect to ldap server" ) );
+}
- try
- {
- m_msgid = 0;
- m_qname.clear();
- m_pldap = NULL;
- m_ttl = 0;
- m_axfrqlen = 0;
- m_last_modified = 0;
- m_qlog = arg().mustDo( "query-logging" );
- m_default_ttl = arg().asNum( "default-ttl" );
- m_myname = "[LdapBackend]";
-
- setArgPrefix( "ldap" + suffix );
-
- m_getdn = false;
- m_list_fcnt = &LdapBackend::list_simple;
- m_lookup_fcnt = &LdapBackend::lookup_simple;
- m_prepare_fcnt = &LdapBackend::prepare_simple;
-
- if( getArg( "method" ) == "tree" )
- {
- m_lookup_fcnt = &LdapBackend::lookup_tree;
- }
-
- if( getArg( "method" ) == "strict" || mustDo( "disable-ptrrecord" ) )
- {
- m_list_fcnt = &LdapBackend::list_strict;
- m_lookup_fcnt = &LdapBackend::lookup_strict;
- m_prepare_fcnt = &LdapBackend::prepare_strict;
- }
-
- stringtok( hosts, getArg( "host" ), ", " );
- idx = ldap_host_index++ % hosts.size();
- hoststr = hosts[idx];
-
- for( i = 1; i < hosts.size(); i++ )
- {
- hoststr += " " + hosts[ ( idx + i ) % hosts.size() ];
- }
-
- L << Logger::Info << m_myname << " LDAP servers = " << hoststr << endl;
-
- m_pldap = new PowerLDAP( hoststr.c_str(), LDAP_PORT, mustDo( "starttls" ) );
- m_pldap->setOption( LDAP_OPT_DEREF, LDAP_DEREF_ALWAYS );
- m_pldap->bind( getArg( "binddn" ), getArg( "secret" ), LDAP_AUTH_SIMPLE, getArgAsNum( "timeout" ) );
-
- L << Logger::Notice << m_myname << " Ldap connection succeeded" << endl;
- return;
- }
- catch( LDAPTimeout < )
- {
- L << Logger::Error << m_myname << " Ldap connection to server failed because of timeout" << endl;
- }
- catch( LDAPException &le )
- {
- L << Logger::Error << m_myname << " Ldap connection to server failed: " << le.what() << endl;
- }
- catch( std::exception &e )
- {
- L << Logger::Error << m_myname << " Caught STL exception: " << e.what() << endl;
- }
- if( m_pldap != NULL ) { delete( m_pldap ); }
- throw( PDNSException( "Unable to connect to ldap server" ) );
+LdapBackend::~LdapBackend()
+{
+ delete( m_pldap );
+ delete( m_authenticator );
+ L << Logger::Notice << m_myname << " Ldap connection closed" << endl;
}
-LdapBackend::~LdapBackend()
+bool LdapBackend::reconnect()
{
- if( m_pldap != NULL ) { delete( m_pldap ); }
- try {
- L << Logger::Notice << m_myname << " Ldap connection closed" << endl;
- }
- catch (...) {
- }
+ int attempts = m_reconnect_attempts;
+ bool connected = false;
+ while ( !connected && attempts > 0 ) {
+ L << Logger::Debug << m_myname << " Reconnection attempts left: " << attempts << endl;
+ connected = m_pldap->connect();
+ if ( !connected )
+ Utility::usleep( 250 );
+ --attempts;
+ }
+
+ if ( connected )
+ m_pldap->bind( m_authenticator );
+
+ return connected;
}
bool LdapBackend::list( const DNSName& target, int domain_id, bool include_disabled )
{
- try
- {
- m_qname = target;
- m_axfrqlen = target.toStringRootDot().length();
- m_adomain = m_adomains.end(); // skip loops in get() first time
-
- return (this->*m_list_fcnt)( target, domain_id );
- }
- catch( LDAPTimeout < )
- {
- L << Logger::Warning << m_myname << " Unable to get zone " << target << " from LDAP directory: " << lt.what() << endl;
- throw( DBException( "LDAP server timeout" ) );
- }
- catch( LDAPException &le )
- {
- L << Logger::Error << m_myname << " Unable to get zone " << target << " from LDAP directory: " << le.what() << endl;
- throw( PDNSException( "LDAP server unreachable" ) ); // try to reconnect to another server
- }
- catch( std::exception &e )
- {
- L << Logger::Error << m_myname << " Caught STL exception for target " << target << ": " << e.what() << endl;
- throw( DBException( "STL exception" ) );
- }
-
- return false;
+ try
+ {
+ m_qname = target;
+ m_axfrqlen = target.toStringRootDot().length();
+ m_adomain = m_adomains.end(); // skip loops in get() first time
+
+ return (this->*m_list_fcnt)( target, domain_id );
+ }
+ catch( LDAPTimeout < )
+ {
+ L << Logger::Warning << m_myname << " Unable to get zone " << target << " from LDAP directory: " << lt.what() << endl;
+ throw( DBException( "LDAP server timeout" ) );
+ }
+ catch( LDAPNoConnection &lnc )
+ {
+ L << Logger::Warning << m_myname << " Connection to LDAP lost, trying to reconnect" << endl;
+ if ( reconnect() )
+ this->list( target, domain_id );
+ else
+ throw PDNSException( "Failed to reconnect to LDAP server" );
+ }
+ catch( LDAPException &le )
+ {
+ L << Logger::Error << m_myname << " Unable to get zone " << target << " from LDAP directory: " << le.what() << endl;
+ throw( PDNSException( "LDAP server unreachable" ) ); // try to reconnect to another server
+ }
+ catch( std::exception &e )
+ {
+ L << Logger::Error << m_myname << " Caught STL exception for target " << target << ": " << e.what() << endl;
+ throw( DBException( "STL exception" ) );
+ }
+
+ return false;
}
inline bool LdapBackend::list_simple( const DNSName& target, int domain_id )
{
- string dn;
- string filter;
- string qesc;
-
-
- dn = getArg( "basedn" );
- qesc = toLower( m_pldap->escape( target.toStringRootDot() ) );
-
- // search for SOARecord of target
- filter = strbind( ":target:", "&(associatedDomain=" + qesc + ")(sOARecord=*)", getArg( "filter-axfr" ) );
- m_msgid = m_pldap->search( dn, LDAP_SCOPE_SUBTREE, filter, (const char**) ldap_attrany );
- m_pldap->getSearchEntry( m_msgid, m_result, true );
-
- if( m_result.count( "dn" ) && !m_result["dn"].empty() )
- {
- if( !mustDo( "basedn-axfr-override" ) )
- {
- dn = m_result["dn"][0];
- }
- m_result.erase( "dn" );
- }
-
- prepare();
- filter = strbind( ":target:", "associatedDomain=*." + qesc, getArg( "filter-axfr" ) );
- DLOG( L << Logger::Debug << m_myname << " Search = basedn: " << dn << ", filter: " << filter << endl );
- m_msgid = m_pldap->search( dn, LDAP_SCOPE_SUBTREE, filter, (const char**) ldap_attrany );
-
- return true;
+ string dn;
+ string filter;
+ string qesc;
+
+
+ dn = getArg( "basedn" );
+ qesc = toLower( m_pldap->escape( target.toStringRootDot() ) );
+
+ // search for SOARecord of target
+ filter = strbind( ":target:", "&(associatedDomain=" + qesc + ")(sOARecord=*)", getArg( "filter-axfr" ) );
+ m_msgid = m_pldap->search( dn, LDAP_SCOPE_SUBTREE, filter, (const char**) ldap_attrany );
+ m_pldap->getSearchEntry( m_msgid, m_result, true );
+
+ if( m_result.count( "dn" ) && !m_result["dn"].empty() )
+ {
+ if( !mustDo( "basedn-axfr-override" ) )
+ {
+ dn = m_result["dn"][0];
+ }
+ m_result.erase( "dn" );
+ }
+
+ prepare();
+ filter = strbind( ":target:", "associatedDomain=*." + qesc, getArg( "filter-axfr" ) );
+ DLOG( L << Logger::Debug << m_myname << " Search = basedn: " << dn << ", filter: " << filter << endl );
+ m_msgid = m_pldap->search( dn, LDAP_SCOPE_SUBTREE, filter, (const char**) ldap_attrany );
+
+ return true;
}
inline bool LdapBackend::list_strict( const DNSName& target, int domain_id )
{
- if( target.isPartOf(DNSName("in-addr.arpa")) || target.isPartOf(DNSName(".ip6.arpa")) )
- {
- L << Logger::Warning << m_myname << " Request for reverse zone AXFR, but this is not supported in strict mode" << endl;
- return false; // AXFR isn't supported in strict mode. Use simple mode and additional PTR records
- }
+ if( target.isPartOf(DNSName("in-addr.arpa")) || target.isPartOf(DNSName(".ip6.arpa")) )
+ {
+ L << Logger::Warning << m_myname << " Request for reverse zone AXFR, but this is not supported in strict mode" << endl;
+ return false; // AXFR isn't supported in strict mode. Use simple mode and additional PTR records
+ }
- return list_simple( target, domain_id );
+ return list_simple( target, domain_id );
}
void LdapBackend::lookup( const QType &qtype, const DNSName &qname, DNSPacket *dnspkt, int zoneid )
{
- try
- {
- m_axfrqlen = 0;
- m_qname = qname;
- m_adomain = m_adomains.end(); // skip loops in get() first time
- m_qtype = qtype;
-
- if( m_qlog ) { L.log( "Query: '" + qname.toStringRootDot() + "|" + qtype.getName() + "'", Logger::Error ); }
- (this->*m_lookup_fcnt)( qtype, qname, dnspkt, zoneid );
- }
- catch( LDAPTimeout < )
- {
- L << Logger::Warning << m_myname << " Unable to search LDAP directory: " << lt.what() << endl;
- throw( DBException( "LDAP server timeout" ) );
- }
- catch( LDAPException &le )
- {
- L << Logger::Error << m_myname << " Unable to search LDAP directory: " << le.what() << endl;
- throw( PDNSException( "LDAP server unreachable" ) ); // try to reconnect to another server
- }
- catch( std::exception &e )
- {
- L << Logger::Error << m_myname << " Caught STL exception for qname " << qname << ": " << e.what() << endl;
- throw( DBException( "STL exception" ) );
- }
+ try
+ {
+ m_axfrqlen = 0;
+ m_qname = qname;
+ m_adomain = m_adomains.end(); // skip loops in get() first time
+ m_qtype = qtype;
+
+ if( m_qlog ) { L.log( "Query: '" + qname.toStringRootDot() + "|" + qtype.getName() + "'", Logger::Error ); }
+ (this->*m_lookup_fcnt)( qtype, qname, dnspkt, zoneid );
+ }
+ catch( LDAPTimeout < )
+ {
+ L << Logger::Warning << m_myname << " Unable to search LDAP directory: " << lt.what() << endl;
+ throw( DBException( "LDAP server timeout" ) );
+ }
+ catch( LDAPNoConnection &lnc )
+ {
+ L << Logger::Warning << m_myname << " Connection to LDAP lost, trying to reconnect" << endl;
+ if ( reconnect() )
+ this->lookup( qtype, qname, dnspkt, zoneid );
+ else
+ throw PDNSException( "Failed to reconnect to LDAP server" );
+ }
+ catch( LDAPException &le )
+ {
+ L << Logger::Error << m_myname << " Unable to search LDAP directory: " << le.what() << endl;
+ throw( PDNSException( "LDAP server unreachable" ) ); // try to reconnect to another server
+ }
+ catch( std::exception &e )
+ {
+ L << Logger::Error << m_myname << " Caught STL exception for qname " << qname << ": " << e.what() << endl;
+ throw( DBException( "STL exception" ) );
+ }
}
void LdapBackend::lookup_simple( const QType &qtype, const DNSName &qname, DNSPacket *dnspkt, int zoneid )
{
- string filter, attr, qesc;
- const char** attributes = ldap_attrany + 1; // skip associatedDomain
- const char* attronly[] = { NULL, "dNSTTL", "modifyTimestamp", NULL };
+ string filter, attr, qesc;
+ const char** attributes = ldap_attrany + 1; // skip associatedDomain
+ const char* attronly[] = { NULL, "dNSTTL", "modifyTimestamp", NULL };
- qesc = toLower( m_pldap->escape( qname.toStringRootDot() ) );
- filter = "associatedDomain=" + qesc;
+ qesc = toLower( m_pldap->escape( qname.toStringRootDot() ) );
+ filter = "associatedDomain=" + qesc;
- if( qtype.getCode() != QType::ANY )
- {
- attr = qtype.getName() + "Record";
- filter = "&(" + filter + ")(" + attr + "=*)";
- attronly[0] = attr.c_str();
- attributes = attronly;
- }
+ if( qtype.getCode() != QType::ANY )
+ {
+ attr = qtype.getName() + "Record";
+ filter = "&(" + filter + ")(" + attr + "=*)";
+ attronly[0] = attr.c_str();
+ attributes = attronly;
+ }
- filter = strbind( ":target:", filter, getArg( "filter-lookup" ) );
+ filter = strbind( ":target:", filter, getArg( "filter-lookup" ) );
- DLOG( L << Logger::Debug << m_myname << " Search = basedn: " << getArg( "basedn" ) << ", filter: " << filter << ", qtype: " << qtype.getName() << endl );
- m_msgid = m_pldap->search( getArg( "basedn" ), LDAP_SCOPE_SUBTREE, filter, attributes );
+ DLOG( L << Logger::Debug << m_myname << " Search = basedn: " << getArg( "basedn" ) << ", filter: " << filter << ", qtype: " << qtype.getName() << endl );
+ m_msgid = m_pldap->search( getArg( "basedn" ), LDAP_SCOPE_SUBTREE, filter, attributes );
}
void LdapBackend::lookup_strict( const QType &qtype, const DNSName &qname, DNSPacket *dnspkt, int zoneid )
{
- int len;
- vector<string> parts;
- string filter, attr, qesc;
- const char** attributes = ldap_attrany + 1; // skip associatedDomain
- const char* attronly[] = { NULL, "dNSTTL", "modifyTimestamp", NULL };
-
-
- qesc = toLower( m_pldap->escape( qname.toStringRootDot() ) );
- stringtok( parts, qesc, "." );
- len = qesc.length();
-
- if( parts.size() == 6 && len > 13 && qesc.substr( len - 13, 13 ) == ".in-addr.arpa" ) // IPv4 reverse lookups
- {
- filter = "aRecord=" + ptr2ip4( parts );
- attronly[0] = "associatedDomain";
- attributes = attronly;
- }
- else if( parts.size() == 34 && len > 9 && ( qesc.substr( len - 9, 9 ) == ".ip6.arpa" ) ) // IPv6 reverse lookups
- {
- filter = "aAAARecord=" + ptr2ip6( parts );
- attronly[0] = "associatedDomain";
- attributes = attronly;
- }
- else // IPv4 and IPv6 lookups
- {
- filter = "associatedDomain=" + qesc;
- if( qtype.getCode() != QType::ANY )
- {
- attr = qtype.getName() + "Record";
- filter = "&(" + filter + ")(" + attr + "=*)";
- attronly[0] = attr.c_str();
- attributes = attronly;
- }
- }
-
- filter = strbind( ":target:", filter, getArg( "filter-lookup" ) );
-
- DLOG( L << Logger::Debug << m_myname << " Search = basedn: " << getArg( "basedn" ) << ", filter: " << filter << ", qtype: " << qtype.getName() << endl );
- m_msgid = m_pldap->search( getArg( "basedn" ), LDAP_SCOPE_SUBTREE, filter, attributes );
+ int len;
+ vector<string> parts;
+ string filter, attr, qesc;
+ const char** attributes = ldap_attrany + 1; // skip associatedDomain
+ const char* attronly[] = { NULL, "dNSTTL", "modifyTimestamp", NULL };
+
+
+ qesc = toLower( m_pldap->escape( qname.toStringRootDot() ) );
+ stringtok( parts, qesc, "." );
+ len = qesc.length();
+
+ if( parts.size() == 6 && len > 13 && qesc.substr( len - 13, 13 ) == ".in-addr.arpa" ) // IPv4 reverse lookups
+ {
+ filter = "aRecord=" + ptr2ip4( parts );
+ attronly[0] = "associatedDomain";
+ attributes = attronly;
+ }
+ else if( parts.size() == 34 && len > 9 && ( qesc.substr( len - 9, 9 ) == ".ip6.arpa" ) ) // IPv6 reverse lookups
+ {
+ filter = "aAAARecord=" + ptr2ip6( parts );
+ attronly[0] = "associatedDomain";
+ attributes = attronly;
+ }
+ else // IPv4 and IPv6 lookups
+ {
+ filter = "associatedDomain=" + qesc;
+ if( qtype.getCode() != QType::ANY )
+ {
+ attr = qtype.getName() + "Record";
+ filter = "&(" + filter + ")(" + attr + "=*)";
+ attronly[0] = attr.c_str();
+ attributes = attronly;
+ }
+ }
+
+ filter = strbind( ":target:", filter, getArg( "filter-lookup" ) );
+
+ DLOG( L << Logger::Debug << m_myname << " Search = basedn: " << getArg( "basedn" ) << ", filter: " << filter << ", qtype: " << qtype.getName() << endl );
+ m_msgid = m_pldap->search( getArg( "basedn" ), LDAP_SCOPE_SUBTREE, filter, attributes );
}
void LdapBackend::lookup_tree( const QType &qtype, const DNSName &qname, DNSPacket *dnspkt, int zoneid )
{
- string filter, attr, qesc, dn;
- const char** attributes = ldap_attrany + 1; // skip associatedDomain
- const char* attronly[] = { NULL, "dNSTTL", "modifyTimestamp", NULL };
- vector<string> parts;
+ string filter, attr, qesc, dn;
+ const char** attributes = ldap_attrany + 1; // skip associatedDomain
+ const char* attronly[] = { NULL, "dNSTTL", "modifyTimestamp", NULL };
+ vector<string> parts;
- qesc = toLower( m_pldap->escape( qname.toStringRootDot() ) );
- filter = "associatedDomain=" + qesc;
+ qesc = toLower( m_pldap->escape( qname.toStringRootDot() ) );
+ filter = "associatedDomain=" + qesc;
- if( qtype.getCode() != QType::ANY )
- {
- attr = qtype.getName() + "Record";
- filter = "&(" + filter + ")(" + attr + "=*)";
- attronly[0] = attr.c_str();
- attributes = attronly;
- }
+ if( qtype.getCode() != QType::ANY )
+ {
+ attr = qtype.getName() + "Record";
+ filter = "&(" + filter + ")(" + attr + "=*)";
+ attronly[0] = attr.c_str();
+ attributes = attronly;
+ }
- filter = strbind( ":target:", filter, getArg( "filter-lookup" ) );
+ filter = strbind( ":target:", filter, getArg( "filter-lookup" ) );
- stringtok( parts, toLower( qname.toString() ), "." );
- for(auto i = parts.crbegin(); i != parts.crend(); i++ )
- {
- dn = "dc=" + *i + "," + dn;
- }
+ stringtok( parts, toLower( qname.toString() ), "." );
+ for(auto i = parts.crbegin(); i != parts.crend(); i++ )
+ {
+ dn = "dc=" + *i + "," + dn;
+ }
- DLOG( L << Logger::Debug << m_myname << " Search = basedn: " << dn + getArg( "basedn" ) << ", filter: " << filter << ", qtype: " << qtype.getName() << endl );
- m_msgid = m_pldap->search( dn + getArg( "basedn" ), LDAP_SCOPE_BASE, filter, attributes );
+ DLOG( L << Logger::Debug << m_myname << " Search = basedn: " << dn + getArg( "basedn" ) << ", filter: " << filter << ", qtype: " << qtype.getName() << endl );
+ m_msgid = m_pldap->search( dn + getArg( "basedn" ), LDAP_SCOPE_BASE, filter, attributes );
}
inline bool LdapBackend::prepare()
{
- m_adomains.clear();
- m_ttl = m_default_ttl;
- m_last_modified = 0;
-
- if( m_result.count( "dNSTTL" ) && !m_result["dNSTTL"].empty() )
- {
- char* endptr;
-
- m_ttl = (uint32_t) strtol( m_result["dNSTTL"][0].c_str(), &endptr, 10 );
- if( *endptr != '\0' )
- {
- L << Logger::Warning << m_myname << " Invalid time to live for " << m_qname << ": " << m_result["dNSTTL"][0] << endl;
- m_ttl = m_default_ttl;
- }
- m_result.erase( "dNSTTL" );
- }
-
- if( m_result.count( "modifyTimestamp" ) && !m_result["modifyTimestamp"].empty() )
- {
- if( ( m_last_modified = str2tstamp( m_result["modifyTimestamp"][0] ) ) == 0 )
- {
- L << Logger::Warning << m_myname << " Invalid modifyTimestamp for " << m_qname << ": " << m_result["modifyTimestamp"][0] << endl;
- }
- m_result.erase( "modifyTimestamp" );
- }
-
- if( !(this->*m_prepare_fcnt)() )
- {
- return false;
- }
-
- m_adomain = m_adomains.begin();
- m_attribute = m_result.begin();
- m_value = m_attribute->second.begin();
-
- return true;
+ m_adomains.clear();
+ m_ttl = m_default_ttl;
+ m_last_modified = 0;
+
+ if( m_result.count( "dNSTTL" ) && !m_result["dNSTTL"].empty() )
+ {
+ char* endptr;
+
+ m_ttl = (uint32_t) strtol( m_result["dNSTTL"][0].c_str(), &endptr, 10 );
+ if( *endptr != '\0' )
+ {
+ L << Logger::Warning << m_myname << " Invalid time to live for " << m_qname << ": " << m_result["dNSTTL"][0] << endl;
+ m_ttl = m_default_ttl;
+ }
+ m_result.erase( "dNSTTL" );
+ }
+
+ if( m_result.count( "modifyTimestamp" ) && !m_result["modifyTimestamp"].empty() )
+ {
+ if( ( m_last_modified = str2tstamp( m_result["modifyTimestamp"][0] ) ) == 0 )
+ {
+ L << Logger::Warning << m_myname << " Invalid modifyTimestamp for " << m_qname << ": " << m_result["modifyTimestamp"][0] << endl;
+ }
+ m_result.erase( "modifyTimestamp" );
+ }
+
+ if( !(this->*m_prepare_fcnt)() )
+ {
+ return false;
+ }
+
+ m_adomain = m_adomains.begin();
+ m_attribute = m_result.begin();
+ m_value = m_attribute->second.begin();
+
+ return true;
}
inline bool LdapBackend::prepare_simple()
{
- if( !m_axfrqlen ) // request was a normal lookup()
- {
- m_adomains.push_back( m_qname );
- }
- else // request was a list() for AXFR
- {
- if( m_result.count( "associatedDomain" ) )
- {
- for(auto i = m_result["associatedDomain"].begin(); i != m_result["associatedDomain"].end(); i++ ) {
- if( i->size() >= m_axfrqlen && i->substr( i->size() - m_axfrqlen, m_axfrqlen ) == m_qname.toStringRootDot() /* ugh */ ) {
- m_adomains.push_back( DNSName(*i) );
- }
- }
- m_result.erase( "associatedDomain" );
- }
- }
-
- return true;
+ if( !m_axfrqlen ) // request was a normal lookup()
+ {
+ m_adomains.push_back( m_qname );
+ }
+ else // request was a list() for AXFR
+ {
+ if( m_result.count( "associatedDomain" ) )
+ {
+ for(auto i = m_result["associatedDomain"].begin(); i != m_result["associatedDomain"].end(); i++ ) {
+ if( i->size() >= m_axfrqlen && i->substr( i->size() - m_axfrqlen, m_axfrqlen ) == m_qname.toStringRootDot() /* ugh */ ) {
+ m_adomains.push_back( DNSName(*i) );
+ }
+ }
+ m_result.erase( "associatedDomain" );
+ }
+ }
+
+ return true;
}
inline bool LdapBackend::prepare_strict()
{
- if( !m_axfrqlen ) // request was a normal lookup()
- {
- m_adomains.push_back( m_qname );
- if( m_result.count( "associatedDomain" ) )
- {
- m_result["PTRRecord"] = m_result["associatedDomain"];
- m_result.erase( "associatedDomain" );
- }
- }
- else // request was a list() for AXFR
- {
- if( m_result.count( "associatedDomain" ) )
- {
- for(auto i = m_result["associatedDomain"].begin(); i != m_result["associatedDomain"].end(); i++ ) {
- if( i->size() >= m_axfrqlen && i->substr( i->size() - m_axfrqlen, m_axfrqlen ) == m_qname.toStringRootDot() /* ugh */ ) {
- m_adomains.push_back( DNSName(*i) );
- }
- }
- m_result.erase( "associatedDomain" );
- }
- }
-
- return true;
+ if( !m_axfrqlen ) // request was a normal lookup()
+ {
+ m_adomains.push_back( m_qname );
+ if( m_result.count( "associatedDomain" ) )
+ {
+ m_result["PTRRecord"] = m_result["associatedDomain"];
+ m_result.erase( "associatedDomain" );
+ }
+ }
+ else // request was a list() for AXFR
+ {
+ if( m_result.count( "associatedDomain" ) )
+ {
+ for(auto i = m_result["associatedDomain"].begin(); i != m_result["associatedDomain"].end(); i++ ) {
+ if( i->size() >= m_axfrqlen && i->substr( i->size() - m_axfrqlen, m_axfrqlen ) == m_qname.toStringRootDot() /* ugh */ ) {
+ m_adomains.push_back( DNSName(*i) );
+ }
+ }
+ m_result.erase( "associatedDomain" );
+ }
+ }
+
+ return true;
}
bool LdapBackend::get( DNSResourceRecord &rr )
{
- QType qt;
- vector<string> parts;
- string attrname, qstr;
-
-
- try
- {
- do
- {
- while( m_adomain != m_adomains.end() )
- {
- while( m_attribute != m_result.end() )
- {
- attrname = m_attribute->first;
- qstr = attrname.substr( 0, attrname.length() - 6 ); // extract qtype string from ldap attribute name
- qt = const_cast<char*>(toUpper( qstr ).c_str());
-
- while( m_value != m_attribute->second.end() )
- {
- if(m_qtype != qt && m_qtype != QType::ANY) {
- m_value++;
- continue;
- }
-
- rr.qtype = qt;
- rr.qname = *m_adomain;
- rr.ttl = m_ttl;
- rr.last_modified = m_last_modified;
- rr.content = *m_value;
- m_value++;
-
- DLOG( L << Logger::Debug << m_myname << " Record = qname: " << rr.qname << ", qtype: " << (rr.qtype).getName() << ", ttl: " << rr.ttl << ", content: " << rr.content << endl );
- return true;
- }
-
- m_attribute++;
- m_value = m_attribute->second.begin();
- }
- m_adomain++;
- m_attribute = m_result.begin();
- m_value = m_attribute->second.begin();
- }
- }
- while( m_pldap->getSearchEntry( m_msgid, m_result, m_getdn ) && prepare() );
-
- }
- catch( LDAPTimeout < )
- {
- L << Logger::Warning << m_myname << " Search failed: " << lt.what() << endl;
- throw( DBException( "LDAP server timeout" ) );
- }
- catch( LDAPException &le )
- {
- L << Logger::Error << m_myname << " Search failed: " << le.what() << endl;
- throw( PDNSException( "LDAP server unreachable" ) ); // try to reconnect to another server
- }
- catch( std::exception &e )
- {
- L << Logger::Error << m_myname << " Caught STL exception for " << m_qname << ": " << e.what() << endl;
- throw( DBException( "STL exception" ) );
- }
-
- return false;
+ QType qt;
+ vector<string> parts;
+ string attrname, qstr;
+
+
+ try
+ {
+ do
+ {
+ while( m_adomain != m_adomains.end() )
+ {
+ while( m_attribute != m_result.end() )
+ {
+ attrname = m_attribute->first;
+ qstr = attrname.substr( 0, attrname.length() - 6 ); // extract qtype string from ldap attribute name
+ qt = const_cast<char*>(toUpper( qstr ).c_str());
+
+ while( m_value != m_attribute->second.end() )
+ {
+ if(m_qtype != qt && m_qtype != QType::ANY) {
+ m_value++;
+ continue;
+ }
+
+
+ rr.qtype = qt;
+ rr.qname = *m_adomain;
+ rr.ttl = m_ttl;
+ rr.last_modified = m_last_modified;
+ rr.content = *m_value;
+ m_value++;
+
+ DLOG( L << Logger::Debug << m_myname << " Record = qname: " << rr.qname << ", qtype: " << (rr.qtype).getName() << ", ttl: " << rr.ttl << ", content: " << rr.content << endl );
+ return true;
+ }
+
+ m_attribute++;
+ m_value = m_attribute->second.begin();
+ }
+ m_adomain++;
+ m_attribute = m_result.begin();
+ m_value = m_attribute->second.begin();
+ }
+ }
+ while( m_pldap->getSearchEntry( m_msgid, m_result, m_getdn ) && prepare() );
+
+ }
+ catch( LDAPTimeout < )
+ {
+ L << Logger::Warning << m_myname << " Search failed: " << lt.what() << endl;
+ throw( DBException( "LDAP server timeout" ) );
+ }
+ catch( LDAPException &le )
+ {
+ L << Logger::Error << m_myname << " Search failed: " << le.what() << endl;
+ throw( PDNSException( "LDAP server unreachable" ) ); // try to reconnect to another server
+ }
+ catch( std::exception &e )
+ {
+ L << Logger::Error << m_myname << " Caught STL exception for " << m_qname << ": " << e.what() << endl;
+ throw( DBException( "STL exception" ) );
+ }
+
+ return false;
+}
+
+
+
+void LdapBackend::getUpdatedMasters( vector<DomainInfo>* domains )
+{
+ string filter;
+ int msgid;
+ PowerLDAP::sentry_t result;
+ const char* attronly[] = {
+ "associatedDomain",
+ NULL
+ };
+
+ try
+ {
+ // First get all domains on which we are master.
+ filter = strbind( ":target:", "&(SOARecord=*)(PdnsDomainId=*)", getArg( "filter-axfr" ) );
+ msgid = m_pldap->search( getArg( "basedn" ), LDAP_SCOPE_SUBTREE, filter, attronly );
+ }
+ catch( LDAPTimeout < )
+ {
+ L << Logger::Warning << m_myname << " Unable to search LDAP directory: " << lt.what() << endl;
+ throw( DBException( "LDAP server timeout" ) );
+ }
+ catch( LDAPNoConnection &lnc )
+ {
+ L << Logger::Warning << m_myname << " Connection to LDAP lost, trying to reconnect" << endl;
+ if ( reconnect() )
+ this->getUpdatedMasters( domains );
+ else
+ throw PDNSException( "Failed to reconnect to LDAP server" );
+ }
+ catch( LDAPException &le )
+ {
+ L << Logger::Error << m_myname << " Unable to search LDAP directory: " << le.what() << endl;
+ throw( PDNSException( "LDAP server unreachable" ) ); // try to reconnect to another server
+ }
+ catch( std::exception &e )
+ {
+ throw( DBException( "STL exception" ) );
+ }
+
+ while( m_pldap->getSearchEntry( msgid, result ) ) {
+ if( !result.count( "associatedDomain" ) || result["associatedDomain"].empty() )
+ continue;
+
+ DomainInfo di;
+ if ( !getDomainInfo( result["associatedDomain"][0], di ) )
+ continue;
+
+ di.backend = this;
+
+ if( di.notified_serial < di.serial )
+ domains->push_back( di );
+ }
}
- bool LdapBackend::getDomainInfo( const string& domain, DomainInfo& di )
+void LdapBackend::setNotified( uint32_t id, uint32_t serial )
{
- string filter;
- SOAData sd;
- const char* attronly[] = { "sOARecord", NULL };
-
-
- // search for SOARecord of domain
- filter = "(&(associatedDomain=" + toLower( m_pldap->escape( domain ) ) + ")(SOARecord=*))";
- m_msgid = m_pldap->search( getArg( "basedn" ), LDAP_SCOPE_SUBTREE, filter, attronly );
- m_pldap->getSearchEntry( m_msgid, m_result );
+ string filter;
+ int msgid;
+ PowerLDAP::sresult_t results;
+ PowerLDAP::sentry_t entry;
+ const char* attronly[] = { "associatedDomain", NULL };
+
+ try
+ {
+ // Try to find the notified domain
+ filter = strbind( ":target:", "PdnsDomainId=" + std::to_string( id ), getArg( "filter-axfr" ) );
+ msgid = m_pldap->search( getArg( "basedn" ), LDAP_SCOPE_SUBTREE, filter, attronly );
+ m_pldap->getSearchResults( msgid, results, true );
+ }
+ catch( LDAPTimeout < )
+ {
+ L << Logger::Warning << m_myname << " Unable to search LDAP directory: " << lt.what() << endl;
+ throw( DBException( "LDAP server timeout" ) );
+ }
+ catch( LDAPNoConnection &lnc )
+ {
+ L << Logger::Warning << m_myname << " Connection to LDAP lost, trying to reconnect" << endl;
+ if ( reconnect() )
+ this->setNotified( id, serial );
+ else
+ throw PDNSException( "Failed to reconnect to LDAP server" );
+ }
+ catch( LDAPException &le )
+ {
+ L << Logger::Error << m_myname << " Unable to search LDAP directory: " << le.what() << endl;
+ throw( PDNSException( "LDAP server unreachable" ) ); // try to reconnect to another server
+ }
+ catch( std::exception &e )
+ {
+ throw( DBException( "STL exception" ) );
+ }
+
+ if ( results.empty() )
+ throw PDNSException( "No results found when trying to update domain notified_serial for ID " + std::to_string( id ) );
+
+ entry = results.front();
+ string dn = entry["dn"][0];
+ string serialStr = std::to_string( serial );
+ LDAPMod *mods[2];
+ LDAPMod mod;
+ char *vals[2];
+
+ mod.mod_op = LDAP_MOD_REPLACE;
+ mod.mod_type = (char*)"PdnsDomainNotifiedSerial";
+ vals[0] = const_cast<char*>( serialStr.c_str() );
+ vals[1] = NULL;
+ mod.mod_values = vals;
+
+ mods[0] = &mod;
+ mods[1] = NULL;
+
+ try
+ {
+ m_pldap->modify( dn, mods );
+ }
+ catch( LDAPNoConnection &lnc )
+ {
+ L << Logger::Warning << m_myname << " Connection to LDAP lost, trying to reconnect" << endl;
+ if ( reconnect() )
+ this->setNotified( id, serial );
+ else
+ throw PDNSException( "Failed to reconnect to LDAP server" );
+ }
+ catch( LDAPException &le )
+ {
+ L << Logger::Error << m_myname << " Unable to search LDAP directory: " << le.what() << endl;
+ throw( PDNSException( "LDAP server unreachable" ) ); // try to reconnect to another server
+ }
+ catch( std::exception &e )
+ {
+ throw( DBException( "STL exception" ) );
+ }
+}
- if( m_result.count( "sOARecord" ) && !m_result["sOARecord"].empty() )
- {
- sd.serial = 0;
- fillSOAData( m_result["sOARecord"][0], sd );
- di.id = 0;
- di.serial = sd.serial;
- di.zone = DNSName(domain);
- di.last_check = 0;
- di.backend = this;
- di.kind = DomainInfo::Master;
- return true;
- }
-
- return false;
+bool LdapBackend::getDomainInfo( const string& domain, DomainInfo& di )
+{
+ string filter;
+ SOAData sd;
+ int msgid;
+ PowerLDAP::sentry_t result;
+ const char* attronly[] = {
+ "sOARecord",
+ "PdnsDomainId",
+ "PdnsDomainNotifiedSerial",
+ "PdnsDomainLastCheck",
+ "PdnsDomainMaster",
+ "PdnsDomainType",
+ NULL
+ };
+
+ try
+ {
+ // search for SOARecord of domain
+ filter = "(&(associatedDomain=" + toLower( m_pldap->escape( domain ) ) + ")(SOARecord=*))";
+ m_msgid = m_pldap->search( getArg( "basedn" ), LDAP_SCOPE_SUBTREE, filter, attronly );
+ m_pldap->getSearchEntry( msgid, result );
+ }
+ catch( LDAPTimeout < )
+ {
+ L << Logger::Warning << m_myname << " Unable to search LDAP directory: " << lt.what() << endl;
+ throw( DBException( "LDAP server timeout" ) );
+ }
+ catch( LDAPNoConnection &lnc )
+ {
+ L << Logger::Warning << m_myname << " Connection to LDAP lost, trying to reconnect" << endl;
+ if ( reconnect() )
+ this->getDomainInfo( domain, di );
+ else
+ throw PDNSException( "Failed to reconnect to LDAP server" );
+ }
+ catch( LDAPException &le )
+ {
+ L << Logger::Error << m_myname << " Unable to search LDAP directory: " << le.what() << endl;
+ throw( PDNSException( "LDAP server unreachable" ) ); // try to reconnect to another server
+ }
+ catch( std::exception &e )
+ {
+ throw( DBException( "STL exception" ) );
+ }
+
+ if( result.count( "sOARecord" ) && !result["sOARecord"].empty() )
+ {
+ sd.serial = 0;
+ fillSOAData( result["sOARecord"][0], sd );
+
+ if ( result.count( "PdnsDomainId" ) && !result["PdnsDomainId"].empty() )
+ di.id = std::stoi( result["PdnsDomainId"][0] );
+ else
+ di.id = 0;
+
+ di.serial = sd.serial;
+ di.zone = DNSName(domain);
+
+ if( result.count( "PdnsDomainLastCheck" ) && !result["PdnsDomainLastCheck"].empty() )
+ di.last_check = pdns_stou( result["PdnsDomainLastCheck"][0] );
+ else
+ di.last_check = 0;
+
+ if ( result.count( "PdnsDomainNotifiedSerial" ) && !result["PdnsDomainNotifiedSerial"].empty() )
+ di.notified_serial = pdns_stou( result["PdnsDomainNotifiedSerial"][0] );
+ else
+ di.notified_serial = 0;
+
+ if ( result.count( "PdnsDomainMaster" ) && !result["PdnsDomainMaster"].empty() )
+ di.masters = result["PdnsDomainMaster"];
+
+ if ( result.count( "PdnsDomainType" ) && !result["PdnsDomainType"].empty() ) {
+ string kind = result["PdnsDomainType"][0];
+ if ( kind == "master" )
+ di.kind = DomainInfo::Master;
+ else if ( kind == "slave" )
+ di.kind = DomainInfo::Slave;
+ else
+ di.kind = DomainInfo::Native;
+ }
+ else {
+ di.kind = DomainInfo::Native;
+ }
+
+ return true;
+ }
+
+ return false;
}
class LdapFactory : public BackendFactory
{
-
-public:
-
- LdapFactory() : BackendFactory( "ldap" ) {}
-
-
- void declareArguments( const string &suffix="" )
- {
- declare( suffix, "host", "One or more LDAP server with ports or LDAP URIs (separated by spaces)","ldap://127.0.0.1:389/" );
- declare( suffix, "starttls", "Use TLS to encrypt connection (unused for LDAP URIs)", "no" );
- declare( suffix, "basedn", "Search root in ldap tree (must be set)","" );
- declare( suffix, "basedn-axfr-override", "Override base dn for AXFR subtree search", "no" );
- declare( suffix, "binddn", "User dn for non anonymous binds","" );
- declare( suffix, "secret", "User password for non anonymous binds", "" );
- declare( suffix, "timeout", "Seconds before connecting to server fails", "5" );
- declare( suffix, "method", "How to search entries (simple, strict or tree)", "simple" );
- declare( suffix, "filter-axfr", "LDAP filter for limiting AXFR results", "(:target:)" );
- declare( suffix, "filter-lookup", "LDAP filter for limiting IP or name lookups", "(:target:)" );
- declare( suffix, "disable-ptrrecord", "Deprecated, use ldap-method=strict instead", "no" );
- }
-
-
- DNSBackend* make( const string &suffix="" )
- {
- return new LdapBackend( suffix );
- }
+ public:
+
+ LdapFactory() : BackendFactory( "ldap" ) {}
+
+ void declareArguments( const string &suffix="" )
+ {
+ declare( suffix, "host", "One or more LDAP server with ports or LDAP URIs (separated by spaces)","ldap://127.0.0.1:389/" );
+ declare( suffix, "starttls", "Use TLS to encrypt connection (unused for LDAP URIs)", "no" );
+ declare( suffix, "basedn", "Search root in ldap tree (must be set)","" );
+ declare( suffix, "basedn-axfr-override", "Override base dn for AXFR subtree search", "no" );
+ declare( suffix, "bindmethod", "Bind method to use (simple or gssapi)", "simple" );
+ declare( suffix, "binddn", "User dn for non anonymous binds","" );
+ declare( suffix, "secret", "User password for non anonymous binds", "" );
+ declare( suffix, "krb5-keytab", "The keytab to use for GSSAPI authentication", "" );
+ declare( suffix, "krb5-ccache", "The credentials cache used for GSSAPI authentication", "" );
+ declare( suffix, "timeout", "Seconds before connecting to server fails", "5" );
+ declare( suffix, "method", "How to search entries (simple, strict or tree)", "simple" );
+ declare( suffix, "filter-axfr", "LDAP filter for limiting AXFR results", "(:target:)" );
+ declare( suffix, "filter-lookup", "LDAP filter for limiting IP or name lookups", "(:target:)" );
+ declare( suffix, "disable-ptrrecord", "Deprecated, use ldap-method=strict instead", "no" );
+ declare( suffix, "reconnect-attempts", "Number of attempts to re-establish a lost LDAP connection", "5" );
+ }
+
+
+ DNSBackend* make( const string &suffix="" )
+ {
+ return new LdapBackend( suffix );
+ }
};
class LdapLoader
{
- LdapFactory factory;
+ LdapFactory factory;
-public:
+ public:
- LdapLoader()
- {
- BackendMakers().report( &factory );
- L << Logger::Info << "[ldapbackend] This is the ldap backend version " VERSION
+ LdapLoader()
+ {
+ BackendMakers().report( &factory );
+ L << Logger::Info << "[ldapbackend] This is the ldap backend version " VERSION
#ifndef REPRODUCIBLE
- << " (" __DATE__ " " __TIME__ ")"
+ << " (" __DATE__ " " __TIME__ ")"
#endif
- << " reporting" << endl;
- }
+ << " reporting" << endl;
+ }
};
using std::string;
using std::vector;
-
+class LdapAuthenticator;
/*
* Known DNS RR types
*/
static const char* ldap_attrany[] = {
- "associatedDomain",
- "dNSTTL",
- "aRecord",
- "nSRecord",
- "cNAMERecord",
- "sOARecord",
- "pTRRecord",
- "hInfoRecord",
- "mXRecord",
- "tXTRecord",
- "rPRecord",
- "aFSDBRecord",
-// "SigRecord",
- "KeyRecord",
-// "gPosRecord",
- "aAAARecord",
- "lOCRecord",
- "sRVRecord",
- "nAPTRRecord",
- "kXRecord",
- "certRecord",
-// "a6Record",
-// "dNameRecord",
-// "aPLRecord",
- "dSRecord",
- "sSHFPRecord",
- "iPSecKeyRecord",
- "rRSIGRecord",
- "nSECRecord",
- "dNSKeyRecord",
- "dHCIDRecord",
- "sPFRecord",
- "TYPE65534Record",
- "EUI48Record",
- "EUI64Record",
- "TYPE65226Record",
- "modifyTimestamp",
- NULL
+ "associatedDomain",
+ "dNSTTL",
+ "aRecord",
+ "nSRecord",
+ "cNAMERecord",
+ "sOARecord",
+ "pTRRecord",
+ "hInfoRecord",
+ "mXRecord",
+ "tXTRecord",
+ "rPRecord",
+ "aFSDBRecord",
+// "SigRecord",
+ "KeyRecord",
+// "gPosRecord",
+ "aAAARecord",
+ "lOCRecord",
+ "sRVRecord",
+ "nAPTRRecord",
+ "kXRecord",
+ "certRecord",
+// "a6Record",
+// "dNameRecord",
+// "aPLRecord",
+ "dSRecord",
+ "sSHFPRecord",
+ "iPSecKeyRecord",
+ "rRSIGRecord",
+ "nSECRecord",
+ "dNSKeyRecord",
+ "dHCIDRecord",
+ "sPFRecord",
+ "TYPE65534Record",
+ "EUI48Record",
+ "EUI64Record",
+ "TYPE65226Record",
+ "modifyTimestamp",
+ NULL
};
class LdapBackend : public DNSBackend
{
- bool m_getdn;
- bool m_qlog;
- int m_msgid;
- uint32_t m_ttl;
- uint32_t m_default_ttl;
- unsigned int m_axfrqlen;
- time_t m_last_modified;
- string m_myname;
- DNSName m_qname;
- PowerLDAP* m_pldap;
- PowerLDAP::sentry_t m_result;
- PowerLDAP::sentry_t::iterator m_attribute;
- vector<string>::iterator m_value;
- vector<DNSName>::iterator m_adomain;
- vector<DNSName> m_adomains;
- QType m_qtype;
-
- bool (LdapBackend::*m_list_fcnt)( const DNSName&, int );
- void (LdapBackend::*m_lookup_fcnt)( const QType&, const DNSName&, DNSPacket*, int );
- bool (LdapBackend::*m_prepare_fcnt)();
-
- bool list_simple( const DNSName& target, int domain_id );
- bool list_strict( const DNSName& target, int domain_id );
-
- void lookup_simple( const QType& qtype, const DNSName& qdomain, DNSPacket* p, int zoneid );
- void lookup_strict( const QType& qtype, const DNSName& qdomain, DNSPacket* p, int zoneid );
- void lookup_tree( const QType& qtype, const DNSName& qdomain, DNSPacket* p, int zoneid );
-
- bool prepare();
- bool prepare_simple();
- bool prepare_strict();
-
- bool getDomainInfo( const string& domain, DomainInfo& di );
-
-public:
-
- LdapBackend( const string &suffix="" );
- ~LdapBackend();
-
- bool list( const DNSName& target, int domain_id, bool include_disabled=false );
- void lookup( const QType& qtype, const DNSName& qdomain, DNSPacket* p = 0, int zoneid = -1 );
- bool get( DNSResourceRecord& rr );
+ bool m_getdn;
+ bool m_qlog;
+ int m_msgid;
+ uint32_t m_ttl;
+ uint32_t m_default_ttl;
+ unsigned int m_axfrqlen;
+ time_t m_last_modified;
+ string m_myname;
+ DNSName m_qname;
+ PowerLDAP* m_pldap;
+ LdapAuthenticator *m_authenticator;
+ PowerLDAP::sentry_t m_result;
+ PowerLDAP::sentry_t::iterator m_attribute;
+ vector<string>::iterator m_value;
+ vector<DNSName>::iterator m_adomain;
+ vector<DNSName> m_adomains;
+ QType m_qtype;
+ int m_reconnect_attempts;
+
+ bool (LdapBackend::*m_list_fcnt)( const DNSName&, int );
+ void (LdapBackend::*m_lookup_fcnt)( const QType&, const DNSName&, DNSPacket*, int );
+ bool (LdapBackend::*m_prepare_fcnt)();
+
+ bool list_simple( const DNSName& target, int domain_id );
+ bool list_strict( const DNSName& target, int domain_id );
+
+ void lookup_simple( const QType& qtype, const DNSName& qdomain, DNSPacket* p, int zoneid );
+ void lookup_strict( const QType& qtype, const DNSName& qdomain, DNSPacket* p, int zoneid );
+ void lookup_tree( const QType& qtype, const DNSName& qdomain, DNSPacket* p, int zoneid );
+
+ bool prepare();
+ bool prepare_simple();
+ bool prepare_strict();
+
+ bool reconnect();
+
+ public:
+
+ LdapBackend( const string &suffix="" );
+ ~LdapBackend();
+
+ // Native backend
+ bool list( const DNSName& target, int domain_id, bool include_disabled=false );
+ void lookup( const QType& qtype, const DNSName& qdomain, DNSPacket* p = 0, int zoneid = -1 );
+ bool get( DNSResourceRecord& rr );
+
+ bool getDomainInfo( const string& domain, DomainInfo& di );
+
+ // Master backend
+ void getUpdatedMasters( vector<DomainInfo>* domains );
+ void setNotified( uint32_t id, uint32_t serial );
};
#endif /* LDAPBACKEND_HH */
--- /dev/null
+#include "ldaputils.hh"
+#include <sys/time.h>
+
+void ldapSetOption( LDAP *conn, int option, void *value )
+{
+ if( ldap_set_option( conn, option, value ) != LDAP_OPT_SUCCESS )
+ {
+ throw( LDAPException( "Unable to set option" ) );
+ }
+}
+
+void ldapGetOption( LDAP *conn, int option, void *value )
+{
+ if( ldap_get_option( conn, option, value ) != LDAP_OPT_SUCCESS )
+ {
+ throw( LDAPException( "Unable to get option" ) );
+ }
+}
+
+std::string ldapGetError( LDAP *conn, int code )
+{
+ if ( code == -1 )
+ ldapGetOption( conn, LDAP_OPT_ERROR_NUMBER, &code );
+ return std::string( ldap_err2string( code ) );
+}
+
+int ldapWaitResult( LDAP *conn, int msgid, int timeout, LDAPMessage** result )
+{
+ struct timeval tv;
+ LDAPMessage* res;
+
+
+ tv.tv_sec = timeout;
+ tv.tv_usec = 0;
+
+ int rc = ldap_result( conn, msgid, LDAP_MSG_ONE, &tv, &res );
+
+ if ( rc == -1 || rc == 0 )
+ return rc;
+
+ if( result == NULL )
+ {
+ ldap_msgfree( res );
+ return rc;
+ }
+
+ *result = res;
+ return rc;
+}
--- /dev/null
+/*
+ * PowerDNS LDAP Connector
+ * By PowerDNS.COM BV
+ * By Norbert Sendetzky <norbert@linuxnetworks.de> (2003-2007)
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2
+ * as published by the Free Software Foundation.
+ *
+ * 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 St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "exceptions.hh"
+
+#include <ldap.h>
+#include <string>
+
+#ifndef LDAPUTILS_HH
+#define LDAPUTILS_HH
+
+void ldapSetOption( LDAP *conn, int option, void *value );
+
+void ldapGetOption( LDAP *conn, int option, void *value );
+
+std::string ldapGetError( LDAP *conn, int code );
+
+int ldapWaitResult( LDAP *conn, int msgid = LDAP_RES_ANY, int timeout = 0, LDAPMessage** result = NULL );
+
+#endif // LDAPUTILS_HH
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
+#include "exceptions.hh"
+#include "ldapauthenticator.hh"
+#include "ldaputils.hh"
#include "powerldap.hh"
#include "pdns/misc.hh"
#include <sys/time.h>
PowerLDAP::PowerLDAP( const string& hosts, uint16_t port, bool tls )
{
- d_ld = 0;
- d_hosts = hosts;
- d_port = port;
- d_tls = tls;
- ensureConnect();
+ d_ld = 0;
+ d_hosts = hosts;
+ d_port = port;
+ d_tls = tls;
+ ensureConnect();
}
void PowerLDAP::ensureConnect()
{
- int err;
+ int err;
- if(d_ld) {
- ldap_unbind_ext( d_ld, NULL, NULL );
- }
+ if(d_ld) {
+ ldap_unbind_ext( d_ld, NULL, NULL );
+ }
#ifdef HAVE_LDAP_INITIALIZE
- if( ( err = ldap_initialize( &d_ld, d_hosts.c_str() ) ) != LDAP_SUCCESS )
- {
- string ldapuris;
- vector<string> uris;
- stringtok( uris, d_hosts );
-
- for( size_t i = 0; i < uris.size(); i++ )
- {
- ldapuris += " ldap://" + uris[i];
- }
-
- if( ( err = ldap_initialize( &d_ld, ldapuris.c_str() ) ) != LDAP_SUCCESS )
- {
- throw LDAPException( "Error initializing LDAP connection to '" + ldapuris + ": " + getError( err ) );
- }
- }
+ if( ( err = ldap_initialize( &d_ld, d_hosts.c_str() ) ) != LDAP_SUCCESS )
+ {
+ string ldapuris;
+ vector<string> uris;
+ stringtok( uris, d_hosts );
+
+ for( size_t i = 0; i < uris.size(); i++ )
+ {
+ ldapuris += " ldap://" + uris[i];
+ }
+
+ if( ( err = ldap_initialize( &d_ld, ldapuris.c_str() ) ) != LDAP_SUCCESS )
+ {
+ throw LDAPException( "Error initializing LDAP connection to '" + ldapuris + ": " + getError( err ) );
+ }
+ }
#else
- if( ( d_ld = ldap_init( d_hosts.c_str(), d_port ) ) == NULL )
- {
- throw LDAPException( "Error initializing LDAP connection to '" + d_hosts + "': " + string( strerror( errno ) ) );
- }
+ if( ( d_ld = ldap_init( d_hosts.c_str(), d_port ) ) == NULL )
+ {
+ throw LDAPException( "Error initializing LDAP connection to '" + d_hosts + "': " + string( strerror( errno ) ) );
+ }
#endif
- int protocol = LDAP_VERSION3;
- if( ldap_set_option( d_ld, LDAP_OPT_PROTOCOL_VERSION, &protocol ) != LDAP_OPT_SUCCESS )
- {
- protocol = LDAP_VERSION2;
- if( ldap_set_option( d_ld, LDAP_OPT_PROTOCOL_VERSION, &protocol ) != LDAP_OPT_SUCCESS )
- {
- ldap_unbind_ext( d_ld, NULL, NULL );
- throw LDAPException( "Couldn't set protocol version to LDAPv3 or LDAPv2" );
- }
- }
-
- if( d_tls && ( err = ldap_start_tls_s( d_ld, NULL, NULL ) ) != LDAP_SUCCESS )
- {
- ldap_unbind_ext( d_ld, NULL, NULL );
- throw LDAPException( "Couldn't perform STARTTLS: " + getError( err ) );
- }
+ int protocol = LDAP_VERSION3;
+ if( ldap_set_option( d_ld, LDAP_OPT_PROTOCOL_VERSION, &protocol ) != LDAP_OPT_SUCCESS )
+ {
+ protocol = LDAP_VERSION2;
+ if( ldap_set_option( d_ld, LDAP_OPT_PROTOCOL_VERSION, &protocol ) != LDAP_OPT_SUCCESS )
+ {
+ ldap_unbind_ext( d_ld, NULL, NULL );
+ throw LDAPException( "Couldn't set protocol version to LDAPv3 or LDAPv2" );
+ }
+ }
+
+ if( d_tls && ( err = ldap_start_tls_s( d_ld, NULL, NULL ) ) != LDAP_SUCCESS )
+ {
+ ldap_unbind_ext( d_ld, NULL, NULL );
+ throw LDAPException( "Couldn't perform STARTTLS: " + getError( err ) );
+ }
}
PowerLDAP::~PowerLDAP()
{
- ldap_unbind_ext( d_ld, NULL, NULL );
+ ldap_unbind_ext( d_ld, NULL, NULL );
+}
+
+
+bool PowerLDAP::connect()
+{
+ try
+ {
+ ensureConnect();
+ return true;
+ }
+ catch( LDAPException &le )
+ {
+ return false;
+ }
}
void PowerLDAP::setOption( int option, int value )
{
- if( ldap_set_option( d_ld, option, (void*) &value ) != LDAP_OPT_SUCCESS )
- {
- throw( LDAPException( "Unable to set option" ) );
- }
+ ldapSetOption( d_ld, option, (void*) &value );
}
void PowerLDAP::getOption( int option, int *value )
{
- if( ldap_get_option( d_ld, option, (void*) value ) != LDAP_OPT_SUCCESS )
- {
- throw( LDAPException( "Unable to get option" ) );
- }
+ ldapGetOption( d_ld, option, (void*) value );
+}
+
+
+void PowerLDAP::bind( LdapAuthenticator* authenticator )
+{
+ if ( !authenticator->authenticate( d_ld ) )
+ throw LDAPException( "Failed to bind to LDAP server: " + authenticator->getError() );
}
void PowerLDAP::bind( const string& ldapbinddn, const string& ldapsecret, int method, int timeout )
{
- int msgid;
+ int msgid;
#ifdef HAVE_LDAP_SASL_BIND
- int rc;
- struct berval passwd;
+ int rc;
+ struct berval passwd;
- passwd.bv_val = (char *)ldapsecret.c_str();
- passwd.bv_len = strlen( passwd.bv_val );
+ passwd.bv_val = (char *)ldapsecret.c_str();
+ passwd.bv_len = strlen( passwd.bv_val );
- if( ( rc = ldap_sasl_bind( d_ld, ldapbinddn.c_str(), LDAP_SASL_SIMPLE, &passwd, NULL, NULL, &msgid ) ) != LDAP_SUCCESS )
- {
- throw LDAPException( "Failed to bind to LDAP server: " + getError( rc ) );
- }
+ if( ( rc = ldap_sasl_bind( d_ld, ldapbinddn.c_str(), LDAP_SASL_SIMPLE, &passwd, NULL, NULL, &msgid ) ) != LDAP_SUCCESS )
+ {
+ throw LDAPException( "Failed to bind to LDAP server: " + getError( rc ) );
+ }
#else
- if( ( msgid = ldap_bind( d_ld, ldapbinddn.c_str(), ldapsecret.c_str(), method ) ) == -1 )
- {
- throw LDAPException( "Failed to bind to LDAP server: " + getError( msgid ) );
- }
+ if( ( msgid = ldap_bind( d_ld, ldapbinddn.c_str(), ldapsecret.c_str(), method ) ) == -1 )
+ {
+ throw LDAPException( "Failed to bind to LDAP server: " + getError( msgid ) );
+ }
#endif
- waitResult( msgid, timeout, NULL );
+ waitResult( msgid, timeout, NULL );
}
void PowerLDAP::simpleBind( const string& ldapbinddn, const string& ldapsecret )
{
- this->bind( ldapbinddn, ldapsecret, LDAP_AUTH_SIMPLE, 30 );
+ this->bind( ldapbinddn, ldapsecret, LDAP_AUTH_SIMPLE, 30 );
+}
+
+
+void PowerLDAP::modify( const string &dn, LDAPMod *mods[], LDAPControl **scontrols, LDAPControl **ccontrols )
+{
+ int rc;
+
+ rc = ldap_modify_ext_s( d_ld, dn.c_str(), mods, scontrols, ccontrols );
+ if ( rc == LDAP_SERVER_DOWN || rc == LDAP_CONNECT_ERROR )
+ throw LDAPNoConnection();
+ else if ( rc != LDAP_SUCCESS )
+ throw LDAPException( "Error modifying LDAP entry " + dn + ": " + getError( rc ) );
}
int PowerLDAP::search( const string& base, int scope, const string& filter, const char** attr )
{
- int msgid, rc;
+ int msgid, rc;
- if( ( rc = ldap_search_ext( d_ld, base.c_str(), scope, filter.c_str(), const_cast<char**> (attr), 0, NULL, NULL, NULL, LDAP_NO_LIMIT, &msgid ) ) != LDAP_SUCCESS )
- {
- throw LDAPException( "Starting LDAP search: " + getError( rc ) );
- }
+ if ( ( rc = ldap_search_ext( d_ld, base.c_str(), scope, filter.c_str(), const_cast<char**> (attr), 0, NULL, NULL, NULL, LDAP_NO_LIMIT, &msgid ) ) ) {
+ throw LDAPException( "Starting LDAP search: " + getError( rc ) );
+ }
- return msgid;
+ return msgid;
}
int PowerLDAP::waitResult( int msgid, int timeout, LDAPMessage** result )
{
- struct timeval tv;
- LDAPMessage* res;
-
- tv.tv_sec = timeout;
- tv.tv_usec = 0;
- int rc;
-
- rc = ldap_result( d_ld, msgid, LDAP_MSG_ONE, &tv, &res );
-
- switch( rc )
- {
- case -1:
- ensureConnect();
- throw LDAPException( "Error waiting for LDAP result: " + getError() );
- case 0:
- throw LDAPTimeout();
- }
-
- if( result == NULL )
- {
- ldap_msgfree( res );
- return rc;
- }
-
- *result = res;
- return rc;
+ return ldapWaitResult( d_ld, msgid, timeout, result );
}
bool PowerLDAP::getSearchEntry( int msgid, sentry_t& entry, bool dn, int timeout )
{
- int i;
- char* attr;
- BerElement* ber;
- struct berval** berval;
- vector<string> values;
- LDAPMessage* result;
- LDAPMessage* object;
-
-
- if( ( i = waitResult( msgid, timeout, &result ) ) == LDAP_RES_SEARCH_RESULT )
- {
- ldap_msgfree( result );
- return false;
- }
-
- if( i != LDAP_RES_SEARCH_ENTRY )
+ int i;
+ char* attr;
+ BerElement* ber;
+ struct berval** berval;
+ vector<string> values;
+ LDAPMessage* result;
+ LDAPMessage* object;
+ bool hasResult = false;
+
+ while ( !hasResult ) {
+ i = waitResult( msgid, timeout, &result );
+ // Here we deliberately ignore LDAP_RES_SEARCH_REFERENCE as we don't follow them.
+ // Instead we get the next result.
+ // If the function returned an error (i <= 0) we'll deal with after this loop too.
+ if ( i == LDAP_RES_SEARCH_ENTRY || i == LDAP_RES_SEARCH_RESULT || i <= 0 )
+ hasResult = true;
+ }
+
+ if ( i == -1 ) {
+ // Error while retrieving the message
+ int err_code;
+ ldapGetOption( d_ld, LDAP_OPT_ERROR_NUMBER, &err_code );
+ if ( err_code == LDAP_SERVER_DOWN || err_code == LDAP_CONNECT_ERROR )
+ throw LDAPNoConnection();
+ else
+ throw LDAPException( "PowerLDAP::getSearchEntry(): Error when retrieving LDAP result: " + getError( err_code ) );
+ }
+
+ if ( i == 0 ) {
+ // Timeout expired before the message could be retrieved
+ throw LDAPTimeout();
+ }
+
+ if ( i == LDAP_RES_SEARCH_RESULT ) {
+ // We're done with this request
+ ldap_msgfree( result );
+ return false;
+ }
+
+ if( ( object = ldap_first_entry( d_ld, result ) ) == NULL )
+ {
+ ldap_msgfree( result );
+ throw LDAPException( "Couldn't get first result entry: " + getError() );
+ }
+
+ entry.clear();
+
+ if( dn )
+ {
+ attr = ldap_get_dn( d_ld, object );
+ values.push_back( string( attr ) );
+ ldap_memfree( attr );
+ entry["dn"] = values;
+ }
+
+ if( ( attr = ldap_first_attribute( d_ld, object, &ber ) ) != NULL )
+ {
+ do
+ {
+ if( ( berval = ldap_get_values_len( d_ld, object, attr ) ) != NULL )
+ {
+ values.clear();
+ for( i = 0; i < ldap_count_values_len( berval ); i++ )
{
- ldap_msgfree( result );
- throw LDAPException( "Search returned an unexpected result" );
+ values.push_back( berval[i]->bv_val ); // use berval[i]->bv_len for non string values?
}
- if( ( object = ldap_first_entry( d_ld, result ) ) == NULL )
- {
- ldap_msgfree( result );
- throw LDAPException( "Couldn't get first result entry: " + getError() );
- }
-
- entry.clear();
-
- if( dn )
- {
- attr = ldap_get_dn( d_ld, object );
- values.push_back( string( attr ) );
- ldap_memfree( attr );
- entry["dn"] = values;
- }
+ entry[attr] = values;
+ ldap_value_free_len( berval );
+ }
+ ldap_memfree( attr );
+ }
+ while( ( attr = ldap_next_attribute( d_ld, object, ber ) ) != NULL );
- if( ( attr = ldap_first_attribute( d_ld, object, &ber ) ) != NULL )
- {
- do
- {
- if( ( berval = ldap_get_values_len( d_ld, object, attr ) ) != NULL )
- {
- values.clear();
- for( i = 0; i < ldap_count_values_len( berval ); i++ )
- {
- values.push_back( berval[i]->bv_val ); // use berval[i]->bv_len for non string values?
- }
-
- entry[attr] = values;
- ldap_value_free_len( berval );
- }
- ldap_memfree( attr );
- }
- while( ( attr = ldap_next_attribute( d_ld, object, ber ) ) != NULL );
-
- ber_free( ber, 0 );
- }
+ ber_free( ber, 0 );
+ }
- ldap_msgfree( result );
- return true;
+ ldap_msgfree( result );
+ return true;
}
void PowerLDAP::getSearchResults( int msgid, sresult_t& result, bool dn, int timeout )
{
- sentry_t entry;
+ sentry_t entry;
- result.clear();
- while( getSearchEntry( msgid, entry, dn, timeout ) )
- {
- result.push_back( entry );
- }
+ result.clear();
+ while( getSearchEntry( msgid, entry, dn, timeout ) )
+ {
+ result.push_back( entry );
+ }
}
const string PowerLDAP::getError( int rc )
{
- if( rc == -1 ) { getOption( LDAP_OPT_ERROR_NUMBER, &rc ); }
-
- return string( ldap_err2string( rc ) );;
+ return ldapGetError( d_ld, rc );
}
const string PowerLDAP::escape( const string& str )
{
- string a;
- string::const_iterator i;
- char tmp[4];
-
- for( i = str.begin(); i != str.end(); i++ )
- {
- // RFC4515 3
- if( *i == '*' ||
- *i == '(' ||
- *i == ')' ||
- *i == '\\' ||
- *i == '\0' ||
- *i > 127)
- {
- sprintf(tmp,"\\%02x", (unsigned char)*i);
-
- a += tmp;
- }
- else
- a += *i;
- }
-
- return a;
+ string a;
+ string::const_iterator i;
+ char tmp[4];
+
+ for( i = str.begin(); i != str.end(); i++ )
+ {
+ // RFC4515 3
+ if( *i == '*' ||
+ *i == '(' ||
+ *i == ')' ||
+ *i == '\\' ||
+ *i == '\0' ||
+ *i > 127)
+ {
+ sprintf(tmp,"\\%02x", (unsigned char)*i);
+
+ a += tmp;
+ }
+ else
+ a += *i;
+ }
+
+ return a;
}
#include <map>
#include <string>
#include <vector>
-#include <exception>
#include <stdexcept>
#include <inttypes.h>
#include <errno.h>
using std::string;
using std::vector;
-class LDAPException : public std::runtime_error
-{
-public:
- explicit LDAPException( const string &str ) : std::runtime_error( str ) {}
-};
-
-class LDAPTimeout : public LDAPException
-{
-public:
- explicit LDAPTimeout() : LDAPException( "Timeout" ) {}
-};
+class LdapAuthenticator;
class PowerLDAP
{
- LDAP* d_ld;
- string d_hosts;
- int d_port;
- bool d_tls;
-
- const string getError( int rc = -1 );
- int waitResult( int msgid = LDAP_RES_ANY, int timeout = 0, LDAPMessage** result = NULL );
- void ensureConnect();
-
-public:
- typedef map<string, vector<string> > sentry_t;
- typedef vector<sentry_t> sresult_t;
-
- PowerLDAP( const string& hosts = "ldap://127.0.0.1/", uint16_t port = LDAP_PORT, bool tls = false );
- ~PowerLDAP();
-
- void getOption( int option, int* value );
- void setOption( int option, int value );
-
- void bind( const string& ldapbinddn = "", const string& ldapsecret = "", int method = LDAP_AUTH_SIMPLE, int timeout = 5 );
- void simpleBind( const string& ldapbinddn = "", const string& ldapsecret = "" );
- int search( const string& base, int scope, const string& filter, const char** attr = 0 );
-
- bool getSearchEntry( int msgid, sentry_t& entry, bool dn = false, int timeout = 5 );
- void getSearchResults( int msgid, sresult_t& result, bool dn = false, int timeout = 5 );
-
- static const string escape( const string& tobe );
+ LDAP* d_ld;
+ string d_hosts;
+ int d_port;
+ bool d_tls;
+
+ const string getError( int rc = -1 );
+ int waitResult( int msgid = LDAP_RES_ANY, int timeout = 0, LDAPMessage** result = NULL );
+ void ensureConnect();
+
+ public:
+ typedef map<string, vector<string> > sentry_t;
+ typedef vector<sentry_t> sresult_t;
+
+ PowerLDAP( const string& hosts = "ldap://127.0.0.1/", uint16_t port = LDAP_PORT, bool tls = false );
+ ~PowerLDAP();
+
+ bool connect();
+
+ void getOption( int option, int* value );
+ void setOption( int option, int value );
+
+ void bind( LdapAuthenticator *authenticator );
+ void bind( const string& ldapbinddn = "", const string& ldapsecret = "", int method = LDAP_AUTH_SIMPLE, int timeout = 5 );
+ void simpleBind( const string& ldapbinddn = "", const string& ldapsecret = "" );
+ int search( const string& base, int scope, const string& filter, const char** attr = 0 );
+ void modify( const string& dn, LDAPMod *mods[], LDAPControl **scontrols = 0, LDAPControl **ccontrols = 0 );
+
+ bool getSearchEntry( int msgid, sentry_t& entry, bool dn = false, int timeout = 5 );
+ void getSearchResults( int msgid, sresult_t& result, bool dn = false, int timeout = 5 );
+
+ static const string escape( const string& tobe );
};
inline string ptr2ip4( vector<string>& parts )
{
- string ip;
- parts.pop_back();
- parts.pop_back();
+ string ip;
+ parts.pop_back();
+ parts.pop_back();
- ip = parts.back();
- parts.pop_back();
+ ip = parts.back();
+ parts.pop_back();
- while( !parts.empty() )
- {
- ip += "." + parts.back();
- parts.pop_back();
- }
+ while( !parts.empty() )
+ {
+ ip += "." + parts.back();
+ parts.pop_back();
+ }
- return ip;
+ return ip;
}
inline string ptr2ip6( vector<string>& parts )
{
- int i = 0;
- string ip;
-
-
- parts.pop_back();
- parts.pop_back();
-
- while( i < 3 && parts.size() > 1 && parts.back() == "0" )
- {
- parts.pop_back();
- i++;
- }
-
- while( i++ < 4 && !parts.empty() )
- {
- ip += parts.back();
- parts.pop_back();
- }
-
- while( !parts.empty() )
- {
- i = 0;
- ip += ":";
-
- while( i < 3 && parts.size() > 1 && parts.back() == "0" )
- {
- parts.pop_back();
- i++;
- }
-
- while( i++ < 4 && !parts.empty() )
- {
- ip += parts.back();
- parts.pop_back();
- }
- }
-
- return ip;
+ int i = 0;
+ string ip;
+
+
+ parts.pop_back();
+ parts.pop_back();
+
+ while( i < 3 && parts.size() > 1 && parts.back() == "0" )
+ {
+ parts.pop_back();
+ i++;
+ }
+
+ while( i++ < 4 && !parts.empty() )
+ {
+ ip += parts.back();
+ parts.pop_back();
+ }
+
+ while( !parts.empty() )
+ {
+ i = 0;
+ ip += ":";
+
+ while( i < 3 && parts.size() > 1 && parts.back() == "0" )
+ {
+ parts.pop_back();
+ i++;
+ }
+
+ while( i++ < 4 && !parts.empty() )
+ {
+ ip += parts.back();
+ parts.pop_back();
+ }
+ }
+
+ return ip;
}
inline string ip2ptr4( const string& ip )
{
- string ptr;
- vector<string> parts;
+ string ptr;
+ vector<string> parts;
- stringtok( parts, ip, "." );
- while( !parts.empty() )
- {
- ptr += parts.back() + ".";
- parts.pop_back();
- }
+ stringtok( parts, ip, "." );
+ while( !parts.empty() )
+ {
+ ptr += parts.back() + ".";
+ parts.pop_back();
+ }
- return ptr + "in-addr.arpa";
+ return ptr + "in-addr.arpa";
}
inline string ip2ptr6( const string& ip )
{
- string ptr, part, defstr;
- vector<string> parts;
-
- stringtok( parts, ip, ":" );
- while( !parts.empty() )
- {
- defstr = "0.0.0.0.";
- part = parts.back();
-
- while( part.length() < 4 )
- {
- part = "0" + part;
- }
-
- defstr[0] = part[3];
- defstr[2] = part[2];
- defstr[4] = part[1];
- defstr[6] = part[0];
- ptr += defstr;
- parts.pop_back();
- }
-
- return ptr + "ip6.arpa";
+ string ptr, part, defstr;
+ vector<string> parts;
+
+ stringtok( parts, ip, ":" );
+ while( !parts.empty() )
+ {
+ defstr = "0.0.0.0.";
+ part = parts.back();
+
+ while( part.length() < 4 )
+ {
+ part = "0" + part;
+ }
+
+ defstr[0] = part[3];
+ defstr[2] = part[2];
+ defstr[4] = part[1];
+ defstr[6] = part[0];
+ ptr += defstr;
+ parts.pop_back();
+ }
+
+ return ptr + "ip6.arpa";
}
inline string strbind( const string& search, const string& replace, string subject )
{
- size_t pos = 0;
+ size_t pos = 0;
- while( ( pos = subject.find( search, pos ) ) != string::npos )
- {
- subject.replace( pos, search.size(), replace );
- pos += replace.size();
- }
+ while( ( pos = subject.find( search, pos ) ) != string::npos )
+ {
+ subject.replace( pos, search.size(), replace );
+ pos += replace.size();
+ }
- return subject;
+ return subject;
}
/*
inline time_t str2tstamp( const string& str )
{
- char* tmp;
- struct tm tm;
+ char* tmp;
+ struct tm tm;
- tmp = strptime( str.c_str(), "%Y%m%d%H%M%SZ", &tm );
+ tmp = strptime( str.c_str(), "%Y%m%d%H%M%SZ", &tm );
- if( tmp != NULL && *tmp == 0 )
- {
- return Utility::timegm( &tm );
- }
+ if( tmp != NULL && *tmp == 0 )
+ {
+ return Utility::timegm( &tm );
+ }
- return 0;
+ return 0;
}
#endif
libtestremotebackend_la_SOURCES = \
../../pdns/arguments.hh ../../pdns/arguments.cc \
+ ../../pdns/auth-packetcache.cc ../../pdns/auth-packetcache.hh \
+ ../../pdns/auth-querycache.cc ../../pdns/auth-querycache.hh \
../../pdns/base32.cc \
../../pdns/base64.cc \
../../pdns/dnsbackend.hh ../../pdns/dnsbackend.cc \
../../pdns/dnsrecords.cc \
../../pdns/dnssecinfra.cc \
../../pdns/ednssubnet.cc \
+ ../../pdns/ednsoptions.cc ../../pdns/ednsoptions.hh \
../../pdns/iputils.cc \
../../pdns/logger.cc \
../../pdns/misc.cc \
../../pdns/nsecrecords.cc \
- ../../pdns/packetcache.hh ../../pdns/packetcache.cc \
+ ../../pdns/packetcache.hh \
../../pdns/qtype.cc \
../../pdns/sillyrecords.cc \
../../pdns/statbag.cc \
#include "pdns/arguments.hh"
#include "pdns/json.hh"
#include "pdns/statbag.hh"
-#include "pdns/packetcache.hh"
+#include "pdns/auth-packetcache.hh"
+#include "pdns/auth-querycache.hh"
StatBag S;
-PacketCache PC;
+AuthPacketCache PC;
+AuthQueryCache QC;
ArgvMap &arg()
{
static ArgvMap arg;
#include "pdns/arguments.hh"
#include "pdns/json.hh"
#include "pdns/statbag.hh"
-#include "pdns/packetcache.hh"
+#include "pdns/auth-packetcache.hh"
+#include "pdns/auth-querycache.hh"
StatBag S;
-PacketCache PC;
+AuthPacketCache PC;
+AuthQueryCache QC;
ArgvMap &arg()
{
static ArgvMap arg;
#include "pdns/dnsrecords.hh"
#include "pdns/json.hh"
#include "pdns/statbag.hh"
-#include "pdns/packetcache.hh"
+#include "pdns/auth-packetcache.hh"
+#include "pdns/auth-querycache.hh"
StatBag S;
-PacketCache PC;
+AuthPacketCache PC;
+AuthQueryCache QC;
ArgvMap &arg()
{
static ArgvMap arg;
#include "pdns/arguments.hh"
#include "pdns/json.hh"
#include "pdns/statbag.hh"
-#include "pdns/packetcache.hh"
+#include "pdns/auth-packetcache.hh"
+#include "pdns/auth-querycache.hh"
StatBag S;
-PacketCache PC;
+AuthPacketCache PC;
+AuthQueryCache QC;
ArgvMap &arg()
{
static ArgvMap arg;
#include "pdns/dnsrecords.hh"
#include "pdns/json.hh"
#include "pdns/statbag.hh"
-#include "pdns/packetcache.hh"
+#include "pdns/auth-packetcache.hh"
+#include "pdns/auth-querycache.hh"
StatBag S;
-PacketCache PC;
+AuthPacketCache PC;
+AuthQueryCache QC;
ArgvMap &arg()
{
static ArgvMap arg;
#include "pdns/dnsrecords.hh"
#include "pdns/json.hh"
#include "pdns/statbag.hh"
-#include "pdns/packetcache.hh"
+#include "pdns/auth-packetcache.hh"
+#include "pdns/auth-querycache.hh"
StatBag S;
-PacketCache PC;
+AuthPacketCache PC;
+AuthQueryCache QC;
ArgvMap &arg()
{
static ArgvMap arg;
#include "pdns/arguments.hh"
#include "pdns/json.hh"
#include "pdns/statbag.hh"
-#include "pdns/packetcache.hh"
#include "test-remotebackend-keys.hh"
#2847484148 auto axfr-get
-Zexample.com:ns1.example.com.:ahu.example.com.:2847484148:28800:7200:604800:86400:100000
+&dsdelegation.example.com::ns.example.com.:120
&example.com::ns1.example.com.:120
&example.com::ns2.example.com.:120
-@example.com::smtp-servers.example.com.:10:120
-@example.com::smtp-servers.test.com.:15:120
-+double.example.com:192.168.5.1:120
-&dsdelegation.example.com::ns.example.com.:120
-:dsdelegation.example.com:43:m\341\010\001\312\361\352\256\315\253\347afpx\217\220\042EK\365\375\237\332:120
-:escapedtext.example.com:16:\005begin\022the\040\042middle\042\040p\134art\007the\040end:120
-Cexternal.example.com:somewhere.else.net.:120
-@external-mail.example.com::server1.test.com.:25:120
&france.example.com::ns1.otherprovider.net.:120
&france.example.com::ns2.otherprovider.net.:120
-:google-alias.example.com:65401:\023google-public-dns-a\006google\300\024:120
+&italy.example.com::italy-ns1.example.com.:120
+&italy.example.com::italy-ns2.example.com.:120
+&usa.example.com::usa-ns1.usa.example.com.:120
+&usa.example.com::usa-ns2.usa.example.com.:120
++\052.w5.example.com:1.2.3.5:120
++double.example.com:192.168.5.1:120
+hightype.example.com:192.168.1.5:120
-:hightype.example.com:65534:\007\355\046\000\001:120
+host-0.example.com:192.168.1.0:120
-:host-0.example.com:108:\000PV\233\000\347:120
+host-1.example.com:192.168.1.1:120
-:host-1.example.com:109:\000PV\233\000\347\176W:120
+host-10.example.com:192.168.1.10:120
+host-100.example.com:192.168.1.100:120
+host-1000.example.com:192.168.1.232:120
+host-9998.example.com:192.168.1.14:120
+host-9999.example.com:192.168.1.15:120
+host-for-auto-ptr.example.com:192.0.2.1:120
-:hwinfo.example.com:13:\003abc\003def:120
-:ipv6.example.com:28:\040\001\006\250\000\000\000\001\002\020K\377\376KLa:120
-&italy.example.com::italy-ns1.example.com.:120
-&italy.example.com::italy-ns2.example.com.:120
++host.\052.sub.example.com:192.168.6.1:120
+italy-ns1.example.com:192.168.5.1:120
+italy-ns2.example.com:192.168.5.2:120
+localhost.example.com:127.0.0.1:120
-:location.example.com:29:\000\022\026\023\213\044\310\373\201D\030\300\000\230\230\020:120
-:location.example.com:29:\000\042\026\023t\3331\320\201D\030\300\000\230\230\020:120
-:location.example.com:29:\0002\026\023\213\044\323e\176\273\347\100\000\230\230\020:120
-:location.example.com:29:\000B\026\023t\333\053\274\176\273\347\100\000\230\230\020:120
-Cloop1.example.com:loop2.example.com.:120
-Cloop2.example.com:loop3.example.com.:120
-Cloop3.example.com:loop1.example.com.:120
-@mail.example.com::smtp1.example.com.:25:120
-:multitext.example.com:16:\015text\040part\040one\015text\040part\040two\017text\040part\040three:120
+ns1.example.com:192.168.1.1:120
+ns2.example.com:192.168.1.2:120
-Cnxd.example.com:nxdomain.example.com.:120
+outpost.example.com:192.168.2.1:120
-Crhs-at-expansion.example.com:example.com.:120
-Csemi-external.example.com:bla.something.wtest.com.:120
-Cserver1.example.com:server1.france.example.com.:120
+smtp-servers.example.com:192.168.0.2:120
+smtp-servers.example.com:192.168.0.3:120
+smtp-servers.example.com:192.168.0.4:120
-Csmtp1.example.com:outpost.example.com.:120
-Cstart.example.com:x.y.z.w1.example.com.:120
-Cstart1.example.com:start2.example.com.:120
-Cstart2.example.com:start3.example.com.:120
-Cstart3.example.com:start4.example.com.:120
+start4.example.com:192.168.2.2:120
-+host.\052.sub.example.com:192.168.6.1:120
-:text.example.com:16:\025Hi\054\040this\040is\040some\040text:120
-:text0.example.com:16:\014k\075rsa\073\040p\075one:120
-:text1.example.com:16:\014k\075rsa\073\040p\075one:120
-:text2.example.com:16:\015k\075rsa\134\073\040p\075one:120
-:text3.example.com:16:\015k\075rsa\134\073\040p\075one:120
-@together-too-much.example.com::toomuchinfo-a.example.com.:25:120
-@together-too-much.example.com::toomuchinfo-b.example.com.:25:120
-+toomuchinfo-a.example.com:192.168.99.1:120
+toomuchinfo-a.example.com:192.168.99.10:120
+toomuchinfo-a.example.com:192.168.99.11:120
+toomuchinfo-a.example.com:192.168.99.12:120
+toomuchinfo-a.example.com:192.168.99.17:120
+toomuchinfo-a.example.com:192.168.99.18:120
+toomuchinfo-a.example.com:192.168.99.19:120
-+toomuchinfo-a.example.com:192.168.99.2:120
++toomuchinfo-a.example.com:192.168.99.1:120
+toomuchinfo-a.example.com:192.168.99.20:120
+toomuchinfo-a.example.com:192.168.99.21:120
+toomuchinfo-a.example.com:192.168.99.22:120
+toomuchinfo-a.example.com:192.168.99.23:120
+toomuchinfo-a.example.com:192.168.99.24:120
+toomuchinfo-a.example.com:192.168.99.25:120
++toomuchinfo-a.example.com:192.168.99.2:120
+toomuchinfo-a.example.com:192.168.99.3:120
+toomuchinfo-a.example.com:192.168.99.4:120
+toomuchinfo-a.example.com:192.168.99.5:120
+toomuchinfo-b.example.com:192.168.99.88:120
+toomuchinfo-b.example.com:192.168.99.89:120
+toomuchinfo-b.example.com:192.168.99.90:120
-Cunauth.example.com:no-idea.example.org.:120
-&usa.example.com::usa-ns1.usa.example.com.:120
-&usa.example.com::usa-ns2.usa.example.com.:120
+usa-ns1.usa.example.com:192.168.4.1:120
+usa-ns2.usa.example.com:192.168.4.2:120
+:dsdelegation.example.com:43:m\341\010\001\312\361\352\256\315\253\347afpx\217\220\042EK\365\375\237\332:120
+:escapedtext.example.com:16:\005begin\022the\040\042middle\042\040p\134art\007the\040end:120
+:google-alias.example.com:65401:\023google-public-dns-a\006google\300\024:120
+:hightype.example.com:65534:\007\355\046\000\001:120
+:host-0.example.com:108:\000PV\233\000\347:120
+:host-1.example.com:109:\000PV\233\000\347\176W:120
+:hwinfo.example.com:13:\003abc\003def:120
+:ipv6.example.com:28:\040\001\006\250\000\000\000\001\002\020K\377\376KLa:120
+:location.example.com:29:\0002\026\023\213\044\323e\176\273\347\100\000\230\230\020:120
+:location.example.com:29:\000B\026\023t\333\053\274\176\273\347\100\000\230\230\020:120
+:location.example.com:29:\000\022\026\023\213\044\310\373\201D\030\300\000\230\230\020:120
+:location.example.com:29:\000\042\026\023t\3331\320\201D\030\300\000\230\230\020:120
+:multitext.example.com:16:\015text\040part\040one\015text\040part\040two\017text\040part\040three:120
+:text.example.com:16:\025Hi\054\040this\040is\040some\040text:120
+:text0.example.com:16:\014k\075rsa\073\040p\075one:120
+:text1.example.com:16:\014k\075rsa\073\040p\075one:120
+:text2.example.com:16:\015k\075rsa\134\073\040p\075one:120
+:text3.example.com:16:\015k\075rsa\134\073\040p\075one:120
+@example.com::smtp-servers.example.com.:10:120
+@example.com::smtp-servers.test.com.:15:120
+@external-mail.example.com::server1.test.com.:25:120
+@mail.example.com::smtp1.example.com.:25:120
+@together-too-much.example.com::toomuchinfo-a.example.com.:25:120
+@together-too-much.example.com::toomuchinfo-b.example.com.:25:120
C\052.w1.example.com:x.y.z.w2.example.com.:120
C\052.w2.example.com:x.y.z.w3.example.com.:120
C\052.w3.example.com:x.y.z.w4.example.com.:120
C\052.w4.example.com:x.y.z.w5.example.com.:120
-+\052.w5.example.com:1.2.3.5:120
+Cexternal.example.com:somewhere.else.net.:120
+Cloop1.example.com:loop2.example.com.:120
+Cloop2.example.com:loop3.example.com.:120
+Cloop3.example.com:loop1.example.com.:120
+Cnxd.example.com:nxdomain.example.com.:120
+Crhs-at-expansion.example.com:example.com.:120
+Csemi-external.example.com:bla.something.wtest.com.:120
+Cserver1.example.com:server1.france.example.com.:120
+Csmtp1.example.com:outpost.example.com.:120
+Cstart.example.com:x.y.z.w1.example.com.:120
+Cstart1.example.com:start2.example.com.:120
+Cstart2.example.com:start3.example.com.:120
+Cstart3.example.com:start4.example.com.:120
+Cunauth.example.com:no-idea.example.org.:120
Cwww.example.com:outpost.example.com.:120
+Zexample.com:ns1.example.com.:ahu.example.com.:2847484148:28800:7200:604800:86400:100000
#2005092501 auto axfr-get
-Ztest.com:ns1.test.com.:ahu.example.com.:2005092501:28800:7200:604800:86400:3600
+&blah.test.com::blah.test.com.:3600
+&sub.test.test.com::ns-test.example.net.test.com.:3600
&test.com::ns1.test.com.:3600
&test.com::ns2.test.com.:3600
-@test.com::..:10:3600
-@test.com::smtp-servers.test.com.:15:3600
-:_underscore.test.com:16:\030underscores\040are\040terrible:3600
-:aland.test.com:16:\016\303\205LAND\040ISLANDS:3600
-&blah.test.com::blah.test.com.:3600
-+blah.test.com:192.168.6.1:3600
-+b.c.test.com:5.6.7.8:3600
+\052.a.b.c.test.com:8.7.6.5:3600
++b.c.test.com:5.6.7.8:3600
++blah.test.com:192.168.6.1:3600
+counter.test.com:1.1.1.5:3600
-:d.test.com:39:\002d2\005test2\003com\000:3600
++interrupted-rrset.test.com:1.1.1.1:3600
++interrupted-rrset.test.com:2.2.2.2:3600
++ns1.test.com:1.1.1.1:3600
++ns2.test.com:2.2.2.2:3600
++server1.test.com:1.2.3.4:3600
++www.test.test.com:4.3.2.1:3600
:_double._tcp.dc.test.com:33:\000\000\000d\001\205\007server1\004test\003com\000:3600
:_double._tcp.dc.test.com:33:\000\001\000d\001\205\007server1\004test\003com\000:3600
:_ldap._tcp.dc.test.com:33:\000\000\000d\001\205\007server2\007example\003net\000:3600
:_root._tcp.dc.test.com:33:\000\000\000\000\000\000\000:3600
+:_underscore.test.com:16:\030underscores\040are\040terrible:3600
+:aland.test.com:16:\016\303\205LAND\040ISLANDS:3600
+:d.test.com:39:\002d2\005test2\003com\000:3600
:enum.test.com:35:\000d\0002\001u\007e2u\053sip\000\010testuser\006domain\003com\000:3600
:hightxt.test.com:16:\042v\075spf1\040mx\040ip4\07278.46.192.210\040\342\200\223all:3600
:hightxt.test.com:99:\042v\075spf1\040mx\040ip4\07278.46.192.210\040\342\200\223all:3600
-+ns1.test.com:1.1.1.1:3600
-+ns2.test.com:2.2.2.2:3600
-+server1.test.com:1.2.3.4:3600
+:interrupted-rrset.test.com:16:\023check\040AXFR\040signpipe:3600
:server1.test.com:17:\003ahu\004ds9a\002nl\000\007counter\004test\003com\000:3600
-C\052.test.test.com:server1.test.com.:3600
-&sub.test.test.com::ns-test.example.net.test.com.:3600
-+www.test.test.com:4.3.2.1:3600
-Ctoroot.test.com:..:3600
:urc65226.test.com:65226:ABC:3600
:very-long-txt.test.com:16:\377A\040very\040long\040TXT\040record\041\040boy\040you\040won\047t\040believe\040how\040long.\040A\040very\040long\040TXT\040record\041\040boy\040you\040won\047t\040believe\040how\040long.\040A\040very\040long\040TXT\040record\041\040boy\040you\040won\047t\040believe\040how\040long.\040A\040very\040long\040TXT\040record\041\040boy\040you\040won\047t\040believe\040how\040long.\040A\040very\040long\040TXT\040record\041\040boy\040you\030\040won\047t\040believe\040how\040long\041:3600
+@test.com::..:10:3600
+@test.com::smtp-servers.test.com.:15:3600
+C\052.test.test.com:server1.test.com.:3600
+Ctoroot.test.com:..:3600
Cwithin-server.test.com:outpost.example.com.:3600
Cwww.test.com:server1.test.com.:3600
+Ztest.com:ns1.test.com.:ahu.example.com.:2005092501:28800:7200:604800:86400:3600
#2012060701 auto axfr-get
-Ztest.dyndns:ns1.test.dyndns.:ahu.example.dyndns.:2012060701:28800:7200:604800:86400:3600
&test.dyndns::ns1.test.dyndns.:3600
&test.dyndns::ns2.test.dyndns.:3600
-@test.dyndns::host-1.test.dyndns.:10:3600
-@test.dyndns::host-2.test.dyndns.:20:3600
-Ccname1.test.dyndns:host-1.test.dyndns.:3600
-Ccname2.test.dyndns:host-2.test.dyndns.:3600
-+delete-add.test.dyndns:127.0.0.108:3600
-:delete-add.test.dyndns:16:\034Should\040be\040gone\040after\040a\040while:3600
++\052.wild.test.dyndns:127.0.1.255:3600
+a.host.test.dyndns:1.1.1.1:3600
++delete-add.test.dyndns:127.0.0.108:3600
+e.host.test.dyndns:1.1.1.1:3600
+host-1.test.dyndns:127.0.0.101:3600
+host-2.test.dyndns:127.0.0.102:3600
+ns2.test.dyndns:127.0.0.2:3600
+replace.test.dyndns:127.0.0.1:3600
+ttl.test.dyndns:127.0.0.1:3600
+:delete-add.test.dyndns:16:\034Should\040be\040gone\040after\040a\040while:3600
:txt.test.dyndns:16:\021This\040is\040some\040text:3600
-+\052.wild.test.dyndns:127.0.1.255:3600
+@test.dyndns::host-1.test.dyndns.:10:3600
+@test.dyndns::host-2.test.dyndns.:20:3600
+Ccname1.test.dyndns:host-1.test.dyndns.:3600
+Ccname2.test.dyndns:host-2.test.dyndns.:3600
+Ztest.dyndns:ns1.test.dyndns.:ahu.example.dyndns.:2012060701:28800:7200:604800:86400:3600
#2005092501 auto axfr-get
-Zwtest.com:ns1.wtest.com.:ahu.example.com.:2005092501:28800:7200:604800:86400:3600
&wtest.com::..:3600
&wtest.com::ns1.wtest.com.:3600
-@wtest.com::smtp-servers.example.com.:10:3600
-@wtest.com::smtp-servers.wtest.com.:15:3600
-+wtest.com:9.9.9.9:3600
-C\052.wtest.com:server1.wtest.com.:3600
-:e.wtest.com:16:\011non-empty:3600
-:d.e.wtest.com:16:\011non-empty:3600
-:c.d.e.wtest.com:16:\011non-empty:3600
-:b.c.d.e.wtest.com:16:\011non-empty:3600
-:a.b.c.d.e.wtest.com:16:\011non-empty:3600
+\052.a.b.c.d.e.wtest.com:6.7.8.9:3600
++\052.something.wtest.com:4.3.2.1:3600
++a.something.wtest.com:10.11.12.13:3600
+ns1.wtest.com:2.3.4.5:3600
-@secure.wtest.com::server1.wtest.com.:10:3600
+server1.wtest.com:1.2.3.4:3600
++wtest.com:9.9.9.9:3600
+:a.b.c.d.e.wtest.com:16:\011non-empty:3600
+:b.c.d.e.wtest.com:16:\011non-empty:3600
+:c.d.e.wtest.com:16:\011non-empty:3600
+:d.e.wtest.com:16:\011non-empty:3600
+:e.wtest.com:16:\011non-empty:3600
:something.wtest.com:16:\045make\040the\040empty\040non-terminal\040non-empty:3600
-+\052.something.wtest.com:4.3.2.1:3600
-+a.something.wtest.com:10.11.12.13:3600
+@secure.wtest.com::server1.wtest.com.:10:3600
+@wtest.com::smtp-servers.example.com.:10:3600
+@wtest.com::smtp-servers.wtest.com.:15:3600
+C\052.wtest.com:server1.wtest.com.:3600
+Zwtest.com:ns1.wtest.com.:ahu.example.com.:2005092501:28800:7200:604800:86400:3600
#2005092501 auto axfr-get
-Znztest.com:ns1.nztest.com.:ahu.example.com.:2005092501:28800:7200:604800:86400:3600
&nztest.com::ns1.nztest.com.:3600
&nztest.com::ns2.nztest.com.:3600
-+nztest.com:127.0.0.1:3600
+ns1.nztest.com:1.1.1.1:3600
+ns2.nztest.com:2.2.2.2:3600
++nztest.com:127.0.0.1:3600
+Znztest.com:ns1.nztest.com.:ahu.example.com.:2005092501:28800:7200:604800:86400:3600
#2005092501 auto axfr-get
-Zdnssec-parent.com:ns1.dnssec-parent.com.:ahu.example.com.:2005092501:28800:7200:604800:86400:3600
+&delegated.dnssec-parent.com::ns1.delegated.dnssec-parent.com.:3600
+&delegated.dnssec-parent.com::ns2.delegated.dnssec-parent.com.:3600
&dnssec-parent.com::ns1.dnssec-parent.com.:3600
&dnssec-parent.com::ns2.dnssec-parent.com.:3600
-+dnssec-parent.com:9.9.9.9:3600
&insecure-delegated.ent.ent.auth-ent.dnssec-parent.com::ns.example.com.:3600
-+something1.auth-ent.dnssec-parent.com:1.1.2.3:3600
-&delegated.dnssec-parent.com::ns1.delegated.dnssec-parent.com.:3600
-&delegated.dnssec-parent.com::ns2.delegated.dnssec-parent.com.:3600
-+ns1.delegated.dnssec-parent.com:4.5.6.7:3600
-+ns2.delegated.dnssec-parent.com:5.6.7.8:3600
-+ns1.dnssec-parent.com:1.2.3.4:3600
-+ns2.dnssec-parent.com:4.3.2.1:3600
-:secure-delegated.dnssec-parent.com:43:\324\057\010\002\240\271\303\214\323\044\030\052\360\357f\203\015\012\016\205\241\325\211y\311\203N\030\310qw\236\004\010W\267:3600
&secure-delegated.dnssec-parent.com::ns1.secure-delegated.dnssec-parent.com.:3600
&secure-delegated.dnssec-parent.com::ns2.secure-delegated.dnssec-parent.com.:3600
++dnssec-parent.com:9.9.9.9:3600
++ns1.delegated.dnssec-parent.com:4.5.6.7:3600
++ns1.dnssec-parent.com:1.2.3.4:3600
+ns1.secure-delegated.dnssec-parent.com:1.2.3.4:3600
++ns2.delegated.dnssec-parent.com:5.6.7.8:3600
++ns2.dnssec-parent.com:4.3.2.1:3600
+ns2.secure-delegated.dnssec-parent.com:5.6.7.8:3600
++something1.auth-ent.dnssec-parent.com:1.1.2.3:3600
+:secure-delegated.dnssec-parent.com:43:\324\057\010\002\240\271\303\214\323\044\030\052\360\357f\203\015\012\016\205\241\325\211y\311\203N\030\310qw\236\004\010W\267:3600
+Zdnssec-parent.com:ns1.dnssec-parent.com.:ahu.example.com.:2005092501:28800:7200:604800:86400:3600
#2005092501 auto axfr-get
-Zdelegated.dnssec-parent.com:ns1.delegated.dnssec-parent.com.:ahu.example.com.:2005092501:28800:7200:604800:86400:3600
&delegated.dnssec-parent.com::ns1.delegated.dnssec-parent.com.:3600
&delegated.dnssec-parent.com::ns2.delegated.dnssec-parent.com.:3600
+delegated.dnssec-parent.com:9.9.9.9:3600
-:delegated.dnssec-parent.com:43:\253\376\010\002\324\303\325U\053\206y\372\356\2741\176\137\004\213aK.\137\140\175\305\177\025S\030-I\253\041y\367:3600
+ns1.delegated.dnssec-parent.com:4.5.6.7:3600
+ns2.delegated.dnssec-parent.com:5.6.7.8:3600
+:delegated.dnssec-parent.com:43:\253\376\010\002\324\303\325U\053\206y\372\356\2741\176\137\004\213aK.\137\140\175\305\177\025S\030-I\253\041y\367:3600
Cwww.delegated.dnssec-parent.com:delegated.dnssec-parent.com.:3600
+Zdelegated.dnssec-parent.com:ns1.delegated.dnssec-parent.com.:ahu.example.com.:2005092501:28800:7200:604800:86400:3600
#2005092501 auto axfr-get
-Zsecure-delegated.dnssec-parent.com:ns1.secure-delegated.dnssec-parent.com.:ahu.example.com.:2005092501:28800:7200:604800:86400:3600
&secure-delegated.dnssec-parent.com::ns1.secure-delegated.dnssec-parent.com.:3600
&secure-delegated.dnssec-parent.com::ns2.secure-delegated.dnssec-parent.com.:3600
-+secure-delegated.dnssec-parent.com:9.9.9.9:3600
+ns1.secure-delegated.dnssec-parent.com:1.2.3.4:3600
+ns2.secure-delegated.dnssec-parent.com:5.6.7.8:3600
++secure-delegated.dnssec-parent.com:9.9.9.9:3600
Cwww.secure-delegated.dnssec-parent.com:secure-delegated.dnssec-parent.com.:3600
+Zsecure-delegated.dnssec-parent.com:ns1.secure-delegated.dnssec-parent.com.:ahu.example.com.:2005092501:28800:7200:604800:86400:3600
#2000081501 auto axfr-get
-Zminimal.com:ns1.example.com.:ahu.example.com.:2000081501:28800:7200:604800:86400:120
&minimal.com::ns1.example.com.:120
&minimal.com::ns2.example.com.:120
+Zminimal.com:ns1.example.com.:ahu.example.com.:2000081501:28800:7200:604800:86400:120
#2000081501 auto axfr-get
-Ztsig.com:ns1.example.com.:ahu.example.com.:2000081501:28800:7200:604800:86400:120
&tsig.com::ns1.example.com.:120
&tsig.com::ns2.example.com.:120
+Ztsig.com:ns1.example.com.:ahu.example.com.:2000081501:28800:7200:604800:86400:120
#2000081501 auto axfr-get
-Zstest.com:ns1.example.com.:ahu.example.com.:2000081501:28800:7200:604800:86400:120
&stest.com::ns1.example.com.:120
&stest.com::ns2.example.com.:120
+Zstest.com:ns1.example.com.:ahu.example.com.:2000081501:28800:7200:604800:86400:120
#2005092501 auto axfr-get
-Zcdnskey-cds-test.com:ns1.cdnskey-cds-test.:ahu.example.com.:2005092501:28800:7200:604800:86400:3600
&cdnskey-cds-test.com::ns1.cdnskey-cds-test.com.:3600
&cdnskey-cds-test.com::ns2.cdnskey-cds-test.com.:3600
+cdnskey-cds-test.com:127.0.0.1:3600
+ns1.cdnskey-cds-test.com:1.1.1.1:3600
+ns2.cdnskey-cds-test.com:2.2.2.2:3600
+Zcdnskey-cds-test.com:ns1.cdnskey-cds-test.:ahu.example.com.:2005092501:28800:7200:604800:86400:3600
#2000081501 auto axfr-get
-Z2.0.192.in-addr.arpa:ns1.example.com.:ahu.example.com.:2000081501:28800:7200:604800:86400:120
&2.0.192.in-addr.arpa::ns1.example.com.:120
&2.0.192.in-addr.arpa::ns2.example.com.:120
+Z2.0.192.in-addr.arpa:ns1.example.com.:ahu.example.com.:2000081501:28800:7200:604800:86400:120
for zone in $(grep 'zone ' ../../regression-tests/named.conf | cut -f2 -d\")
do
$TCPCLIENT 127.0.0.1 5300 $AXFRGET $zone $zone.out $zone.out.tmp
- cat $zone.out >> data
+ LC_ALL=C sort $zone.out >> data
rm $zone.out
done
pdns_server_SOURCES = \
arguments.cc arguments.hh \
+ ascii.hh \
auth-carbon.cc \
+ auth-caches.cc auth-caches.hh \
+ auth-packetcache.cc auth-packetcache.hh \
+ auth-querycache.cc auth-querycache.hh \
backends/gsql/gsqlbackend.cc backends/gsql/gsqlbackend.hh \
backends/gsql/ssql.hh \
base32.cc base32.hh \
dynhandler.cc dynhandler.hh \
dynlistener.cc dynlistener.hh \
dynmessenger.hh \
+ ednsoptions.cc ednsoptions.hh \
ednssubnet.cc ednssubnet.hh \
gss_context.cc gss_context.hh \
iputils.cc iputils.hh \
namespaces.hh \
nsecrecords.cc \
opensslsigners.cc opensslsigners.hh \
- packetcache.cc packetcache.hh \
+ packetcache.hh \
packethandler.cc packethandler.hh \
pdnsexception.hh \
qtype.cc qtype.hh \
pdnsutil_SOURCES = \
arguments.cc \
+ auth-caches.cc auth-caches.hh \
+ auth-packetcache.cc auth-packetcache.hh \
+ auth-querycache.cc auth-querycache.hh \
backends/gsql/gsqlbackend.cc backends/gsql/gsqlbackend.hh \
backends/gsql/ssql.hh \
base32.cc \
dnssecsigner.cc \
dnswriter.cc dnswriter.hh \
dynlistener.cc \
+ ednsoptions.cc ednsoptions.hh \
ednssubnet.cc \
gss_context.cc gss_context.hh \
iputils.cc iputils.hh \
misc.cc misc.hh \
nsecrecords.cc \
opensslsigners.cc opensslsigners.hh \
- packetcache.cc \
pdnsutil.cc \
qtype.cc \
randomhelper.cc \
dnsrecords.cc \
dnsreplay.cc \
dnswriter.cc dnswriter.hh \
- ednssubnet.cc ednssubnet.hh \
ednsoptions.cc ednsoptions.hh \
+ ednssubnet.cc ednssubnet.hh \
iputils.cc \
logger.cc \
misc.cc \
testrunner_SOURCES = \
arguments.cc \
+ auth-caches.cc auth-caches.hh \
+ auth-packetcache.cc auth-packetcache.hh \
+ auth-querycache.cc auth-querycache.hh \
base32.cc \
base64.cc \
bindlexer.l \
misc.cc \
nameserver.cc \
nsecrecords.cc \
- packetcache.cc \
qtype.cc \
rcpgenerator.cc \
responsestats.cc \
test-dnsparser_hh.cc \
test-dnsrecords_cc.cc \
test-iputils_hh.cc \
+ test-lock_hh.cc \
test-md5_hh.cc \
test-misc_hh.cc \
test-nameserver_cc.cc \
the default one being represented by the empty string:
```
-pc = newPacketCache(10000, 86400, 0, 60, 60)
+pc = newPacketCache(10000, 86400, 0, 60, 60, false)
getPool(""):setCache(pc)
```
The first parameter (10000) is the maximum number of entries stored in the cache, and is the
-only one required. All the other parameter are optional and in seconds.
+only one required. The second, third, fourth and fifth parameters are optional and in seconds.
The second one (86400) is the maximum lifetime of an entry in the cache, the third one (0) is
the minimum TTL an entry should have to be considered for insertion in the cache,
-the fourth one (60) is the TTL used for a Server Failure or a Refused response. The last
-one (60) is the TTL that will be used when a stale cache entry is returned.
+the fourth one (60) is the TTL used for a Server Failure or a Refused response. The fifth
+one (60) is the TTL that will be used when a stale cache entry is returned. The last one
+is a boolean that indicates whether the TTL of reponses should be reduced by the number of
+seconds the response has been in the cache.
For performance reasons the cache will pre-allocate buckets based on the maximum number
of entries, so be careful to set the first parameter to a reasonable value. Something
along the lines of a dozen bytes per pre-allocated entry can be expected on 64-bit.
> generateDNSCryptCertificate("/path/to/providerPrivate.key", "/path/to/resolver.cert", "/path/to/resolver.key", serial, validFrom, validUntil)
```
+Note that 'validFrom' and 'validUntil' are UNIX epoch timestamps. These can
+easily be calculated as 'os.time(), os.time()+2*365*86400' for example to
+get a certificate that is valid for two years from now.
+
Ideally, the certificates and keys should be generated on an offline dedicated hardware and not on the resolver.
The resolver key should be regularly rotated and should never touch persistent storage, being stored in a tmpfs
with no swap configured.
* `expunge(n)`: remove entries from the cache, leaving at most `n` entries
* `expungeByName(DNSName [, qtype=ANY, suffixMatch=false])`: remove entries matching the supplied DNSName and type from the cache. If suffixMatch is specified also removes names below DNSName
* `isFull()`: return true if the cache has reached the maximum number of entries
- * `newPacketCache(maxEntries[, maxTTL=86400, minTTL=0, temporaryFailureTTL=60, staleTTL=60])`: return a new PacketCache
+ * `newPacketCache(maxEntries[, maxTTL=86400, minTTL=0, temporaryFailureTTL=60, staleTTL=60, dontAge=false])`: return a new PacketCache
* `printStats()`: print the cache stats (hits, misses, deferred lookups and deferred inserts)
* `purgeExpired(n)`: remove expired entries from the cache until there is at most `n` entries remaining in the cache
* `toString()`: return the number of entries in the Packet Cache, and the maximum number of entries
* `setCacheCleaningDelay(n)`: set the interval in seconds between two runs of the cache cleaning algorithm, removing expired entries
* `setCacheCleaningPercentage(n)`: set the percentage of the cache that the cache cleaning algorithm will try to free by removing expired entries. By default (100), all expired entries are removed
* `setStaleCacheEntriesTTL(n)`: allows using cache entries expired for at most `n` seconds when no backend available to answer for a query
+ * `setTCPDownstreamCleanupInterval(interval)`: minimum interval in seconds between two cleanups of the idle TCP downstream connections. Defaults to 60s
* `setTCPUseSinglePipe(bool)`: whether the incoming TCP connections should be put into a single queue instead of using per-thread queues. Defaults to false
* `setTCPRecvTimeout(n)`: set the read timeout on TCP connections from the client, in seconds
* `setTCPSendTimeout(n)`: set the write timeout on TCP connections from the client, in seconds
* DNSCrypt related:
* `addDNSCryptBind("127.0.0.1:8443", "provider name", "/path/to/resolver.cert", "/path/to/resolver.key", [false], [TCP Fast Open queue size]):` 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 sets SO_REUSEPORT when available. The last parameter sets the TCP Fast Open queue size, enabling TCP Fast Open when available and the value is larger than 0.
* `generateDNSCryptProviderKeys("/path/to/providerPublic.key", "/path/to/providerPrivate.key"):` generate a new provider keypair
- * `generateDNSCryptCertificate("/path/to/providerPrivate.key", "/path/to/resolver.cert", "/path/to/resolver.key", serial, validFrom, validUntil):` generate a new resolver private key and related certificate, valid from the `validFrom` timestamp until the `validUntil` one, signed with the provider private key
+ * `generateDNSCryptCertificate("/path/to/providerPrivate.key", "/path/to/resolver.cert", "/path/to/resolver.key", serial, validFrom, validUntil):` generate a new resolver private key and related certificate, valid from the `validFrom` UNIX timestamp until the `validUntil` one, signed with the provider private key
* `printDNSCryptProviderFingerprint("/path/to/providerPublic.key")`: display the fingerprint of the provided resolver public key
* `showDNSCryptBinds():`: display the currently configured DNSCrypt binds
* BPFFilter related:
--- /dev/null
+/*
+ * 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
+
+inline unsigned char dns_tolower(unsigned char c)
+{
+ if(c>='A' && c<='Z')
+ c+='a'-'A';
+ return c;
+}
--- /dev/null
+/*
+ * 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 "auth-querycache.hh"
+#include "auth-packetcache.hh"
+
+extern AuthPacketCache PC;
+extern AuthQueryCache QC;
+
+/* empty all caches */
+uint64_t purgeAuthCaches()
+{
+ uint64_t ret = 0;
+ ret += PC.purge();
+ ret += QC.purge();
+ return ret;
+}
+
+ /* remove specific entries from all caches, can be $ terminated */
+uint64_t purgeAuthCaches(const std::string& match)
+{
+ uint64_t ret = 0;
+ ret += PC.purge(match);
+ ret += QC.purge(match);
+ return ret;
+}
+
+/* remove specific entries from all caches, no wildcard matching */
+uint64_t purgeAuthCachesExact(const DNSName& qname)
+{
+ uint64_t ret = 0;
+ ret += PC.purgeExact(qname);
+ ret += QC.purgeExact(qname);
+ return ret;
+}
+
+
+
--- /dev/null
+/*
+ * 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.
+ */
+#ifndef AUTH_CACHES_HH
+#define AUTH_CACHES_HH
+
+#include <string>
+#include <cstdint>
+
+#include "dnsname.hh"
+
+uint64_t purgeAuthCaches(); /* empty all caches */
+uint64_t purgeAuthCaches(const std::string& match); /* remove specific entries from all caches, can be $ terminated */
+uint64_t purgeAuthCachesExact(const DNSName& qname); /* remove specific entries from all caches, no wildcard matching */
+
+#endif /* AUTH_CACHES_HH */
--- /dev/null
+/*
+ * 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.
+ */
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+#include "auth-packetcache.hh"
+#include "logger.hh"
+#include "statbag.hh"
+#include "cachecleaner.hh"
+extern StatBag S;
+
+const unsigned int AuthPacketCache::s_mincleaninterval, AuthPacketCache::s_maxcleaninterval;
+
+AuthPacketCache::AuthPacketCache(size_t mapsCount): d_lastclean(time(nullptr))
+{
+ d_maps.resize(mapsCount);
+ for(auto& mc : d_maps) {
+ pthread_rwlock_init(&mc.d_mut, 0);
+ }
+
+ S.declare("packetcache-hit", "Number of hits on the packet cache");
+ S.declare("packetcache-miss", "Number of misses on the packet cache");
+ S.declare("packetcache-size", "Number of entries in the packet cache");
+ S.declare("deferred-packetcache-inserts","Amount of packet cache inserts that were deferred because of maintenance");
+ S.declare("deferred-packetcache-lookup","Amount of packet cache lookups that were deferred because of maintenance");
+
+ d_statnumhit=S.getPointer("packetcache-hit");
+ d_statnummiss=S.getPointer("packetcache-miss");
+ d_statnumentries=S.getPointer("packetcache-size");
+}
+
+AuthPacketCache::~AuthPacketCache()
+{
+ try {
+ vector<WriteLock*> locks;
+ for(auto& mc : d_maps) {
+ locks.push_back(new WriteLock(&mc.d_mut));
+ }
+ for(auto wl : locks) {
+ delete wl;
+ }
+ }
+ catch(...) {
+ }
+}
+
+bool AuthPacketCache::get(DNSPacket *p, DNSPacket *cached)
+{
+ cleanupIfNeeded();
+
+ if(!d_ttl) {
+ (*d_statnummiss)++;
+ return false;
+ }
+
+ uint32_t hash = canHashPacket(p->getString(), false);
+ p->setHash(hash);
+
+ string value;
+ bool haveSomething;
+ time_t now = time(nullptr);
+ auto& mc = getMap(p->qdomain);
+ {
+ TryReadLock rl(&mc.d_mut);
+ if(!rl.gotIt()) {
+ S.inc("deferred-packetcache-lookup");
+ return false;
+ }
+
+ haveSomething = getEntryLocked(mc.d_map, hash, p->qdomain, p->qtype.getCode(), p->d_tcp, now, value);
+ }
+
+ if (!haveSomething) {
+ (*d_statnummiss)++;
+ return false;
+ }
+
+ if(cached->noparse(value.c_str(), value.size()) < 0) {
+ return false;
+ }
+
+ (*d_statnumhit)++;
+ cached->spoofQuestion(p); // for correct case
+ cached->qdomain = p->qdomain;
+ cached->qtype = p->qtype;
+
+ return true;
+}
+
+void AuthPacketCache::insert(DNSPacket *q, DNSPacket *r, unsigned int maxTTL)
+{
+ cleanupIfNeeded();
+
+ if (ntohs(q->d.qdcount) != 1) {
+ return; // do not try to cache packets with multiple questions
+ }
+
+ if (q->qclass != QClass::IN) // we only cache the INternet
+ return;
+
+ uint32_t ourttl = std::min(d_ttl, maxTTL);
+ if (ourttl == 0) {
+ return;
+ }
+
+ uint32_t hash = q->getHash();
+ time_t now = time(nullptr);
+ CacheEntry entry;
+ entry.hash = hash;
+ entry.created = now;
+ entry.ttd = now + ourttl;
+ entry.qname = q->qdomain;
+ entry.qtype = q->qtype.getCode();
+ entry.value = r->getString();
+ entry.tcp = r->d_tcp;
+
+ auto& mc = getMap(entry.qname);
+ {
+ TryWriteLock l(&mc.d_mut);
+ if (!l.gotIt()) {
+ S.inc("deferred-packetcache-inserts");
+ return;
+ }
+
+ auto& idx = mc.d_map.get<HashTag>();
+ auto range = idx.equal_range(hash);
+ auto iter = range.first;
+
+ for( ; iter != range.second ; ++iter) {
+ if (iter->tcp != entry.tcp || iter->qtype != entry.qtype || iter->qname != entry.qname)
+ continue;
+
+ iter->value = entry.value;
+ iter->ttd = now + ourttl;
+ iter->created = now;
+ return;
+ }
+
+ /* no existing entry found to refresh */
+ mc.d_map.insert(entry);
+ (*d_statnumentries)++;
+ }
+}
+
+bool AuthPacketCache::getEntryLocked(cmap_t& map, uint32_t hash, const DNSName &qname, uint16_t qtype, bool tcp, time_t now, string& value)
+{
+ auto& idx = map.get<HashTag>();
+ auto range = idx.equal_range(hash);
+
+ for(auto iter = range.first; iter != range.second ; ++iter) {
+ if (iter->ttd < now)
+ continue;
+
+ if (iter->tcp != tcp || iter->qtype != qtype || iter->qname != qname)
+ continue;
+
+ value = iter->value;
+ return true;
+ }
+
+ return false;
+}
+
+/* clears the entire cache. */
+uint64_t AuthPacketCache::purge()
+{
+ d_statnumentries->store(0);
+
+ return purgeLockedCollectionsVector(d_maps);
+}
+
+uint64_t AuthPacketCache::purgeExact(const DNSName& qname)
+{
+ auto& mc = getMap(qname);
+ uint64_t delcount = purgeExactLockedCollection(mc, qname);
+
+ *d_statnumentries -= delcount;
+
+ return delcount;
+}
+
+/* purges entries from the packetcache. If match ends on a $, it is treated as a suffix */
+uint64_t AuthPacketCache::purge(const string &match)
+{
+ uint64_t delcount = 0;
+
+ if(ends_with(match, "$")) {
+ delcount = purgeLockedCollectionsVector(d_maps, match);
+ *d_statnumentries -= delcount;
+ }
+ else {
+ delcount = purgeExact(DNSName(match));
+ }
+
+ return delcount;
+}
+
+void AuthPacketCache::cleanup()
+{
+ uint64_t maxCached = d_maxEntries;
+ uint64_t cacheSize = *d_statnumentries;
+ uint64_t totErased = 0;
+
+ totErased = pruneLockedCollectionsVector(d_maps, maxCached, cacheSize);
+ *d_statnumentries -= totErased;
+
+ DLOG(L<<"Done with cache clean, cacheSize: "<<(*d_statnumentries)<<", totErased"<<totErased<<endl);
+}
+
+/* the logic:
+ after d_nextclean operations, we clean. We also adjust the cleaninterval
+ a bit so we slowly move it to a value where we clean roughly every 30 seconds.
+
+ If d_nextclean has reached its maximum value, we also test if we were called
+ within 30 seconds, and if so, we skip cleaning. This means that under high load,
+ we will not clean more often than every 30 seconds anyhow.
+*/
+
+void AuthPacketCache::cleanupIfNeeded()
+{
+ if (d_ops++ == d_nextclean) {
+ time_t now = time(nullptr);
+ int timediff = max((int)(now - d_lastclean), 1);
+
+ DLOG(L<<"cleaninterval: "<<d_cleaninterval<<", timediff: "<<timediff<<endl);
+
+ if (d_cleaninterval == s_maxcleaninterval && timediff < 30) {
+ d_cleanskipped = true;
+ d_nextclean += d_cleaninterval;
+
+ DLOG(L<<"cleaning skipped, timediff: "<<timediff<<endl);
+
+ return;
+ }
+
+ if(!d_cleanskipped) {
+ d_cleaninterval=(int)(0.6*d_cleaninterval)+(0.4*d_cleaninterval*(30.0/timediff));
+ d_cleaninterval=std::max(d_cleaninterval, s_mincleaninterval);
+ d_cleaninterval=std::min(d_cleaninterval, s_maxcleaninterval);
+
+ DLOG(L<<"new cleaninterval: "<<d_cleaninterval<<endl);
+ } else {
+ d_cleanskipped = false;
+ }
+
+ d_nextclean += d_cleaninterval;
+ d_lastclean=now;
+ cleanup();
+ }
+}
--- /dev/null
+/*
+ * 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.
+ */
+#ifndef AUTH_PACKETCACHE_HH
+#define AUTH_PACKETCACHE_HH
+
+#include <string>
+#include <map>
+#include "dns.hh"
+#include <boost/version.hpp>
+#include "namespaces.hh"
+using namespace ::boost::multi_index;
+
+#include <boost/multi_index/hashed_index.hpp>
+
+#include "dnspacket.hh"
+#include "lock.hh"
+#include "packetcache.hh"
+
+/** This class performs 'whole packet caching'. Feed it a question packet and it will
+ try to find an answer. If you have an answer, insert it to have it cached for later use.
+ Take care not to replace existing cache entries. While this works, it is wasteful. Only
+ insert packets that where not found by get()
+
+ Locking!
+
+ The cache itself is protected by a read/write lock. Because deleting is a two step process, which
+ first marks and then sweeps, a second lock is present to prevent simultaneous inserts and deletes.
+*/
+
+class AuthPacketCache : public PacketCache
+{
+public:
+ AuthPacketCache(size_t mapsCount=1024);
+ ~AuthPacketCache();
+
+ void insert(DNSPacket *q, DNSPacket *r, uint32_t maxTTL); //!< We copy the contents of *p into our cache. Do not needlessly call this to insert questions already in the cache as it wastes resources
+
+ bool get(DNSPacket *p, DNSPacket *q); //!< We return a dynamically allocated copy out of our cache. You need to delete it. You also need to spoof in the right ID with the DNSPacket.spoofID() method.
+
+ void cleanup(); //!< force the cache to preen itself from expired packets
+ uint64_t purge();
+ uint64_t purge(const std::string& match); // could be $ terminated. Is not a dnsname!
+ uint64_t purgeExact(const DNSName& qname); // no wildcard matching here
+
+ uint64_t size() const { return *d_statnumentries; };
+
+ void setMaxEntries(uint64_t maxEntries)
+ {
+ d_maxEntries = maxEntries;
+ }
+ void setTTL(uint32_t ttl)
+ {
+ d_ttl = ttl;
+ }
+private:
+
+ struct CacheEntry
+ {
+ mutable string value;
+ DNSName qname;
+
+ mutable time_t created{0};
+ mutable time_t ttd{0};
+ uint32_t hash{0};
+ uint16_t qtype{0};
+ bool tcp{false};
+ };
+
+ struct HashTag{};
+ struct NameTag{};
+ struct SequenceTag{};
+ typedef multi_index_container<
+ CacheEntry,
+ indexed_by <
+ hashed_non_unique<tag<HashTag>, member<CacheEntry,uint32_t,&CacheEntry::hash> >,
+ ordered_non_unique<tag<NameTag>, member<CacheEntry,DNSName,&CacheEntry::qname>, CanonDNSNameCompare >,
+ sequenced<tag<SequenceTag>>
+ >
+ > cmap_t;
+
+ struct MapCombo
+ {
+ pthread_rwlock_t d_mut;
+ cmap_t d_map;
+ };
+
+ vector<MapCombo> d_maps;
+ MapCombo& getMap(const DNSName& name)
+ {
+ return d_maps[name.hash() % d_maps.size()];
+ }
+
+ bool getEntryLocked(cmap_t& map, uint32_t hash, const DNSName &qname, uint16_t qtype, bool tcp, time_t now, string& entry);
+ void cleanupIfNeeded();
+
+ AtomicCounter d_ops{0};
+ AtomicCounter *d_statnumhit;
+ AtomicCounter *d_statnummiss;
+ AtomicCounter *d_statnumentries;
+
+ uint64_t d_maxEntries{0};
+ time_t d_lastclean; // doesn't need to be atomic
+ unsigned long d_nextclean{4096};
+ unsigned int d_cleaninterval{4096};
+ uint32_t d_ttl{0};
+ bool d_cleanskipped{false};
+
+ static const unsigned int s_mincleaninterval=1000, s_maxcleaninterval=300000;
+};
+
+#endif /* AUTH_PACKETCACHE_HH */
--- /dev/null
+/*
+ * 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.
+ */
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "auth-querycache.hh"
+#include "logger.hh"
+#include "statbag.hh"
+#include "cachecleaner.hh"
+extern StatBag S;
+
+const unsigned int AuthQueryCache::s_mincleaninterval, AuthQueryCache::s_maxcleaninterval;
+
+extern StatBag S;
+
+AuthQueryCache::AuthQueryCache(size_t mapsCount): d_lastclean(time(nullptr))
+{
+ d_maps.resize(mapsCount);
+ for(auto& mc : d_maps) {
+ pthread_rwlock_init(&mc.d_mut, 0);
+ }
+
+ S.declare("query-cache-hit","Number of hits on the query cache");
+ S.declare("query-cache-miss","Number of misses on the query cache");
+ S.declare("query-cache-size", "Number of entries in the query cache");
+ S.declare("deferred-cache-inserts","Amount of cache inserts that were deferred because of maintenance");
+ S.declare("deferred-cache-lookup","Amount of cache lookups that were deferred because of maintenance");
+
+ d_statnumhit=S.getPointer("query-cache-hit");
+ d_statnummiss=S.getPointer("query-cache-miss");
+ d_statnumentries=S.getPointer("query-cache-size");
+}
+
+AuthQueryCache::~AuthQueryCache()
+{
+ try {
+ vector<WriteLock*> locks;
+ for(auto& mc : d_maps) {
+ locks.push_back(new WriteLock(&mc.d_mut));
+ }
+ for(auto wl : locks) {
+ delete wl;
+ }
+ }
+ catch(...) {
+ }
+}
+
+// called from ueberbackend
+bool AuthQueryCache::getEntry(const DNSName &qname, const QType& qtype, vector<DNSZoneRecord>& value, int zoneID)
+{
+ cleanupIfNeeded();
+
+ time_t now = time(nullptr);
+ uint16_t qt = qtype.getCode();
+ auto& mc = getMap(qname);
+ {
+ TryReadLock rl(&mc.d_mut);
+ if(!rl.gotIt()) {
+ S.inc("deferred-cache-lookup");
+ return false;
+ }
+
+ return getEntryLocked(mc.d_map, qname, qt, value, zoneID, now);
+ }
+}
+
+void AuthQueryCache::insert(const DNSName &qname, const QType& qtype, const vector<DNSZoneRecord>& value, uint32_t ttl, int zoneID)
+{
+ cleanupIfNeeded();
+
+ if(!ttl)
+ return;
+
+ time_t now = time(nullptr);
+ CacheEntry val;
+ val.created = now;
+ val.ttd = now + ttl;
+ val.qname = qname;
+ val.qtype = qtype.getCode();
+ val.drs = value;
+ val.zoneID = zoneID;
+
+ auto& mc = getMap(val.qname);
+
+ {
+ TryWriteLock l(&mc.d_mut);
+ if(!l.gotIt()) {
+ S.inc("deferred-cache-inserts");
+ return;
+ }
+
+ bool inserted;
+ cmap_t::iterator place;
+ tie(place, inserted) = mc.d_map.insert(val);
+
+ if (!inserted) {
+ mc.d_map.replace(place, val);
+ }
+ else {
+ (*d_statnumentries)++;
+ }
+ }
+}
+
+bool AuthQueryCache::getEntryLocked(cmap_t& map, const DNSName &qname, uint16_t qtype, vector<DNSZoneRecord>& value, int zoneID, time_t now)
+{
+ auto& idx = boost::multi_index::get<HashTag>(map);
+ auto iter = idx.find(tie(qname, qtype, zoneID));
+
+ if (iter == idx.end())
+ return false;
+
+ if (iter->ttd < now)
+ return false;
+
+ value = iter->drs;
+ return true;
+}
+
+map<char,uint64_t> AuthQueryCache::getCounts()
+{
+ uint64_t queryCacheEntries=0, negQueryCacheEntries=0;
+
+ for(auto& mc : d_maps) {
+ ReadLock l(&mc.d_mut);
+
+ for(cmap_t::const_iterator iter = mc.d_map.begin() ; iter != mc.d_map.end(); ++iter) {
+ if(iter->drs.empty())
+ negQueryCacheEntries++;
+ else
+ queryCacheEntries++;
+ }
+ }
+ map<char,uint64_t> ret;
+
+ ret['!']=negQueryCacheEntries;
+ ret['Q']=queryCacheEntries;
+ return ret;
+}
+
+/* clears the entire cache. */
+uint64_t AuthQueryCache::purge()
+{
+ d_statnumentries->store(0);
+
+ return purgeLockedCollectionsVector(d_maps);
+}
+
+uint64_t AuthQueryCache::purgeExact(const DNSName& qname)
+{
+ auto& mc = getMap(qname);
+ uint64_t delcount = purgeExactLockedCollection(mc, qname);
+
+ *d_statnumentries -= delcount;
+
+ return delcount;
+}
+
+/* purges entries from the querycache. If match ends on a $, it is treated as a suffix */
+uint64_t AuthQueryCache::purge(const string &match)
+{
+ uint64_t delcount = 0;
+
+ if(ends_with(match, "$")) {
+ delcount = purgeLockedCollectionsVector(d_maps, match);
+ *d_statnumentries -= delcount;
+ }
+ else {
+ delcount = purgeExact(DNSName(match));
+ }
+
+ return delcount;
+}
+
+void AuthQueryCache::cleanup()
+{
+ uint64_t maxCached = d_maxEntries;
+ uint64_t cacheSize = *d_statnumentries;
+ uint64_t totErased = 0;
+
+ totErased = pruneLockedCollectionsVector(d_maps, maxCached, cacheSize);
+
+ *d_statnumentries -= totErased;
+ DLOG(L<<"Done with cache clean, cacheSize: "<<*d_statnumentries<<", totErased"<<totErased<<endl);
+}
+
+/* the logic:
+ after d_nextclean operations, we clean. We also adjust the cleaninterval
+ a bit so we slowly move it to a value where we clean roughly every 30 seconds.
+
+ If d_nextclean has reached its maximum value, we also test if we were called
+ within 30 seconds, and if so, we skip cleaning. This means that under high load,
+ we will not clean more often than every 30 seconds anyhow.
+*/
+
+void AuthQueryCache::cleanupIfNeeded()
+{
+ if (d_ops++ == d_nextclean) {
+ time_t now = time(nullptr);
+ int timediff = max((int)(now - d_lastclean), 1);
+
+ DLOG(L<<"cleaninterval: "<<d_cleaninterval<<", timediff: "<<timediff<<endl);
+
+ if (d_cleaninterval == s_maxcleaninterval && timediff < 30) {
+ d_cleanskipped = true;
+ d_nextclean += d_cleaninterval;
+
+ DLOG(L<<"cleaning skipped, timediff: "<<timediff<<endl);
+
+ return;
+ }
+
+ if(!d_cleanskipped) {
+ d_cleaninterval=(int)(0.6*d_cleaninterval)+(0.4*d_cleaninterval*(30.0/timediff));
+ d_cleaninterval=std::max(d_cleaninterval, s_mincleaninterval);
+ d_cleaninterval=std::min(d_cleaninterval, s_maxcleaninterval);
+
+ DLOG(L<<"new cleaninterval: "<<d_cleaninterval<<endl);
+ } else {
+ d_cleanskipped = false;
+ }
+
+ d_nextclean += d_cleaninterval;
+ d_lastclean=now;
+ cleanup();
+ }
+}
--- /dev/null
+/*
+ * 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.
+ */
+#ifndef AUTH_QUERYCACHE_HH
+#define AUTH_QUERYCACHE_HH
+
+#include <string>
+#include <map>
+#include "dns.hh"
+#include <boost/version.hpp>
+#include "namespaces.hh"
+using namespace ::boost::multi_index;
+
+#include <boost/multi_index/hashed_index.hpp>
+
+#include "dns.hh"
+#include "dnspacket.hh"
+#include "lock.hh"
+
+class AuthQueryCache : public boost::noncopyable
+{
+public:
+ AuthQueryCache(size_t mapsCount=1024);
+ ~AuthQueryCache();
+
+ void insert(const DNSName &qname, const QType& qtype, const vector<DNSZoneRecord>& content, uint32_t ttl, int zoneID);
+
+ bool getEntry(const DNSName &qname, const QType& qtype, vector<DNSZoneRecord>& entry, int zoneID);
+
+ size_t size() { return *d_statnumentries; } //!< number of entries in the cache
+ void cleanup(); //!< force the cache to preen itself from expired querys
+ uint64_t purge();
+ uint64_t purge(const std::string& match); // could be $ terminated. Is not a dnsname!
+ uint64_t purgeExact(const DNSName& qname); // no wildcard matching here
+
+ map<char,uint64_t> getCounts();
+
+ void setMaxEntries(uint64_t maxEntries)
+ {
+ d_maxEntries = maxEntries;
+ }
+private:
+
+ struct CacheEntry
+ {
+ DNSName qname;
+ mutable vector<DNSZoneRecord> drs;
+ mutable time_t created{0};
+ mutable time_t ttd{0};
+ uint16_t qtype{0};
+ int zoneID{-1};
+ };
+
+ struct HashTag{};
+ struct NameTag{};
+ struct SequenceTag{};
+ typedef multi_index_container<
+ CacheEntry,
+ indexed_by <
+ hashed_unique<tag<HashTag>, composite_key<CacheEntry,
+ member<CacheEntry,DNSName,&CacheEntry::qname>,
+ member<CacheEntry,uint16_t,&CacheEntry::qtype>,
+ member<CacheEntry,int, &CacheEntry::zoneID> > > ,
+ ordered_non_unique<tag<NameTag>, member<CacheEntry,DNSName,&CacheEntry::qname>, CanonDNSNameCompare >,
+ sequenced<tag<SequenceTag>>
+ >
+ > cmap_t;
+
+
+ struct MapCombo
+ {
+ pthread_rwlock_t d_mut;
+ cmap_t d_map;
+ };
+
+ vector<MapCombo> d_maps;
+ MapCombo& getMap(const DNSName& qname)
+ {
+ return d_maps[qname.hash() % d_maps.size()];
+ }
+
+ bool getEntryLocked(cmap_t& map, const DNSName &content, uint16_t qtype, vector<DNSZoneRecord>& entry, int zoneID, time_t now);
+ void cleanupIfNeeded();
+
+ AtomicCounter d_ops{0};
+ AtomicCounter *d_statnumhit;
+ AtomicCounter *d_statnummiss;
+ AtomicCounter *d_statnumentries;
+
+ uint64_t d_maxEntries{0};
+ time_t d_lastclean; // doesn't need to be atomic
+ unsigned long d_nextclean{4906};
+ unsigned int d_cleaninterval{4096};
+ bool d_cleanskipped{false};
+
+ static const unsigned int s_mincleaninterval=1000, s_maxcleaninterval=300000;
+};
+
+#endif /* AUTH_QUERYCACHE_HH */
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
-#ifndef PDNS_CACHECLEANER_HH
-#define PDNS_CACHECLEANER_HH
+#pragma once
+
+#include "lock.hh"
// this function can clean any cache that has a getTTD() method on its entries, and a 'sequence' index as its second index
// the ritual is that the oldest entries are in *front* of the sequence collection, so on a hit, move an item to the end
moveCacheItemToFrontOrBack(collection, iter, false);
}
-#endif
+template <typename T> uint64_t pruneLockedCollectionsVector(vector<T>& maps, uint64_t maxCached, uint64_t cacheSize)
+{
+ time_t now = time(nullptr);
+ uint64_t totErased = 0;
+ uint64_t toTrim = 0;
+ uint64_t lookAt = 0;
+
+ // two modes - if toTrim is 0, just look through 10% of the cache and nuke everything that is expired
+ // otherwise, scan first 5*toTrim records, and stop once we've nuked enough
+ if (maxCached && cacheSize > maxCached) {
+ toTrim = cacheSize - maxCached;
+ lookAt = 5 * toTrim;
+ } else {
+ lookAt = cacheSize / 10;
+ }
+
+ for(auto& mc : maps) {
+ WriteLock wl(&mc.d_mut);
+ auto& sidx = boost::multi_index::get<2>(mc.d_map);
+ uint64_t erased = 0, lookedAt = 0;
+ for(auto i = sidx.begin(); i != sidx.end(); lookedAt++) {
+ if(i->ttd < now) {
+ i = sidx.erase(i);
+ erased++;
+ } else {
+ ++i;
+ }
+
+ if(toTrim && erased > toTrim / maps.size())
+ break;
+
+ if(lookedAt > lookAt / maps.size())
+ break;
+ }
+ totErased += erased;
+ }
+
+ return totErased;
+}
+
+template <typename T> uint64_t purgeLockedCollectionsVector(vector<T>& maps)
+{
+ uint64_t delcount=0;
+
+ for(auto& mc : maps) {
+ WriteLock wl(&mc.d_mut);
+ delcount += mc.d_map.size();
+ mc.d_map.clear();
+ }
+
+ return delcount;
+}
+
+template <typename T> uint64_t purgeLockedCollectionsVector(vector<T>& maps, const std::string& match)
+{
+ uint64_t delcount=0;
+ string prefix(match);
+ prefix.resize(prefix.size()-1);
+ DNSName dprefix(prefix);
+ for(auto& mc : maps) {
+ WriteLock wl(&mc.d_mut);
+ auto& idx = boost::multi_index::get<1>(mc.d_map);
+ auto iter = idx.lower_bound(dprefix);
+ auto start = iter;
+
+ for(; iter != idx.end(); ++iter) {
+ if(!iter->qname.isPartOf(dprefix)) {
+ break;
+ }
+ delcount++;
+ }
+ idx.erase(start, iter);
+ }
+
+ return delcount;
+}
+
+template <typename T> uint64_t purgeExactLockedCollection(T& mc, const DNSName& qname)
+{
+ uint64_t delcount=0;
+ WriteLock wl(&mc.d_mut);
+ auto& idx = boost::multi_index::get<1>(mc.d_map);
+ auto range = idx.equal_range(qname);
+ if(range.first != range.second) {
+ delcount += distance(range.first, range.second);
+ idx.erase(range.first, range.second);
+ }
+
+ return delcount;
+}
ArgvMap theArg;
StatBag S; //!< Statistics are gathered across PDNS via the StatBag class S
-PacketCache PC; //!< This is the main PacketCache, shared across all threads
+AuthPacketCache PC; //!< This is the main PacketCache, shared across all threads
+AuthQueryCache QC;
DNSProxy *DP;
DynListener *dl;
CommunicatorClass Communicator;
::arg().set("setuid","If set, change user id to this uid for more security")="";
::arg().set("setgid","If set, change group id to this gid for more security")="";
- ::arg().set("max-cache-entries", "Maximum number of cache entries")="1000000";
+ ::arg().set("max-cache-entries", "Maximum number of entries in the query cache")="1000000";
+ ::arg().set("max-packet-cache-entries", "Maximum number of entries in the packet cache")="1000000";
::arg().set("max-signature-cache-entries", "Maximum number of signatures cache entries")="";
::arg().set("max-ent-entries", "Maximum number of empty non-terminals in a zone")="100000";
::arg().set("entropy-source", "If set, read entropy from this file")="/dev/urandom";
::arg().set("include-dir","Include *.conf files from this directory");
::arg().set("security-poll-suffix","Domain name from which to query security update notifications")="secpoll.powerdns.com.";
+ ::arg().setSwitch("expand-alias", "Expand ALIAS records")="no";
::arg().setSwitch("outgoing-axfr-expand-alias", "Expand ALIAS records during outgoing AXFR")="no";
::arg().setSwitch("8bit-dns", "Allow 8bit dns queries")="no";
::arg().setSwitch("axfr-lower-serial", "Also AXFR a zone from a master with a lower serial")="no";
::arg().set("xfr-max-received-mbytes", "Maximum number of megabytes received from an incoming XFR")="100";
+
+ ::arg().set("tcp-fast-open", "Enable TCP Fast Open support on the listening sockets, using the supplied numerical value as the queue size")="0";
}
static time_t s_start=time(0);
S.declare("qsize-q","Number of questions waiting for database attention", getQCount);
- S.declare("deferred-cache-inserts","Amount of cache inserts that were deferred because of maintenance");
- S.declare("deferred-cache-lookup","Amount of cache lookups that were deferred because of maintenance");
-
- S.declare("query-cache-hit","Number of hits on the query cache");
- S.declare("query-cache-miss","Number of misses on the query cache");
-
S.declare("dnsupdate-queries", "DNS update packets received.");
S.declare("dnsupdate-answers", "DNS update packets successfully answered.");
S.declare("dnsupdate-refused", "DNS update packets that are refused.");
continue;
}
}
-
+
if(distributor->isOverloaded()) {
if(logDNSQueries)
L<<"Dropped query, backends are overloaded"<<endl;
DNSPacket::s_udpTruncationThreshold = std::max(512, ::arg().asNum("udp-truncation-threshold"));
DNSPacket::s_doEDNSSubnetProcessing = ::arg().mustDo("edns-subnet-processing");
+ PC.setTTL(::arg().asNum("cache-ttl"));
+ PC.setMaxEntries(::arg().asNum("max-packet-cache-entries"));
+ QC.setMaxEntries(::arg().asNum("max-cache-entries"));
+
stubParseResolveConf();
if(!::arg()["chroot"].empty()) {
#ifndef COMMON_STARTUP_HH
#define COMMON_STARTUP_HH
-#include "packetcache.hh"
+#include "auth-packetcache.hh"
+#include "auth-querycache.hh"
#include "utility.hh"
#include "arguments.hh"
#include "communicator.hh"
extern ArgvMap theArg;
extern StatBag S; //!< Statistics are gathered across PDNS via the StatBag class S
-extern PacketCache PC; //!< This is the main PacketCache, shared across all threads
+extern AuthPacketCache PC; //!< This is the main PacketCache, shared across all threads
+extern AuthQueryCache QC;
extern DNSProxy *DP;
extern DynListener *dl;
extern CommunicatorClass Communicator;
#include "dnsparser.hh"
#include "dnsdist-cache.hh"
-DNSDistPacketCache::DNSDistPacketCache(size_t maxEntries, uint32_t maxTTL, uint32_t minTTL, uint32_t tempFailureTTL, uint32_t staleTTL): d_maxEntries(maxEntries), d_maxTTL(maxTTL), d_tempFailureTTL(tempFailureTTL), d_minTTL(minTTL), d_staleTTL(staleTTL)
+DNSDistPacketCache::DNSDistPacketCache(size_t maxEntries, uint32_t maxTTL, uint32_t minTTL, uint32_t tempFailureTTL, uint32_t staleTTL, bool dontAge): d_maxEntries(maxEntries), d_maxTTL(maxTTL), d_tempFailureTTL(tempFailureTTL), d_minTTL(minTTL), d_staleTTL(staleTTL), d_dontAge(dontAge)
{
pthread_rwlock_init(&d_lock, 0);
/* we reserve maxEntries + 1 to avoid rehashing from occurring
}
}
- if (!skipAging) {
+ if (!d_dontAge && !skipAging) {
ageDNSPacket(response, *responseLen, age);
}
class DNSDistPacketCache : boost::noncopyable
{
public:
- DNSDistPacketCache(size_t maxEntries, uint32_t maxTTL=86400, uint32_t minTTL=0, uint32_t tempFailureTTL=60, uint32_t staleTTL=60);
+ DNSDistPacketCache(size_t maxEntries, uint32_t maxTTL=86400, uint32_t minTTL=0, uint32_t tempFailureTTL=60, uint32_t staleTTL=60, bool dontAge=false);
~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);
uint32_t d_tempFailureTTL;
uint32_t d_minTTL;
uint32_t d_staleTTL;
+ bool d_dontAge;
};
{ "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" },
{ "newDNSName", true, "name", "make a DNSName based on this .-terminated name" },
+ { "newPacketCache", true, "maxEntries[, maxTTL=86400, minTTL=0, temporaryFailureTTL=60, staleTTL=60, dontAge=false]", "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()`" },
{ "setServerPolicy", true, "policy", "set server selection policy to that policy" },
{ "setServerPolicyLua", true, "name, function", "set server selection policy to one named 'name' and provided by 'function'" },
{ "setServFailWhenNoServer", true, "bool", "if set, return a ServFail when no servers are available, instead of the default behaviour of dropping the query" },
+ { "setTCPDownstreamCleanupInterval", true, "interval", "minimum interval in seconds between two cleanups of the idle TCP downstream connections" },
{ "setTCPUseSinglePipe", true, "bool", "whether the incoming TCP connections should be put into a single queue instead of using per-thread queues. Defaults to false" },
{ "setTCPRecvTimeout", true, "n", "set the read timeout on TCP connections from the client, in seconds" },
{ "setTCPSendTimeout", true, "n", "set the write timeout on TCP connections from the client, in seconds" },
});
g_lua.writeFunction("setKey", [](const std::string& key) {
+ if(!g_configurationDone && ! g_key.empty()) { // this makes sure the commandline -k key prevails over dnsdist.conf
+ return; // but later setKeys() trump the -k value again
+ }
+
setLuaSideEffect();
- if(B64Decode(key, g_key) < 0) {
- g_outputBuffer=string("Unable to decode ")+key+" as Base64";
- errlog("%s", g_outputBuffer);
- }
+ string newkey;
+ if(B64Decode(key, newkey) < 0) {
+ g_outputBuffer=string("Unable to decode ")+key+" as Base64";
+ errlog("%s", g_outputBuffer);
+ }
+ else
+ g_key=newkey;
});
}
});
- g_lua.writeFunction("newPacketCache", [client](size_t maxEntries, boost::optional<uint32_t> maxTTL, boost::optional<uint32_t> minTTL, boost::optional<uint32_t> tempFailTTL, boost::optional<uint32_t> staleTTL) {
- return std::make_shared<DNSDistPacketCache>(maxEntries, maxTTL ? *maxTTL : 86400, minTTL ? *minTTL : 0, tempFailTTL ? *tempFailTTL : 60, staleTTL ? *staleTTL : 60);
+ g_lua.writeFunction("newPacketCache", [client](size_t maxEntries, boost::optional<uint32_t> maxTTL, boost::optional<uint32_t> minTTL, boost::optional<uint32_t> tempFailTTL, boost::optional<uint32_t> staleTTL, boost::optional<bool> dontAge) {
+ return std::make_shared<DNSDistPacketCache>(maxEntries, maxTTL ? *maxTTL : 86400, minTTL ? *minTTL : 0, tempFailTTL ? *tempFailTTL : 60, staleTTL ? *staleTTL : 60, dontAge ? *dontAge : false);
});
g_lua.registerFunction("toString", &DNSDistPacketCache::toString);
g_lua.registerFunction("isFull", &DNSDistPacketCache::isFull);
g_outputBuffer=poolObj->policy->name+"\n";
}
});
+
+ g_lua.writeFunction("setTCPDownstreamCleanupInterval", [](uint16_t interval) {
+ setLuaSideEffect();
+ g_downstreamTCPCleanupInterval = interval;
+ });
}
static std::mutex tcpClientsCountMutex;
static std::map<ComboAddress,size_t,ComboAddress::addressOnlyLessThan> tcpClientsCount;
bool g_useTCPSinglePipe{false};
+std::atomic<uint16_t> g_downstreamTCPCleanupInterval{60};
void* tcpClientThread(int pipefd);
return false;
}
+void cleanupClosedTCPConnections(std::map<ComboAddress,int>& sockets)
+{
+ for(auto it = sockets.begin(); it != sockets.end(); ) {
+ if (isTCPSocketUsable(it->second)) {
+ ++it;
+ }
+ else {
+ close(it->second);
+ it = sockets.erase(it);
+ }
+ }
+}
+
std::shared_ptr<TCPClientCollection> g_tcpclientthreads;
void* tcpClientThread(int pipefd)
bool outstanding = false;
blockfilter_t blockFilter = 0;
+ time_t lastTCPCleanup = time(nullptr);
{
std::lock_guard<std::mutex> lock(g_luamutex);
--ds->outstanding;
}
decrementTCPClientCount(ci.remote);
+
+ if (g_downstreamTCPCleanupInterval > 0 && (connectionStartTime > (lastTCPCleanup + g_downstreamTCPCleanupInterval))) {
+ cleanupClosedTCPConnections(sockets);
+ lastTCPCleanup = time(nullptr);
+ }
}
return 0;
}
{"order", (int)a->order},
{"pools", pools},
{"latency", (int)(a->latencyUsec/1000.0)},
- {"queries", (int)a->queries}};
+ {"queries", (double)a->queries}};
/* sending a latency for a DOWN server doesn't make sense */
if (a->availability == DownstreamState::Availability::Down) {
for(const auto& a : localRules) {
Json::object rule{
{"id", num++},
- {"matches", (int)a.first->d_matches},
+ {"matches", (double)a.first->d_matches},
{"rule", a.first->toString()},
{"action", a.second->toString()},
{"action-stats", a.second->getStats()}
for(const auto& a : localResponseRules) {
Json::object rule{
{"id", num++},
- {"matches", (int)a.first->d_matches},
+ {"matches", (double)a.first->d_matches},
{"rule", a.first->toString()},
{"action", a.second->toString()},
};
uint16_t len = (uint16_t) ret;
ComboAddress dest;
- if (!HarvestDestinationAddress(&msgh, &dest)) {
+ if (HarvestDestinationAddress(&msgh, &dest)) {
+ /* we don't get the port, only the address */
+ dest.sin4.sin_port = cs->local.sin4.sin_port;
+ }
+ else {
dest.sin4.sin_family = 0;
}
string pidfile;
string command;
string config;
-#ifdef HAVE_LIBSODIUM
- string setKey;
-#endif
string uid;
string gid;
} g_cmdLine;
break;
#ifdef HAVE_LIBSODIUM
case 'k':
- if (B64Decode(string(optarg), g_cmdLine.setKey) < 0) {
+ if (B64Decode(string(optarg), g_key) < 0) {
cerr<<"Unable to decode key '"<<optarg<<"'."<<endl;
exit(EXIT_FAILURE);
}
setupLua(true, g_cmdLine.config);
if (clientAddress != ComboAddress())
g_serverControl = clientAddress;
-#ifdef HAVE_LIBSODIUM
- if (!g_cmdLine.setKey.empty())
- g_key = g_cmdLine.setKey;
-#endif
doClient(g_serverControl, g_cmdLine.command);
_exit(EXIT_SUCCESS);
}
_exit(EXIT_SUCCESS);
}
+catch(const LuaContext::ExecutionErrorException& e) {
+ try {
+ errlog("Fatal Lua error: %s", e.what());
+ std::rethrow_if_nested(e);
+ } catch(const std::exception& e) {
+ errlog("Details: %s", e.what());
+ }
+ catch(PDNSException &ae)
+ {
+ errlog("Fatal pdns error: %s", ae.reason);
+ }
+ _exit(EXIT_FAILURE);
+}
catch(std::exception &e)
{
errlog("Fatal error: %s", e.what());
extern bool g_servFailOnNoPolicy;
extern uint32_t g_hashperturb;
extern bool g_useTCPSinglePipe;
+extern std::atomic<uint16_t> g_downstreamTCPCleanupInterval;
struct ConsoleKeyword {
std::string name;
dnsdist-web.$(OBJEXT): htmlfiles.h
dnsdist_SOURCES = \
+ ascii.hh \
base64.hh \
bpf-filter.cc bpf-filter.hh \
dns.cc dns.hh \
--- /dev/null
+../ascii.hh
\ No newline at end of file
AC_DEFUN([DNSDIST_ENABLE_DNSCRYPT], [
AC_MSG_CHECKING([whether to enable DNSCrypt support])
AC_ARG_ENABLE([dnscrypt],
- AS_HELP_STRING([--enable-dnscrypt], [enable DNSCrypt support (require libsodium) @<:@default=no@:>@]),
+ AS_HELP_STRING([--enable-dnscrypt], [enable DNSCrypt support (requires libsodium) @<:@default=no@:>@]),
[enable_dnscrypt=$enableval],
[enable_dnscrypt=no]
)
if (static_cast<size_t>(distance) == parent.d_storage.size()) {
auto p = parent.d_storage.cbegin();
for(; us != d_storage.cend(); ++us, ++p) {
- if(dns2_tolower(*p) != dns2_tolower(*us))
+ if(dns_tolower(*p) != dns_tolower(*us))
return false;
}
return true;
throw std::out_of_range("trying to get label at position "+std::to_string(pos)+" of a DNSName that only has "+std::to_string(currentPos)+" labels");
}
+DNSName DNSName::getLastLabel() const
+{
+ DNSName ret(*this);
+ ret.trimToLabels(1);
+ return ret;
+}
+
bool DNSName::chopOff()
{
if(d_storage.empty() || d_storage[0]==0)
#include <boost/container/string.hpp>
#endif
+#include "ascii.hh"
+
uint32_t burtleCI(const unsigned char* k, uint32_t length, uint32_t init);
// #include "dns.hh"
NOTE: For now, everything MUST be . terminated, otherwise it is an error
*/
-inline unsigned char dns2_tolower(unsigned char c)
-{
- if(c>='A' && c<='Z')
- return c+('a'-'A');
- return c;
-}
-
class DNSName
{
public:
void prependRawLabel(const std::string& str); //!< Prepend this unescaped label
std::vector<std::string> getRawLabels() const; //!< Individual raw unescaped labels
std::string getRawLabel(unsigned int pos) const; //!< Get the specified raw unescaped label
+ DNSName getLastLabel() const; //!< Get the DNSName of the last label
bool chopOff(); //!< Turn www.powerdns.com. into powerdns.com., returns false for .
DNSName makeRelative(const DNSName& zone) const;
DNSName makeLowerCase() const
DNSName ret;
ret.d_storage = d_storage;
for(auto & c : ret.d_storage) {
- c=dns2_tolower(c);
+ c=dns_tolower(c);
}
return ret;
}
return std::lexicographical_compare(d_storage.rbegin(), d_storage.rend(),
rhs.d_storage.rbegin(), rhs.d_storage.rend(),
[](const unsigned char& a, const unsigned char& b) {
- return dns2_tolower(a) < dns2_tolower(b);
+ return dns_tolower(a) < dns_tolower(b);
}); // note that this is case insensitive, including on the label lengths
}
rhs.d_storage.c_str() + rhspos[rhscount] + 1,
rhs.d_storage.c_str() + rhspos[rhscount] + 1 + *(rhs.d_storage.c_str() + rhspos[rhscount]),
[](const unsigned char& a, const unsigned char& b) {
- return dns2_tolower(a) < dns2_tolower(b);
+ return dns_tolower(a) < dns_tolower(b);
});
// cout<<"Forward: "<<res<<endl;
d_storage.c_str() + ourpos[ourcount] + 1,
d_storage.c_str() + ourpos[ourcount] + 1 + *(d_storage.c_str() + ourpos[ourcount]),
[](const unsigned char& a, const unsigned char& b) {
- return dns2_tolower(a) < dns2_tolower(b);
+ return dns_tolower(a) < dns_tolower(b);
});
// cout<<"Reverse: "<<res<<endl;
if(res)
auto us = d_storage.cbegin();
auto p = rhs.d_storage.cbegin();
for(; us != d_storage.cend() && p != rhs.d_storage.cend(); ++us, ++p) {
- if(dns2_tolower(*p) != dns2_tolower(*us))
+ if(dns_tolower(*p) != dns_tolower(*us))
return false;
}
return true;
d=orig.d;
d_isQuery = orig.d_isQuery;
+ d_hash = orig.d_hash;
}
void DNSPacket::setRcode(int v)
d_ednsrcode=extRCode;
};
uint8_t getEDNSRCode() const { return d_ednsrcode; };
+ uint32_t getHash() const { return d_hash; };
+ void setHash(uint32_t hash) { d_hash = hash; };
+
//////// DATA !
DNSName qdomain; //!< qname of the question 4 - unsure how this is used
// WARNING! This is really 12 bits
uint16_t d_ednsrcode;
+ uint32_t d_hash{0};
+
bool d_compress; // 1
bool d_tsigtimersonly;
bool d_wantsnsid;
if (query && (dr.d_place == DNSResourceRecord::ANSWER || dr.d_place == DNSResourceRecord::AUTHORITY || (dr.d_type != QType::OPT && dr.d_type != QType::TSIG && dr.d_type != QType::SIG && dr.d_type != QType::TKEY) || ((dr.d_type == QType::TSIG || dr.d_type == QType::SIG || dr.d_type == QType::TKEY) && dr.d_class != QClass::ANY))) {
// cerr<<"discarding RR, query is "<<query<<", place is "<<dr.d_place<<", type is "<<dr.d_type<<", class is "<<dr.d_class<<endl;
- dr.d_content=std::shared_ptr<DNSRecordContent>(new UnknownRecordContent(dr, pr));
+ dr.d_content=std::make_shared<UnknownRecordContent>(dr, pr);
}
else {
// cerr<<"parsing RR, query is "<<query<<", place is "<<dr.d_place<<", type is "<<dr.d_type<<", class is "<<dr.d_class<<endl;
typedef vector<pair<DNSRecord, uint16_t > > answers_t;
- //! All answers contained in this packet
+ //! All answers contained in this packet (everything *but* the question section)
answers_t d_answers;
shared_ptr<PacketReader> getPacketReader(uint16_t offset)
#include "dns_random.hh"
extern StatBag S;
-extern PacketCache PC;
DNSProxy::DNSProxy(const string &remote)
{
d_resanswers=S.getPointer("recursing-answers");
d_resquestions=S.getPointer("recursing-questions");
d_udpanswers=S.getPointer("udp-answers");
- ComboAddress remaddr(remote, 53);
+
+ vector<string> addresses;
+ stringtok(addresses, remote, " ,\t");
+ ComboAddress remaddr(addresses[0], 53);
if((d_sock=socket(remaddr.sin4.sin_family, SOCK_DGRAM,0))<0)
throw PDNSException(string("socket: ")+strerror(errno));
if(sendmsg(i->second.outsock, &msgh, 0) < 0)
L<<Logger::Warning<<"dnsproxy.cc: Error sending reply with sendmsg (socket="<<i->second.outsock<<"): "<<strerror(errno)<<endl;
- PC.insert(&q, &p, true);
i->second.created=0;
}
}
}
+/*
+ * Fills `eo` by parsing the EDNS(0) OPT RR (RFC 6891)
+ */
bool getEDNSOpts(const MOADNSParser& mdp, EDNSOpts* eo)
{
eo->d_Z=0;
d_sor=d_content.size(); // this will remind us where to stuff the record size
}
-void DNSPacketWriter::addOpt(uint16_t udpsize, int extRCode, int Z, const vector<pair<uint16_t,string> >& options)
+void DNSPacketWriter::addOpt(uint16_t udpsize, int extRCode, int Z, const vector<pair<uint16_t,string> >& options, uint8_t version)
{
uint32_t ttl=0;
EDNS0Record stuff;
stuff.extRCode=extRCode;
- stuff.version=0;
+ stuff.version=version;
stuff.Z=htons(Z);
static_assert(sizeof(EDNS0Record) == sizeof(ttl), "sizeof(EDNS0Record) must match sizeof(ttl)");
/** Shorthand way to add an Opt-record, for example for EDNS0 purposes */
typedef vector<pair<uint16_t,std::string> > optvect_t;
- void addOpt(uint16_t udpsize, int extRCode, int Z, const optvect_t& options=optvect_t());
+ void addOpt(uint16_t udpsize, int extRCode, int Z, const optvect_t& options=optvect_t(), uint8_t version=0);
/** needs to be called after the last record is added, but can be called again and again later on. Is called internally by startRecord too.
The content of the vector<> passed to the constructor is inconsistent until commit is called.
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
-#include "packetcache.hh"
+#include "auth-caches.hh"
+#include "auth-querycache.hh"
+#include "auth-packetcache.hh"
#include "utility.hh"
#include "dynhandler.hh"
#include "statbag.hh"
string DLPurgeHandler(const vector<string>&parts, Utility::pid_t ppid)
{
- extern PacketCache PC;
DNSSECKeeper dk;
ostringstream os;
int ret=0;
if(parts.size()>1) {
for (vector<string>::const_iterator i=++parts.begin();i<parts.end();++i) {
- ret+=PC.purge(*i);
+ ret+=purgeAuthCaches(*i);
if(!boost::ends_with(*i, "$"))
dk.clearCaches(DNSName(*i));
else
}
}
else {
- ret=PC.purge();
+ ret = purgeAuthCaches();
dk.clearAllCaches();
}
string DLCCHandler(const vector<string>&parts, Utility::pid_t ppid)
{
- extern PacketCache PC;
- map<char,int> counts=PC.getCounts();
+ extern AuthPacketCache PC;
+ extern AuthQueryCache QC;
+ map<char,uint64_t> counts=QC.getCounts();
+ uint64_t packetEntries = PC.size();
ostringstream os;
bool first=true;
- for(map<char,int>::const_iterator i=counts.begin();i!=counts.end();++i) {
+ for(map<char,uint64_t>::const_iterator i=counts.begin();i!=counts.end();++i) {
if(!first)
os<<", ";
first=false;
os<<"negative queries: ";
else if(i->first=='Q')
os<<"queries: ";
- else if(i->first=='p')
- os<<"packets: ";
else
os<<"unknown: ";
os<<i->second;
}
+ os<<"packets: "<<packetEntries;
return os.str();
}
return ENOENT;
}
+/* extract all EDNS0 options from a pointer on the beginning rdLen of the OPT RR */
+int getEDNSOptions(const char* optRR, const size_t len, std::map<uint16_t, EDNSOptionView>& options)
+{
+ assert(optRR != NULL);
+ size_t pos = 0;
+ if (len < DNS_RDLENGTH_SIZE)
+ return EINVAL;
+
+ const uint16_t rdLen = (((unsigned char) optRR[pos]) * 256) + ((unsigned char) optRR[pos+1]);
+ size_t rdPos = 0;
+ pos += DNS_RDLENGTH_SIZE;
+ if ((pos + rdLen) > len) {
+ return EINVAL;
+ }
+
+ while(len >= (pos + EDNS_OPTION_CODE_SIZE + EDNS_OPTION_LENGTH_SIZE) &&
+ rdLen >= (rdPos + EDNS_OPTION_CODE_SIZE + EDNS_OPTION_LENGTH_SIZE)) {
+ const uint16_t optionCode = (((unsigned char) optRR[pos]) * 256) + ((unsigned char) optRR[pos+1]);
+ pos += EDNS_OPTION_CODE_SIZE;
+ rdPos += EDNS_OPTION_CODE_SIZE;
+ const uint16_t optionLen = (((unsigned char) optRR[pos]) * 256) + ((unsigned char) optRR[pos+1]);
+ pos += EDNS_OPTION_LENGTH_SIZE;
+ rdPos += EDNS_OPTION_LENGTH_SIZE;
+ if (optionLen > (rdLen - rdPos) || optionLen > (len - pos))
+ return EINVAL;
+
+ EDNSOptionView view;
+ view.content = optRR + pos;
+ view.size = optionLen;
+ options[optionCode] = view;
+
+ /* skip this option */
+ pos += optionLen;
+ rdPos += optionLen;
+ }
+
+ return 0;
+}
+
void generateEDNSOption(uint16_t optionCode, const std::string& payload, std::string& res)
{
const uint16_t ednsOptionCode = htons(optionCode);
/* 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);
+
+struct EDNSOptionView
+{
+ const char* content{nullptr};
+ uint16_t size{0};
+};
+
+/* extract all EDNS0 options from a pointer on the beginning rdLen of the OPT RR */
+int getEDNSOptions(const char* optRR, size_t len, std::map<uint16_t, EDNSOptionView>& options);
+
void generateEDNSOption(uint16_t optionCode, const std::string& payload, std::string& res);
#endif
z.qpolAddr.clear();
z.postpolAddr.clear();
z.propolName.clear();
+ z.propolNSAddr.clear();
z.qpolName.clear();
}
+void DNSFilterEngine::clear()
+{
+ for(auto& z : d_zones) {
+ z.qpolAddr.clear();
+ z.postpolAddr.clear();
+ z.propolName.clear();
+ z.propolNSAddr.clear();
+ z.qpolName.clear();
+ }
+}
+
void DNSFilterEngine::addClientTrigger(const Netmask& nm, Policy pol, size_t zone)
{
assureZones(zone);
return false;
}
+
+/* requires a non-blocking socket.
+ On Linux, we could use MSG_DONTWAIT on a blocking socket
+ but this is not portable.
+*/
+bool isTCPSocketUsable(int sock)
+{
+ int err = 0;
+ char buf = '\0';
+ size_t buf_size = sizeof(buf);
+
+ do {
+ ssize_t got = recv(sock, &buf, buf_size, MSG_PEEK);
+
+ if (got > 0) {
+ /* socket is usable, some data is even waiting to be read */
+ return true;
+ }
+ else if (got == 0) {
+ /* other end has closed the socket */
+ return false;
+ }
+ else {
+ int err = errno;
+
+ if (err == EAGAIN || err == EWOULDBLOCK) {
+ /* socket is usable, no data waiting */
+ return true;
+ }
+ else {
+ if (err != EINTR) {
+ /* something is wrong, could be ECONNRESET,
+ ENOTCONN, EPIPE, but anyway this socket is
+ not usable. */
+ return false;
+ }
+ }
+ }
+ } while (err == EINTR);
+
+ return false;
+}
ssize_t sendfromto(int sock, const char* data, size_t len, int flags, const ComboAddress& from, const ComboAddress& to);
ssize_t sendMsgWithTimeout(int fd, const char* buffer, size_t len, int timeout, ComboAddress& dest, const ComboAddress& local, unsigned int localItf);
bool sendSizeAndMsgWithTimeout(int sock, uint16_t bufferLen, const char* buffer, int idleTimeout, const ComboAddress* dest, const ComboAddress* local, unsigned int localItf, int totalTimeout, int flags);
+/* requires a non-blocking, connected TCP socket */
+bool isTCPSocketUsable(int sock);
extern template class NetmaskTree<bool>;
{
pthread_mutex_t *d_lock;
public:
+ Lock(const Lock& rhs) = delete;
+ Lock& operator=(const Lock& rhs) = delete;
Lock(pthread_mutex_t *lock) : d_lock(lock)
{
{
if(g_singleThreaded)
return;
+ if(d_lock) // might have been moved
+ pthread_rwlock_unlock(d_lock);
+ }
- pthread_rwlock_unlock(d_lock);
+ WriteLock(WriteLock&& rhs)
+ {
+ d_lock = rhs.d_lock;
+ rhs.d_lock=0;
}
+ WriteLock(const WriteLock& rhs) = delete;
+ WriteLock& operator=(const WriteLock& rhs) = delete;
+
+
};
class TryWriteLock
pthread_rwlock_t *d_lock;
bool d_havelock;
public:
+ TryWriteLock(const TryWriteLock& rhs) = delete;
+ TryWriteLock& operator=(const TryWriteLock& rhs) = delete;
TryWriteLock(pthread_rwlock_t *lock) : d_lock(lock)
{
throw PDNSException("error acquiring rwlock tryrwlock: "+stringerror());
d_havelock=(errno==0);
}
+
+ TryWriteLock(TryWriteLock&& rhs)
+ {
+ d_lock = rhs.d_lock;
+ rhs.d_lock=0;
+ }
+
+
~TryWriteLock()
{
if(g_singleThreaded)
return;
- if(d_havelock)
+ if(d_havelock && d_lock) // we might be moved
pthread_rwlock_unlock(d_lock);
}
bool gotIt()
pthread_rwlock_t *d_lock;
bool d_havelock;
public:
+ TryReadLock(const TryReadLock& rhs) = delete;
+ TryReadLock& operator=(const TryReadLock& rhs) = delete;
TryReadLock(pthread_rwlock_t *lock) : d_lock(lock)
{
throw PDNSException("error acquiring rwlock tryrdlock: "+stringerror());
d_havelock=(errno==0);
}
+ TryReadLock(TryReadLock&& rhs)
+ {
+ d_lock = rhs.d_lock;
+ rhs.d_lock=0;
+ }
+
~TryReadLock()
{
if(g_singleThreaded)
return;
- if(d_havelock)
+ if(d_havelock && d_lock)
pthread_rwlock_unlock(d_lock);
}
bool gotIt()
return;
if((errno=pthread_rwlock_rdlock(d_lock)))
- throw PDNSException("error acquiring rwlock tryrwlock: "+stringerror());
+ throw PDNSException("error acquiring rwlock readlock: "+stringerror());
}
~ReadLock()
{
if(g_singleThreaded)
return;
-
- pthread_rwlock_unlock(d_lock);
+ if(d_lock) // may have been moved
+ pthread_rwlock_unlock(d_lock);
}
-
- void upgrade()
- {
- if(g_singleThreaded)
- return;
- pthread_rwlock_unlock(d_lock);
- pthread_rwlock_wrlock(d_lock);
+ ReadLock(ReadLock&& rhs)
+ {
+ d_lock = rhs.d_lock;
+ rhs.d_lock=0;
}
+ ReadLock(const ReadLock& rhs) = delete;
+ ReadLock& operator=(const ReadLock& rhs) = delete;
};
#endif
#include "rec-snmp.hh"
#include <unordered_set>
-#undef L
-#include "ext/luawrapper/include/LuaContext.hpp"
-
static int followCNAMERecords(vector<DNSRecord>& ret, const QType& qtype)
{
vector<DNSRecord> resolved;
d_lw->registerMember("type", &DNSRecord::d_type);
d_lw->registerMember("ttl", &DNSRecord::d_ttl);
d_lw->registerMember("place", &DNSRecord::d_place);
-
+
+ d_lw->registerMember("size", &EDNSOptionView::size);
+ d_lw->registerFunction<std::string(EDNSOptionView::*)()>("getContent", [](const EDNSOptionView& option) { return std::string(option.content, option.size); });
+
d_lw->registerFunction<string(DNSRecord::*)()>("getContent", [](const DNSRecord& dr) { return dr.d_content->getZoneRepresentation(); });
d_lw->registerFunction<boost::optional<ComboAddress>(DNSRecord::*)()>("getCA", [](const DNSRecord& dr) {
boost::optional<ComboAddress> ret;
return false; // don't block
}
-unsigned int RecursorLua4::gettag(const ComboAddress& remote, const Netmask& ednssubnet, const ComboAddress& local, const DNSName& qname, uint16_t qtype, std::vector<std::string>* policyTags, std::unordered_map<string,string>& data)
+unsigned int RecursorLua4::gettag(const ComboAddress& remote, const Netmask& ednssubnet, const ComboAddress& local, const DNSName& qname, uint16_t qtype, std::vector<std::string>* policyTags, LuaContext::LuaObject& data, const std::map<uint16_t, EDNSOptionView>& ednsOptions)
{
if(d_gettag) {
- auto ret = d_gettag(remote, ednssubnet, local, qname, qtype);
+ auto ret = d_gettag(remote, ednssubnet, local, qname, qtype, ednsOptions);
if (policyTags) {
const auto& tags = std::get<1>(ret);
}
}
}
- const auto& dataret = std::get<2>(ret);
+ const auto dataret = std::get<2>(ret);
if (dataret) {
data = *dataret;
}
#include "namespaces.hh"
#include "dnsrecords.hh"
#include "filterpo.hh"
+#include "ednsoptions.hh"
+
#include <unordered_map>
+
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
class LuaContext;
+#if defined(HAVE_LUA)
+#undef L
+#include "ext/luawrapper/include/LuaContext.hpp"
+#define L theL()
+#endif
+
class RecursorLua4 : public boost::noncopyable
{
private:
string udpAnswer;
string udpCallback;
- std::unordered_map<string,string> data;
+ LuaContext::LuaObject data;
DNSName followupName;
};
- unsigned int gettag(const ComboAddress& remote, const Netmask& ednssubnet, const ComboAddress& local, const DNSName& qname, uint16_t qtype, std::vector<std::string>* policyTags, std::unordered_map<string,string>& data);
+ unsigned int gettag(const ComboAddress& remote, const Netmask& ednssubnet, const ComboAddress& local, const DNSName& qname, uint16_t qtype, std::vector<std::string>* policyTags, LuaContext::LuaObject& data, const std::map<uint16_t, EDNSOptionView>&);
bool prerpz(DNSQuestion& dq, int& ret);
bool preresolve(DNSQuestion& dq, int& ret);
d_postresolve);
}
- typedef std::function<std::tuple<unsigned int,boost::optional<std::unordered_map<int,string> >,boost::optional<std::unordered_map<string,string> > >(ComboAddress, Netmask, ComboAddress, DNSName,
-uint16_t)> gettag_t;
+ typedef std::function<std::tuple<unsigned int,boost::optional<std::unordered_map<int,string> >,boost::optional<LuaContext::LuaObject> >(ComboAddress, Netmask, ComboAddress, DNSName, uint16_t, const std::map<uint16_t, EDNSOptionView>&)> gettag_t;
gettag_t d_gettag; // public so you can query if we have this hooked
private:
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
-#include "packetcache.hh"
+#include "auth-caches.hh"
#include "utility.hh"
#include <errno.h>
#include "communicator.hh"
// do this via the FindNS class, d_fns
for(auto& di : cmdomains) {
- extern PacketCache PC;
- PC.purgeExact(di.zone);
+ purgeAuthCachesExact(di.zone);
queueNotifyDomain(di, B);
di.backend->setNotified(di.id, di.serial);
}
return c==' ' || c=='\t' || c=='\r' || c=='\n';
}
-inline unsigned char dns_tolower(unsigned char c)
-{
- if(c>='A' && c<='Z')
- c+='a'-'A';
- return c;
-}
-
inline unsigned char dns_toupper(unsigned char c)
{
if(c>='a' && c<='z')
+++ /dev/null
-/*
- * 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.
- */
-#ifdef HAVE_CONFIG_H
-#include "config.h"
-#endif
-#include "utility.hh"
-#include "packetcache.hh"
-#include "logger.hh"
-#include "arguments.hh"
-#include "statbag.hh"
-#include <map>
-#include <boost/algorithm/string.hpp>
-
-const unsigned int PacketCache::s_mincleaninterval, PacketCache::s_maxcleaninterval;
-
-extern StatBag S;
-
-PacketCache::PacketCache()
-{
- d_ops=0;
- d_maps.resize(1024);
- for(auto& mc : d_maps) {
- pthread_rwlock_init(&mc.d_mut, 0);
- }
-
- d_ttl=-1;
-
- d_lastclean=time(0);
- d_cleanskipped=false;
- d_nextclean=d_cleaninterval=4096;
-
- S.declare("packetcache-hit");
- S.declare("packetcache-miss");
- S.declare("packetcache-size");
-
- d_statnumhit=S.getPointer("packetcache-hit");
- d_statnummiss=S.getPointer("packetcache-miss");
- d_statnumentries=S.getPointer("packetcache-size");
-}
-
-PacketCache::~PacketCache()
-{
- try {
- // WriteLock l(&d_mut);
- vector<WriteLock*> locks;
- for(auto& mc : d_maps) {
- locks.push_back(new WriteLock(&mc.d_mut));
- }
- for(auto wl : locks) {
- delete wl;
- }
- }
- catch(...) {
- }
-}
-
-
-
-int PacketCache::get(DNSPacket *p, DNSPacket *cached)
-{
- extern StatBag S;
-
- if(d_ttl<0)
- getTTLS();
-
- cleanupIfNeeded();
-
- if(!d_ttl) {
- (*d_statnummiss)++;
- return 0;
- }
-
- if(ntohs(p->d.qdcount)!=1) // we get confused by packets with more than one question
- return 0;
-
- string value;
- bool haveSomething;
- {
- auto& mc=getMap(p->qdomain);
- TryReadLock l(&mc.d_mut); // take a readlock here
- if(!l.gotIt()) {
- S.inc("deferred-cache-lookup");
- return 0;
- }
-
- uint16_t maxReplyLen = p->d_tcp ? 0xffff : p->getMaxReplyLen();
- haveSomething=getEntryLocked(p->qdomain, p->qtype, PacketCache::PACKETCACHE, value, -1, maxReplyLen, p->d_dnssecOk, p->hasEDNS());
- }
- if(haveSomething) {
- (*d_statnumhit)++;
- if(cached->noparse(value.c_str(), value.size()) < 0)
- return 0;
- cached->spoofQuestion(p); // for correct case
- cached->qdomain=p->qdomain;
- cached->qtype=p->qtype;
- return 1;
- }
-
- // cerr<<"Packet cache miss for '"<<p->qdomain<<"'"<<endl;
- (*d_statnummiss)++;
- return 0; // bummer
-}
-
-void PacketCache::getTTLS()
-{
- d_ttl=::arg().asNum("cache-ttl");
-}
-
-
-void PacketCache::insert(DNSPacket *q, DNSPacket *r, unsigned int maxttl)
-{
- if(d_ttl < 0)
- getTTLS();
-
- if(ntohs(q->d.qdcount)!=1) {
- return; // do not try to cache packets with multiple questions
- }
-
- if(q->qclass != QClass::IN) // we only cache the INternet
- return;
-
- uint16_t maxReplyLen = q->d_tcp ? 0xffff : q->getMaxReplyLen();
- unsigned int ourttl = d_ttl;
- if(maxttl<ourttl)
- ourttl=maxttl;
- insert(q->qdomain, q->qtype, PacketCache::PACKETCACHE, r->getString(), ourttl, -1,
- maxReplyLen, q->d_dnssecOk, q->hasEDNS());
-}
-
-// universal key appears to be: qname, qtype, kind (packet, query cache), optionally zoneid
-void PacketCache::insert(const DNSName &qname, const QType& qtype, CacheEntryType cet, const string& value, unsigned int ttl, int zoneID,
- unsigned int maxReplyLen, bool dnssecOk, bool EDNS)
-{
- cleanupIfNeeded();
-
- if(!ttl)
- return;
-
- //cerr<<"Inserting qname '"<<qname<<"', cet: "<<(int)cet<<", qtype: "<<qtype.getName()<<", ttl: "<<ttl<<", maxreplylen: "<<maxReplyLen<<", hasEDNS: "<<EDNS<<endl;
- CacheEntry val;
- val.created=time(0);
- val.ttd=val.created+ttl;
- val.qname=qname;
- val.qtype=qtype.getCode();
- val.value=value;
- val.ctype=cet;
- val.maxReplyLen = maxReplyLen;
- val.dnssecOk = dnssecOk;
- val.zoneID = zoneID;
- val.hasEDNS = EDNS;
-
- auto& mc = getMap(val.qname);
-
- TryWriteLock l(&mc.d_mut);
- if(l.gotIt()) {
- bool success;
- cmap_t::iterator place;
- tie(place, success)=mc.d_map.insert(val);
-
- if(!success)
- mc.d_map.replace(place, val);
- else
- (*d_statnumentries)++;
- }
- else
- S.inc("deferred-cache-inserts");
-}
-
-void PacketCache::insert(const DNSName &qname, const QType& qtype, CacheEntryType cet, const vector<DNSZoneRecord>& value, unsigned int ttl, int zoneID)
-{
- cleanupIfNeeded();
-
- if(!ttl)
- return;
-
- //cerr<<"Inserting qname '"<<qname<<"', cet: "<<(int)cet<<", qtype: "<<qtype.getName()<<", ttl: "<<ttl<<", maxreplylen: "<<maxReplyLen<<", hasEDNS: "<<EDNS<<endl;
- CacheEntry val;
- val.created=time(0);
- val.ttd=val.created+ttl;
- val.qname=qname;
- val.qtype=qtype.getCode();
- val.drs=value;
- val.ctype=cet;
- val.maxReplyLen = 0;
- val.dnssecOk = false;
- val.zoneID = zoneID;
- val.hasEDNS = false;
-
- auto& mc = getMap(val.qname);
-
- TryWriteLock l(&mc.d_mut);
- if(l.gotIt()) {
- bool success;
- cmap_t::iterator place;
- tie(place, success)=mc.d_map.insert(val);
-
- if(!success)
- mc.d_map.replace(place, val);
- else
- (*d_statnumentries)++;
- }
- else
- S.inc("deferred-cache-inserts");
-}
-
-
-/* clears the entire packetcache. */
-int PacketCache::purge()
-{
- int delcount=0;
- for(auto& mc : d_maps) {
- WriteLock l(&mc.d_mut);
- delcount+=mc.d_map.size();
- mc.d_map.clear();
- }
- d_statnumentries->store(0);
- return delcount;
-}
-
-int PacketCache::purgeExact(const DNSName& qname)
-{
- int delcount=0;
- auto& mc = getMap(qname);
-
- WriteLock l(&mc.d_mut);
- auto range = mc.d_map.equal_range(tie(qname));
- if(range.first != range.second) {
- delcount+=distance(range.first, range.second);
- mc.d_map.erase(range.first, range.second);
- }
- *d_statnumentries-=delcount;
- return delcount;
-}
-
-/* purges entries from the packetcache. If match ends on a $, it is treated as a suffix */
-int PacketCache::purge(const string &match)
-{
- if(ends_with(match, "$")) {
- int delcount=0;
- string prefix(match);
- prefix.resize(prefix.size()-1);
- DNSName dprefix(prefix);
- for(auto& mc : d_maps) {
- WriteLock l(&mc.d_mut);
- cmap_t::const_iterator iter = mc.d_map.lower_bound(tie(dprefix));
- auto start=iter;
-
- for(; iter != mc.d_map.end(); ++iter) {
- if(!iter->qname.isPartOf(dprefix)) {
- break;
- }
- delcount++;
- }
- mc.d_map.erase(start, iter);
- }
- *d_statnumentries-=delcount;
- return delcount;
- }
- else {
- return purgeExact(DNSName(match));
- }
-}
-// called from ueberbackend
-bool PacketCache::getEntry(const DNSName &qname, const QType& qtype, CacheEntryType cet, vector<DNSZoneRecord>& value, int zoneID)
-{
- if(d_ttl<0)
- getTTLS();
-
- cleanupIfNeeded();
-
- auto& mc=getMap(qname);
-
- TryReadLock l(&mc.d_mut); // take a readlock here
- if(!l.gotIt()) {
- S.inc( "deferred-cache-lookup");
- return false;
- }
-
- return getEntryLocked(qname, qtype, cet, value, zoneID);
-}
-
-
-bool PacketCache::getEntryLocked(const DNSName &qname, const QType& qtype, CacheEntryType cet, string& value, int zoneID,
- unsigned int maxReplyLen, bool dnssecOK, bool hasEDNS)
-{
- uint16_t qt = qtype.getCode();
- //cerr<<"Lookup for maxReplyLen: "<<maxReplyLen<<endl;
- auto& mc=getMap(qname);
- // cmap_t::const_iterator i=mc.d_map.find(tie(qname, qt, cet, zoneID, maxReplyLen, dnssecOK, hasEDNS));
-
- auto& idx = boost::multi_index::get<UnorderedNameTag>(mc.d_map);
- auto range=idx.equal_range(tie(qname, qt, cet, zoneID));
-
- if(range.first == range.second)
- return false;
- time_t now=time(0);
- for(auto iter = range.first ; iter != range.second; ++iter) {
- if(maxReplyLen == iter->maxReplyLen && dnssecOK == iter->dnssecOk && hasEDNS == iter->hasEDNS ) {
- if(iter->ttd > now) {
- value = iter->value;
- return true;
- }
- }
- }
-
- return false;
-}
-
-bool PacketCache::getEntryLocked(const DNSName &qname, const QType& qtype, CacheEntryType cet, vector<DNSZoneRecord>& value, int zoneID)
-{
- uint16_t qt = qtype.getCode();
- //cerr<<"Lookup for maxReplyLen: "<<maxReplyLen<<endl;
- auto& mc=getMap(qname);
- auto& idx = boost::multi_index::get<UnorderedNameTag>(mc.d_map);
- auto i=idx.find(tie(qname, qt, cet, zoneID));
- if(i==idx.end())
- return false;
-
- time_t now=time(0);
- if(i->ttd > now) {
- value = i->drs;
- return true;
- }
- return false;
-}
-
-
-map<char,int> PacketCache::getCounts()
-{
- int packets=0, queryCacheEntries=0, negQueryCacheEntries=0;
-
- for(auto& mc : d_maps) {
- ReadLock l(&mc.d_mut);
-
- for(cmap_t::const_iterator iter = mc.d_map.begin() ; iter != mc.d_map.end(); ++iter) {
- if(iter->ctype == PACKETCACHE)
- packets++;
- else if(iter->ctype == QUERYCACHE) {
- if(iter->value.empty())
- negQueryCacheEntries++;
- else
- queryCacheEntries++;
- }
- }
- }
- map<char,int> ret;
-
- ret['!']=negQueryCacheEntries;
- ret['Q']=queryCacheEntries;
- ret['p']=packets;
- return ret;
-}
-
-
-void PacketCache::cleanup()
-{
- unsigned int maxCached = ::arg().asNum("max-cache-entries");
- unsigned long cacheSize = *d_statnumentries;
-
- // two modes - if toTrim is 0, just look through 10% of the cache and nuke everything that is expired
- // otherwise, scan first 5*toTrim records, and stop once we've nuked enough
- unsigned int toTrim = 0, lookAt = 0;
- if(maxCached && cacheSize > maxCached) {
- toTrim = cacheSize - maxCached;
- lookAt = 5 * toTrim;
- } else {
- lookAt = cacheSize / 10;
- }
-
- DLOG(L<<"Starting cache clean, cacheSize: "<<cacheSize<<", lookAt: "<<lookAt<<", toTrim: "<<toTrim<<endl);
-
- time_t now = time(0);
- unsigned int totErased = 0;
- for(auto& mc : d_maps) {
- WriteLock wl(&mc.d_mut);
- auto& sidx = boost::multi_index::get<SequenceTag>(mc.d_map);
- unsigned int erased = 0, lookedAt = 0;
- for(auto i = sidx.begin(); i != sidx.end(); lookedAt++) {
- if(i->ttd < now) {
- i = sidx.erase(i);
- erased++;
- } else {
- ++i;
- }
-
- if(toTrim && erased > toTrim / d_maps.size())
- break;
-
- if(lookedAt > lookAt / d_maps.size())
- break;
- }
- totErased += erased;
- }
- *d_statnumentries -= totErased;
-
- DLOG(L<<"Done with cache clean, cacheSize: "<<*d_statnumentries<<", totErased"<<totErased<<endl);
-}
-
-/* the logic:
- after d_nextclean operations, we clean. We also adjust the cleaninterval
- a bit so we slowly move it to a value where we clean roughly every 30 seconds.
-
- If d_nextclean has reached its maximum value, we also test if we were called
- within 30 seconds, and if so, we skip cleaning. This means that under high load,
- we will not clean more often than every 30 seconds anyhow.
-*/
-
-void PacketCache::cleanupIfNeeded()
-{
- if (d_ops++ == d_nextclean) {
- int timediff = max((int)(time(0) - d_lastclean), 1);
-
- DLOG(L<<"cleaninterval: "<<d_cleaninterval<<", timediff: "<<timediff<<endl);
-
- if (d_cleaninterval == s_maxcleaninterval && timediff < 30) {
- d_cleanskipped = true;
- d_nextclean += d_cleaninterval;
-
- DLOG(L<<"cleaning skipped, timediff: "<<timediff<<endl);
-
- return;
- }
-
- if(!d_cleanskipped) {
- d_cleaninterval=(int)(0.6*d_cleaninterval)+(0.4*d_cleaninterval*(30.0/timediff));
- d_cleaninterval=std::max(d_cleaninterval, s_mincleaninterval);
- d_cleaninterval=std::min(d_cleaninterval, s_maxcleaninterval);
-
- DLOG(L<<"new cleaninterval: "<<d_cleaninterval<<endl);
- } else {
- d_cleanskipped = false;
- }
-
- d_nextclean += d_cleaninterval;
- d_lastclean=time(0);
- cleanup();
- }
-}
#ifndef PACKETCACHE_HH
#define PACKETCACHE_HH
-#include <string>
-#include <utility>
-#include <map>
-#include <map>
-#include "dns.hh"
-#include <boost/version.hpp>
-#include "namespaces.hh"
-using namespace ::boost::multi_index;
-
-#include "namespaces.hh"
-#include <boost/multi_index/hashed_index.hpp>
-#include "dnspacket.hh"
-#include "lock.hh"
-#include "statbag.hh"
-
-/** This class performs 'whole packet caching'. Feed it a question packet and it will
- try to find an answer. If you have an answer, insert it to have it cached for later use.
- Take care not to replace existing cache entries. While this works, it is wasteful. Only
- insert packets that where not found by get()
-
- Locking!
-
- The cache itself is protected by a read/write lock. Because deleting is a two step process, which
- first marks and then sweeps, a second lock is present to prevent simultaneous inserts and deletes.
-*/
+#include "ednsoptions.hh"
+#include "misc.hh"
+#include "iputils.hh"
class PacketCache : public boost::noncopyable
{
-public:
- PacketCache();
- ~PacketCache();
- enum CacheEntryType { PACKETCACHE, QUERYCACHE};
-
- void insert(DNSPacket *q, DNSPacket *r, unsigned int maxttl=UINT_MAX); //!< We copy the contents of *p into our cache. Do not needlessly call this to insert questions already in the cache as it wastes resources
-
- void insert(const DNSName &qname, const QType& qtype, CacheEntryType cet, const string& value, unsigned int ttl, int zoneID=-1,
- unsigned int maxReplyLen=512, bool dnssecOk=false, bool EDNS=false);
-
- void insert(const DNSName &qname, const QType& qtype, CacheEntryType cet, const vector<DNSZoneRecord>& content, unsigned int ttl, int zoneID=-1);
-
- int get(DNSPacket *p, DNSPacket *q); //!< We return a dynamically allocated copy out of our cache. You need to delete it. You also need to spoof in the right ID with the DNSPacket.spoofID() method.
- bool getEntry(const DNSName &qname, const QType& qtype, CacheEntryType cet, string& entry, int zoneID=-1,
- unsigned int maxReplyLen=512, bool dnssecOk=false, bool hasEDNS=false);
- bool getEntry(const DNSName &qname, const QType& qtype, CacheEntryType cet, vector<DNSZoneRecord>& entry, int zoneID=-1);
-
-
- int size() { return *d_statnumentries; } //!< number of entries in the cache
- void cleanupIfNeeded();
- void cleanup(); //!< force the cache to preen itself from expired packets
- int purge();
- int purge(const std::string& match); // could be $ terminated. Is not a dnsname!
- int purgeExact(const DNSName& qname); // no wildcard matching here
-
- map<char,int> getCounts();
-private:
- bool getEntryLocked(const DNSName &content, const QType& qtype, CacheEntryType cet, string& entry, int zoneID=-1,
- unsigned int maxReplyLen=512, bool dnssecOk=false, bool hasEDNS=false);
- bool getEntryLocked(const DNSName &content, const QType& qtype, CacheEntryType cet, vector<DNSZoneRecord>& entry, int zoneID=-1);
-
-
- struct CacheEntry
+protected:
+ static uint32_t canHashPacket(const std::string& packet, bool skipECS=true)
{
- CacheEntry() { qtype = ctype = 0; zoneID = -1; dnssecOk=false; hasEDNS=false; created=0; ttd=0; maxReplyLen=512;}
-
- DNSName qname;
- string value;
- vector<DNSZoneRecord> drs;
- time_t created;
- time_t ttd;
-
- uint16_t qtype;
- uint16_t ctype;
- int zoneID;
- unsigned int maxReplyLen;
-
- bool dnssecOk;
- bool hasEDNS;
- };
-
- void getTTLS();
-
- struct UnorderedNameTag{};
- struct SequenceTag{};
- typedef multi_index_container<
- CacheEntry,
- indexed_by <
- ordered_unique<
- composite_key<
- CacheEntry,
- member<CacheEntry,DNSName,&CacheEntry::qname>,
- member<CacheEntry,uint16_t,&CacheEntry::qtype>,
- member<CacheEntry,uint16_t, &CacheEntry::ctype>,
- member<CacheEntry,int, &CacheEntry::zoneID>,
- member<CacheEntry,unsigned int, &CacheEntry::maxReplyLen>,
- member<CacheEntry,bool, &CacheEntry::dnssecOk>,
- member<CacheEntry,bool, &CacheEntry::hasEDNS>
- >,
- composite_key_compare<CanonDNSNameCompare, std::less<uint16_t>, std::less<uint16_t>, std::less<int>,
- std::less<unsigned int>, std::less<bool>, std::less<bool> >
- >,
- hashed_non_unique<tag<UnorderedNameTag>, composite_key<CacheEntry,
- member<CacheEntry,DNSName,&CacheEntry::qname>,
- member<CacheEntry,uint16_t,&CacheEntry::qtype>,
- member<CacheEntry,uint16_t, &CacheEntry::ctype>,
- member<CacheEntry,int, &CacheEntry::zoneID> > > ,
- sequenced<tag<SequenceTag>>
- >
- > cmap_t;
-
-
- struct MapCombo
- {
- pthread_rwlock_t d_mut;
- cmap_t d_map;
- };
-
- vector<MapCombo> d_maps;
- MapCombo& getMap(const DNSName& qname)
- {
- return d_maps[qname.hash() % d_maps.size()];
+ uint32_t ret = 0;
+ ret=burtle((const unsigned char*)packet.c_str() + 2, 10, ret); // rest of dnsheader, skip id
+ size_t packetSize = packet.size();
+ size_t pos = 12;
+ const char* end = packet.c_str() + packetSize;
+ const char* p = packet.c_str() + pos;
+
+ for(; p < end && *p; ++p) { // XXX if you embed a 0 in your qname we'll stop lowercasing there
+ const unsigned char l = dns_tolower(*p); // label lengths can safely be lower cased
+ ret=burtle(&l, 1, ret);
+ } // XXX the embedded 0 in the qname will break the subnet stripping
+
+ struct dnsheader* dh = (struct dnsheader*)packet.c_str();
+ const char* skipBegin = p;
+ const char* skipEnd = p;
+ /* we need at least 1 (final empty label) + 2 (QTYPE) + 2 (QCLASS)
+ + OPT root label (1), type (2), class (2) and ttl (4)
+ + the OPT RR rdlen (2)
+ = 16
+ */
+ if(skipECS && ntohs(dh->arcount)==1 && (pos+16) < packetSize) {
+ char* optionBegin = nullptr;
+ size_t optionLen = 0;
+ /* skip the final empty label (1), the qtype (2), qclass (2) */
+ /* root label (1), type (2), class (2) and ttl (4) */
+ int res = getEDNSOption((char*) p + 14, end - (p + 14), EDNSOptionCode::ECS, &optionBegin, &optionLen);
+ if (res == 0) {
+ skipBegin = optionBegin;
+ skipEnd = optionBegin + optionLen;
+ }
+ }
+ if (skipBegin > p) {
+ // cerr << "Hashing from " << (p-packet.c_str()) << " for " << skipBegin-p << "bytes, end is at "<< end-packet.c_str() << endl;
+ ret = burtle((const unsigned char*)p, skipBegin-p, ret);
+ }
+ if (skipEnd < end) {
+ // cerr << "Hashing from " << (skipEnd-packet.c_str()) << " for " << end-skipEnd << "bytes, end is at " << end-packet.c_str() << endl;
+ ret = burtle((const unsigned char*) skipEnd, end-skipEnd, ret);
+ }
+
+ return ret;
}
-
- AtomicCounter d_ops;
- time_t d_lastclean; // doesn't need to be atomic
- unsigned long d_nextclean;
- unsigned int d_cleaninterval;
- bool d_cleanskipped;
- AtomicCounter *d_statnumhit;
- AtomicCounter *d_statnummiss;
- AtomicCounter *d_statnumentries;
-
- int d_ttl;
-
- static const unsigned int s_mincleaninterval=1000, s_maxcleaninterval=300000;
};
-
-
#endif /* PACKETCACHE_HH */
{
++s_count;
d_doDNAME=::arg().mustDo("dname-processing");
+ d_doExpandALIAS = ::arg().mustDo("expand-alias");
d_logDNSDetails= ::arg().mustDo("log-dns-details");
d_doIPv6AdditionalProcessing = ::arg().mustDo("do-ipv6-additional-processing");
string fname= ::arg()["lua-prequery-script"];
weRedirected=1;
if(DP && rr.dr.d_type == QType::ALIAS && (p->qtype.getCode() == QType::A || p->qtype.getCode() == QType::AAAA || p->qtype.getCode() == QType::ANY)) {
+ if (!d_doExpandALIAS) {
+ L<<Logger::Info<<"ALIAS record found for "<<target<<", but ALIAS expansion is disabled."<<endl;
+ continue;
+ }
haveAlias=getRR<ALIASRecordContent>(rr.dr)->d_content;
}
for(const auto& rr: r->getRRS()) {
if(rr.scopeMask) {
- noCache=1;
+ noCache=true;
break;
}
}
addRRSigs(d_dk, B, authSet, r->getRRS());
r->wrapup(); // needed for inserting in cache
- if(!noCache)
+ if(!noCache && p->couldBeCached())
PC.insert(p, r, r->getMinTTL()); // in the packet cache
}
catch(DBException &e) {
bool d_logDNSDetails;
bool d_doIPv6AdditionalProcessing;
bool d_doDNAME;
+ bool d_doExpandALIAS;
std::unique_ptr<AuthLua> d_pdl;
std::unique_ptr<AuthLua4> d_update_policy_lua;
static bool g_weDistributeQueries; // if true, only 1 thread listens on the incoming query sockets
static bool g_reusePort{false};
static bool g_useOneSocketPerThread;
+static bool g_gettagNeedsEDNSOptions{false};
std::unordered_set<DNSName> g_delegationOnly;
RecursorControlChannel s_rcc; // only active in thread 0
shared_ptr<TCPConnection> d_tcpConnection;
vector<pair<uint16_t, string> > d_ednsOpts;
std::vector<std::string> d_policyTags;
- std::unordered_map<string,string> d_data;
+ LuaContext::LuaObject d_data;
};
return t_id;
}
+int getMTaskerTID()
+{
+ return MT->getTid();
+}
+
static void handleTCPClientWritable(int fd, FDMultiplexer::funcparam_t& var);
// -1 is error, 0 is timeout, 1 is success
static void handleRPZCustom(const DNSRecord& spoofed, const QType& qtype, SyncRes& sr, int& res, vector<DNSRecord>& ret)
{
if (spoofed.d_type == QType::CNAME) {
- bool oldWantsRPZ = sr.d_wantsRPZ;
- sr.d_wantsRPZ = false;
+ bool oldWantsRPZ = sr.getWantsRPZ();
+ sr.setWantsRPZ(false);
vector<DNSRecord> ans;
res = sr.beginResolve(DNSName(spoofed.d_content->getZoneRepresentation()), qtype, 1, ans);
for (const auto& rec : ans) {
}
}
// Reset the RPZ state of the SyncRes
- sr.d_wantsRPZ = oldWantsRPZ;
+ sr.setWantsRPZ(oldWantsRPZ);
}
}
}
if(g_dnssecmode != DNSSECMode::Off) {
- sr.d_doDNSSEC=true;
+ sr.setDoDNSSEC(true);
// Does the requestor want DNSSEC records?
if(edo.d_Z & EDNSOpts::DNSSECOK) {
pw.getHeader()->cd=0;
}
#ifdef HAVE_PROTOBUF
- sr.d_initialRequestId = dc->d_uuid;
+ sr.setInitialRequestId(dc->d_uuid);
#endif
if (g_useIncomingECS) {
- sr.d_incomingECSFound = dc->d_ecsFound;
+ sr.setIncomingECSFound(dc->d_ecsFound);
if (dc->d_ecsFound) {
- sr.d_incomingECS = dc->d_ednssubnet;
+ sr.setIncomingECS(dc->d_ednssubnet);
}
}
// if there is a RecursorLua active, and it 'took' the query in preResolve, we don't launch beginResolve
if(!t_pdl->get() || !(*t_pdl)->preresolve(dq, res)) {
- sr.d_wantsRPZ = wantsRPZ;
+ sr.setWantsRPZ(wantsRPZ);
if(wantsRPZ) {
switch(appliedPolicy.d_kind) {
case DNSFilterEngine::PolicyKind::NoAction:
}
sr.d_outqueries ? t_RC->cacheMisses++ : t_RC->cacheHits++;
- float spent=makeFloat(sr.d_now-dc->d_now);
+ float spent=makeFloat(sr.getNow()-dc->d_now);
if(spent < 0.001)
g_stats.answers0_1++;
else if(spent < 0.010)
}
}
-static bool getQNameAndSubnet(const std::string& question, DNSName* dnsname, uint16_t* qtype, uint16_t* qclass, EDNSSubnetOpts* ednssubnet)
+static bool getQNameAndSubnet(const std::string& question, DNSName* dnsname, uint16_t* qtype, uint16_t* qclass, EDNSSubnetOpts* ednssubnet, std::map<uint16_t, EDNSOptionView>* options)
{
bool found = false;
const struct dnsheader* dh = (struct dnsheader*)question.c_str();
if(ntohs(dh->arcount) == 1 && questionLen > pos + 11) { // this code can extract one (1) EDNS Subnet option
/* OPT root label (1) followed by type (2) */
if(question.at(pos)==0 && question.at(pos+1)==0 && question.at(pos+2)==QType::OPT) {
- char* ecsStart = nullptr;
- size_t ecsLen = 0;
- int res = getEDNSOption((char*)question.c_str()+pos+9, questionLen - pos - 9, EDNSOptionCode::ECS, &ecsStart, &ecsLen);
- if (res == 0 && ecsLen > 4) {
- EDNSSubnetOpts eso;
- if(getEDNSSubnetOptsFromString(ecsStart + 4, ecsLen - 4, &eso)) {
- *ednssubnet=eso;
- found = true;
+ 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);
+ if (res == 0 && ecsLen > 4) {
+ EDNSSubnetOpts eso;
+ if(getEDNSSubnetOptsFromString(ecsStart + 4, ecsLen - 4, &eso)) {
+ *ednssubnet=eso;
+ found = true;
+ }
+ }
+ }
+ else {
+ int res = getEDNSOptions((char*)question.c_str()+pos+9, questionLen - pos - 9, *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;
+ }
+ }
}
}
}
if(needECS || (t_pdl->get() && (*t_pdl)->d_gettag)) {
try {
+ std::map<uint16_t, EDNSOptionView> ednsOptions;
dc->d_ecsParsed = true;
- dc->d_ecsFound = getQNameAndSubnet(std::string(conn->data, conn->qlen), &qname, &qtype, &qclass, &dc->d_ednssubnet);
+ dc->d_ecsFound = getQNameAndSubnet(std::string(conn->data, conn->qlen), &qname, &qtype, &qclass, &dc->d_ednssubnet, g_gettagNeedsEDNSOptions ? &ednsOptions : nullptr);
if(t_pdl->get() && (*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);
+ dc->d_tag = (*t_pdl)->gettag(conn->d_remote, dc->d_ednssubnet.source, dest, qname, qtype, &dc->d_policyTags, dc->d_data, ednsOptions);
}
catch(std::exception& e) {
if(g_logCommonErrors)
uint32_t qhash = 0;
bool needECS = false;
std::vector<std::string> policyTags;
- std::unordered_map<string,string> data;
+ LuaContext::LuaObject data;
#ifdef HAVE_PROTOBUF
boost::uuids::uuid uniqueId;
auto luaconfsLocal = g_luaconfs.getLocal();
if(needECS || (t_pdl->get() && (*t_pdl)->d_gettag)) {
try {
- ecsFound = getQNameAndSubnet(question, &qname, &qtype, &qclass, &ednssubnet);
+ std::map<uint16_t, EDNSOptionView> ednsOptions;
+ ecsFound = getQNameAndSubnet(question, &qname, &qtype, &qclass, &ednssubnet, g_gettagNeedsEDNSOptions ? &ednsOptions : nullptr);
qnameParsed = true;
ecsParsed = true;
if(t_pdl->get() && (*t_pdl)->d_gettag) {
try {
- ctag=(*t_pdl)->gettag(fromaddr, ednssubnet.source, destaddr, qname, qtype, &policyTags, data);
+ ctag=(*t_pdl)->gettag(fromaddr, ednssubnet.source, destaddr, qname, qtype, &policyTags, data, ednsOptions);
}
catch(std::exception& e) {
if(g_logCommonErrors)
}
#endif
+ if (::arg().asNum("tcp-fast-open") > 0) {
+#ifdef TCP_FASTOPEN
+ int fastOpenQueueSize = ::arg().asNum("tcp-fast-open");
+ if (setsockopt(fd, IPPROTO_TCP, TCP_FASTOPEN, &fastOpenQueueSize, sizeof fastOpenQueueSize) < 0) {
+ L<<Logger::Error<<"Failed to enable TCP Fast Open for listening socket: "<<strerror(errno)<<endl;
+ }
+#else
+ L<<Logger::Warning<<"TCP Fast Open configured but not supported for listening socket"<<endl;
+#endif
+ }
+
sin.sin4.sin_port = htons(st.port);
socklen_t socklen=sin.sin4.sin_family==AF_INET ? sizeof(sin.sin4) : sizeof(sin.sin6);
if (::bind(fd, (struct sockaddr *)&sin, socklen )<0)
t_RC->doPrune(); // this function is local to a thread, so fine anyhow
t_packetCache->doPruneTo(::arg().asNum("max-packetcache-entries") / g_numWorkerThreads);
- pruneCollection(t_sstorage->negcache, ::arg().asNum("max-cache-entries") / (g_numWorkerThreads * 10), 200);
+ t_sstorage->negcache.prune(::arg().asNum("max-cache-entries") / (g_numWorkerThreads * 10));
if(!((cleanCounter++)%40)) { // this is a full scan!
time_t limit=now.tv_sec-300;
}
if(now.tv_sec - last_rootupdate > 7200) {
- int res = getRootNS();
+ int res = SyncRes::getRootNS(g_now, nullptr);
if (!res)
last_rootupdate=now.tv_sec;
}
SyncRes::s_serverID=tmp;
}
+ SyncRes::s_ecsipv4limit = ::arg().asNum("ecs-ipv4-bits");
+ SyncRes::s_ecsipv6limit = ::arg().asNum("ecs-ipv6-bits");
+
g_networkTimeoutMsec = ::arg().asNum("network-timeout");
g_initialDomainMap = parseAuthAndForwards();
g_numThreads = g_numWorkerThreads + g_weDistributeQueries;
g_maxMThreads = ::arg().asNum("max-mthreads");
+ g_gettagNeedsEDNSOptions = ::arg().mustDo("gettag-needs-edns-options");
+
#ifdef SO_REUSEPORT
g_reusePort = ::arg().mustDo("reuseport");
#endif
if(g_useOneSocketPerThread) {
for (unsigned int threadId = 0; threadId < g_numWorkerThreads; threadId++) {
- for(deferredAdd_t::const_iterator i = deferredAdds[threadId].begin(); i != deferredAdds[threadId].end(); ++i) {
+ for(deferredAdd_t::const_iterator i = deferredAdds[threadId].cbegin(); i != deferredAdds[threadId].cend(); ++i) {
t_fdm->addReadFD(i->first, i->second);
}
}
}
else {
if(!g_weDistributeQueries || !t_id) { // if we distribute queries, only t_id = 0 listens
- for(deferredAdd_t::const_iterator i = deferredAdds[0].begin(); i != deferredAdds[0].end(); ++i) {
+ for(deferredAdd_t::const_iterator i = deferredAdds[0].cbegin(); i != deferredAdds[0].cend(); ++i) {
t_fdm->addReadFD(i->first, i->second);
}
}
::arg().setSwitch( "root-nx-trust", "If set, believe that an NXDOMAIN from the root means the TLD does not exist")="yes";
::arg().setSwitch( "any-to-tcp","Answer ANY queries with tc=1, shunting to TCP" )="no";
::arg().setSwitch( "lowercase-outgoing","Force outgoing questions to lowercase")="no";
+ ::arg().setSwitch("gettag-needs-edns-options", "If EDNS Options should be extracted before calling the gettag() hook")="no";
::arg().set("udp-truncation-threshold", "Maximum UDP response size before we truncate")="1680";
::arg().set("edns-outgoing-bufsize", "Outgoing EDNS buffer size")="1680";
::arg().set("minimum-ttl-override", "Set under adverse conditions, a minimum TTL")="0";
::arg().setSwitch("snmp-agent", "If set, register as an SNMP agent")="no";
::arg().setSwitch("snmp-master-socket", "If set and snmp-agent is set, the socket to use to register to the SNMP master")="";
+ ::arg().set("tcp-fast-open", "Enable TCP Fast Open support on the listening sockets, using the supplied numerical value as the queue size")="0";
+
::arg().setCmd("help","Provide a helpful message");
::arg().setCmd("version","Print version string");
::arg().setCmd("config","Output blank configuration");
return ret;
}
-
-int getRootNS(void) {
- SyncRes sr(g_now);
- sr.setDoEDNS0(true);
- sr.setNoCache();
- sr.d_doDNSSEC = (g_dnssecmode != DNSSECMode::Off);
-
- vector<DNSRecord> ret;
- int res=-1;
- try {
- res=sr.beginResolve(g_rootdnsname, QType(QType::NS), 1, ret);
- if (g_dnssecmode != DNSSECMode::Off && g_dnssecmode != DNSSECMode::ProcessNoValidate) {
- ResolveContext ctx;
- auto state = validateRecords(ctx, ret);
- if (state == Bogus)
- throw PDNSException("Got Bogus validation result for .|NS");
- }
- return res;
- }
- catch(PDNSException& e)
- {
- L<<Logger::Error<<"Failed to update . records, got an exception: "<<e.reason<<endl;
- }
-
- catch(std::exception& e)
- {
- L<<Logger::Error<<"Failed to update . records, got an exception: "<<e.what()<<endl;
- }
-
- catch(...)
- {
- L<<Logger::Error<<"Failed to update . records, got an exception"<<endl;
- }
- if(!res) {
- L<<Logger::Notice<<"Refreshed . records"<<endl;
- }
- else
- L<<Logger::Error<<"Failed to update . records, RCODE="<<res<<endl;
- return res;
-}
#include "dnsbackend.hh"
#include "ueberbackend.hh"
#include "arguments.hh"
-#include "packetcache.hh"
+#include "auth-packetcache.hh"
+#include "auth-querycache.hh"
#include "zoneparser-tng.hh"
#include "signingpipe.hh"
#include "dns_random.hh"
#endif
StatBag S;
-PacketCache PC;
+AuthPacketCache PC;
+AuthQueryCache QC;
namespace po = boost::program_options;
po::variables_map g_vm;
//cerr<<"Backend: "<<::arg()["launch"]<<", '" << ::arg()["gmysql-dbname"] <<"'" <<endl;
S.declare("qsize-q","Number of questions waiting for database attention");
-
- S.declare("deferred-cache-inserts","Amount of cache inserts that were deferred because of maintenance");
- S.declare("deferred-cache-lookup","Amount of cache lookups that were deferred because of maintenance");
- S.declare("query-cache-hit","Number of hits on the query cache");
- S.declare("query-cache-miss","Number of misses on the query cache");
::arg().set("max-cache-entries", "Maximum number of cache entries")="1000000";
::arg().set("cache-ttl","Seconds to store packets in the PacketCache")="20";
::arg().set("negquery-cache-ttl","Seconds to store negative query results in the QueryCache")="60";
}
}
- if (kindFilter != -1)
- cout<<type<<" zonecount: "<<count<<endl;
- else
- cout<<"All zonecount: "<<count<<endl;
+ if (g_verbose) {
+ if (kindFilter != -1)
+ cout<<type<<" zonecount: "<<count<<endl;
+ else
+ cout<<"All zonecount: "<<count<<endl;
+ }
+
return 0;
}
return 0;
}
-loadMainConfig(g_vm["config-dir"].as<string>());
+ loadMainConfig(g_vm["config-dir"].as<string>());
#ifdef HAVE_LIBSODIUM
if (sodium_init() == -1) {
#include "misc.hh"
#include "recursor_cache.hh"
#include "syncres.hh"
+#include "negcache.hh"
#include <boost/function.hpp>
#include <boost/optional.hpp>
#include <boost/tuple/tuple.hpp>
}
-static uint64_t dumpNegCache(SyncRes::negcache_t& negcache, int fd)
+static uint64_t dumpNegCache(NegCache& negcache, int fd)
{
FILE* fp=fdopen(dup(fd), "w");
if(!fp) { // dup probably failed
return 0;
}
+ uint64_t ret;
fprintf(fp, "; negcache dump from thread follows\n;\n");
- time_t now = time(0);
-
- typedef SyncRes::negcache_t::nth_index<1>::type sequence_t;
- sequence_t& sidx=negcache.get<1>();
-
- uint64_t count=0;
- for(const NegCacheEntry& neg : sidx)
- {
- ++count;
- fprintf(fp, "%s IN %s %d VIA %s\n", neg.d_name.toString().c_str(), neg.d_qtype.getName().c_str(), (unsigned int) (neg.d_ttd - now), neg.d_qname.toString().c_str());
- }
+ ret = negcache.dumpToFile(fp);
fclose(fp);
- return count;
+ return ret;
}
static uint64_t* pleaseDump(int fd)
uint64_t* pleaseWipeAndCountNegCache(const DNSName& canon, bool subtree)
{
- if(!subtree) {
- uint64_t res = t_sstorage->negcache.count(tie(canon));
- auto range=t_sstorage->negcache.equal_range(tie(canon));
- t_sstorage->negcache.erase(range.first, range.second);
- return new uint64_t(res);
- }
- else {
- unsigned int erased=0;
- for(auto iter = t_sstorage->negcache.lower_bound(tie(canon)); iter != t_sstorage->negcache.end(); ) {
- if(!iter->d_qname.isPartOf(canon))
- break;
- t_sstorage->negcache.erase(iter++);
- erased++;
- }
- return new uint64_t(erased);
- }
+ uint64_t ret = t_sstorage->negcache.wipe(canon, subtree);
+ return new uint64_t(ret);
}
+
template<typename T>
string doWipeCache(T begin, T end)
{
return qname==rname && rtype == qtype && rclass == qclass;
}
-uint32_t RecursorPacketCache::canHashPacket(const std::string& origPacket)
-{
- // return 42; // should still work if you do this!
- uint32_t ret=0;
- ret=burtle((const unsigned char*)origPacket.c_str() + 2, 10, ret); // rest of dnsheader, skip id
- const char* end = origPacket.c_str() + origPacket.size();
- const char* p = origPacket.c_str() + 12;
-
- for(; p < end && *p; ++p) { // XXX if you embed a 0 in your qname we'll stop lowercasing there
- const unsigned char l = dns_tolower(*p); // label lengths can safely be lower cased
- ret=burtle(&l, 1, ret);
- } // XXX the embedded 0 in the qname will break the subnet stripping
-
- struct dnsheader* dh = (struct dnsheader*)origPacket.c_str();
- const char* skipBegin = p;
- const char* skipEnd = p;
- /* we need at least 1 (final empty label) + 2 (QTYPE) + 2 (QCLASS)
- + OPT root label (1), type (2), class (2) and ttl (4)
- + the OPT RR rdlen (2)
- = 16
- */
- if(ntohs(dh->arcount)==1 && (p+16) < end) {
- char* optionBegin = nullptr;
- size_t optionLen = 0;
- /* skip the final empty label (1), the qtype (2), qclass (2) */
- /* root label (1), type (2), class (2) and ttl (4) */
- int res = getEDNSOption((char*) p + 14, end - (p + 14), EDNSOptionCode::ECS, &optionBegin, &optionLen);
- if (res == 0) {
- skipBegin = optionBegin;
- skipEnd = optionBegin + optionLen;
- }
- }
- if (skipBegin > p) {
- //cout << "Hashing from " << (p-origPacket.c_str()) << " for " << skipBegin-p << "bytes, end is at "<< end-origPacket.c_str() << endl;
- ret = burtle((const unsigned char*)p, skipBegin-p, ret);
- }
- if (skipEnd < end) {
- //cout << "Hashing from " << (skipEnd-origPacket.c_str()) << " for " << end-skipEnd << "bytes, end is at " << end-origPacket.c_str() << endl;
- ret = burtle((const unsigned char*) skipEnd, end-skipEnd, ret);
- }
- return ret;
-}
-
bool RecursorPacketCache::checkResponseMatches(std::pair<packetCache_t::index<HashTag>::type::iterator, packetCache_t::index<HashTag>::type::iterator> range, const std::string& queryPacket, const DNSName& qname, uint16_t qtype, uint16_t qclass, time_t now, std::string* responsePacket, uint32_t* age, RecProtoBufMessage* protobufMessage)
{
for(auto iter = range.first ; iter != range.second ; ++ iter) {
bool RecursorPacketCache::getResponsePacket(unsigned int tag, const std::string& queryPacket, const DNSName& qname, uint16_t qtype, uint16_t qclass, time_t now,
std::string* responsePacket, uint32_t* age, uint32_t* qhash, RecProtoBufMessage* protobufMessage)
{
- *qhash = canHashPacket(queryPacket);
+ *qhash = canHashPacket(queryPacket, true);
const auto& idx = d_packetCache.get<HashTag>();
auto range = idx.equal_range(tie(tag,*qhash));
bool RecursorPacketCache::getResponsePacket(unsigned int tag, const std::string& queryPacket, time_t now,
std::string* responsePacket, uint32_t* age, uint32_t* qhash, RecProtoBufMessage* protobufMessage)
{
- *qhash = canHashPacket(queryPacket);
+ *qhash = canHashPacket(queryPacket, true);
const auto& idx = d_packetCache.get<HashTag>();
auto range = idx.equal_range(tie(tag,*qhash));
auto iter = range.first;
for( ; iter != range.second ; ++iter) {
- if(iter->d_type != qtype || iter->d_class != qclass)
- continue;
- // this only happens on insert which is relatively rare and does not need to be super fast
- DNSName respname(iter->d_packet.c_str(), iter->d_packet.length(), sizeof(dnsheader), false, 0, 0, 0);
- if(qname != respname)
+ if(iter->d_type != qtype || iter->d_class != qclass || iter->d_name != qname)
continue;
+
moveCacheItemToBack(d_packetCache, iter);
iter->d_packet = responsePacket;
iter->d_ttd = now + ttl;
#include <boost/tuple/tuple_comparison.hpp>
#include <boost/multi_index/sequenced_index.hpp>
+#include "packetcache.hh"
+
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
you can use a query as a key too. But query and answer must compare as identical!
This precludes doing anything smart with EDNS directly from the packet */
-class RecursorPacketCache
+class RecursorPacketCache: public PacketCache
{
public:
RecursorPacketCache();
return d_ttd;
}
};
- uint32_t canHashPacket(const std::string& origPacket);
+
typedef multi_index_container<
Entry,
indexed_by <
/pdns-recursor.service
/pdns-recursor@.service
/lua.hpp
+/test-suite.log
+/testrunner.log
+/testrunner.trs
pdns_recursor_SOURCES = \
arguments.cc \
+ ascii.hh \
base32.cc base32.hh \
base64.cc base64.hh \
cachecleaner.hh \
mtasker.hh \
mtasker_context.cc mtasker_context.hh \
namespaces.hh \
+ negcache.hh negcache.cc \
nsecrecords.cc \
opensslsigners.cc opensslsigners.hh \
+ packetcache.hh \
pdns_recursor.cc \
pdnsexception.hh \
protobuf.cc protobuf.hh \
base32.cc \
base64.cc base64.hh \
dns.cc dns.hh \
+ dns_random.cc dns_random.hh \
dnslabeltext.cc \
dnsname.cc dnsname.hh \
dnsparser.hh dnsparser.cc \
- dns_random.cc dns_random.hh \
dnsrecords.cc \
dnssecinfra.cc \
dnswriter.cc dnswriter.hh \
+ ednscookies.cc ednscookies.hh \
+ ecs.cc \
ednsoptions.cc ednsoptions.hh \
ednssubnet.cc ednssubnet.hh \
+ filterpo.cc filterpo.hh \
gettime.cc gettime.hh \
gss_context.cc gss_context.hh \
iputils.cc iputils.hh \
logger.cc logger.hh \
misc.cc misc.hh \
+ negcache.hh negcache.cc \
namespaces.hh \
nsecrecords.cc \
pdnsexception.hh \
protobuf.cc protobuf.hh \
qtype.cc qtype.hh \
+ randomhelper.cc \
rcpgenerator.cc \
- recpacketcache.cc recpacketcache.hh \
rec-protobuf.cc rec-protobuf.hh \
+ recpacketcache.cc recpacketcache.hh \
+ recursor_cache.cc recursor_cache.hh \
responsestats.cc \
+ root-dnssec.hh \
sillyrecords.cc \
sholder.hh \
sstuff.hh \
+ syncres.cc syncres.hh \
test-arguments_cc.cc \
test-base32_cc.cc \
test-base64_cc.cc \
test-dnsname_cc.cc \
test-dnsparser_hh.cc \
test-dnsrecords_cc.cc \
+ test-ednsoptions_cc.cc \
test-iputils_hh.cc \
test-misc_hh.cc \
test-nmtree.cc \
+ test-negcache_cc.cc \
test-rcpgenerator_cc.cc \
test-recpacketcache_cc.cc \
+ test-syncres_cc.cc \
test-tsig.cc \
testrunner.cc \
unix_utility.cc \
+ validate.cc validate.hh \
+ validate-recursor.cc validate-recursor.hh \
zoneparser-tng.cc zoneparser-tng.hh
testrunner_LDFLAGS = \
--- /dev/null
+../ascii.hh
\ No newline at end of file
[AC_MSG_NOTICE([Lua/LuaJit: no])])
])
AC_MSG_NOTICE([OpenSSL ECDSA: $libcrypto_ecdsa])
+AS_IF([test "x$LIBSODIUM_LIBS" != "x"],
+ [AC_MSG_NOTICE([libsodium ed25519: yes])],
+ [AC_MSG_NOTICE([libsodium ed25519: no])]
+)
AS_IF([test "x$PROTOBUF_LIBS" != "x" -a x"$PROTOC" != "x"],
[AC_MSG_NOTICE([Protobuf: yes])],
[AC_MSG_NOTICE([Protobuf: no])]
--- /dev/null
+digraph {
+ graph [fontname = "monospace"];
+ node [fontname = "monospace"];
+ edge [fontname = "monospace"];
+
+ subgraph cluster_beginResolve {
+ label="SyncRes::beginResolve(const DNSName &qname, const QType &qtype, uint16_t qclass, vector<DNSRecord>&ret)\nreturns the RCODE\nret is filled with all relevant records";
+
+ beginResolve_doResolve [label="SyncRes::doResolve()", color=red];
+ beginResolve_doSpecialNamesResolve [label="SyncRes::doSpecialNamesResolve()", color=red]
+
+ "Is this an AXFR request?";
+ "Is this an AXFR request?" -> beginResolve_return_minus_1 [label=yes];
+ "Is this an AXFR request?" -> beginResolve_doSpecialNamesResolve [label=no];
+
+ beginResolve_doSpecialNamesResolve -> "Is the qlass IN?" [label="Was not a special name"];
+ beginResolve_doSpecialNamesResolve -> beginResolve_return_0 [label="Was handled!"];
+
+ "Is the qlass IN?" -> beginResolve_return_minus_1 [label=no];
+ "Is the qlass IN?" -> beginResolve_doResolve [label=yes];
+ beginResolve_doResolve -> beginResolve_return_doResolve;
+ beginResolve_return_doResolve [label="return result from doResolve", color=green];
+ beginResolve_return_0 [label="return 0", color=green];
+ beginResolve_return_minus_1 [label="return -1", color=green];
+ }
+
+ subgraph cluster_doResolve {
+ label="SyncRes::doResolve(const DNSName &qname, const QType &qtype, vector<DNSRecord>&ret, unsigned int depth, set<GetBestNSAnswer>& beenthere)";
+
+ doResolve_doOOBResolve [label="SyncRes::doOOBResolve()", color=red];
+ doResolve_doCNAMECacheCheck [label="SyncRes::doCNAMECacheCheck()", color=red];
+ doResolve_asyncresolveWrapper [label="SyncRes::asyncresolveWrapper()", color=red];
+ doResolve_doCacheCheck [label="SyncRes::doCacheCheck()", color=red];
+ doResolve_getBestNSNamesFromCache [label="SyncRes::getBestNSNamesFromCache()", color=red];
+ doResolve_doResolveAt [label="SyncRes::doResolveAt()", color=red];
+
+ doResolve_return_res [label="return res", color=green];
+ doResolve_return_servfail [label="return SERVFAIL", color=green];
+
+ "SERVFAIL if too deep" -> "Check if called from getRootNS()";
+ "Check if called from getRootNS()" -> "Check if RD-bit was not set (d_cacheonly)" [label=yes];
+ "Check if called from getRootNS()" -> doResolve_getBestNSNamesFromCache [label=no];
+ "Check if RD-bit was not set (d_cacheonly)" -> doResolve_doCNAMECacheCheck [label=no];
+ "Check if RD-bit was not set (d_cacheonly)" -> "Check if there is a forward or auth-zone" [label=yes];
+ "Check if there is a forward or auth-zone" -> doResolve_doCNAMECacheCheck [label=no];
+ "Check if there is a forward or auth-zone" -> "Check if we are auth" [label=yes];
+ "Check if we are auth" -> doResolve_asyncresolveWrapper [label="no, so forward"];
+ "Check if we are auth" -> doResolve_doOOBResolve [label=yes];
+ doResolve_doOOBResolve -> "return res from doOOBResolve()";
+ "return res from doOOBResolve()" [color=green];
+ doResolve_asyncresolveWrapper -> "return result from asyncresolveWrapper()";
+ "return result from asyncresolveWrapper()" [color=green];
+
+ doResolve_doCNAMECacheCheck -> doResolve_doCacheCheck [label="returned false"];
+ doResolve_doCNAMECacheCheck -> doResolve_return_res [label="returned true"];
+
+ doResolve_doCacheCheck -> doResolve_getBestNSNamesFromCache [label="returned false"];
+ doResolve_doCacheCheck -> doResolve_return_res [label="returned true"];
+
+ doResolve_getBestNSNamesFromCache -> doResolve_doResolveAt;
+ doResolve_doResolveAt -> doResolve_return_res [label="res == -2"];
+ doResolve_doResolveAt -> doResolve_return_servfail [label="res < 0 &&\nres != -2"];
+ doResolve_doResolveAt -> doResolve_return_res [label="res >= 0"];
+ }
+
+ subgraph cluster_doCacheCheck {
+ label="SyncRes::doCacheCheck(const DNSName &qname, const QType &qtype, vector<DNSRecord>&ret, unsigned int depth, int &res)";
+
+ doCacheCheck_return_false [label="return false", color=green];
+ "Did we have a positive or negative answer?" -> doCacheCheck_return_true [label=yes];
+ "Did we have a positive or negative answer?" -> doCacheCheck_return_false [label=no];
+ doCacheCheck_return_true [label="return true", color=green];
+
+ "Allocate return qname, qtype and qttl vars (RVARS) as qname, qtype" -> "Is the last label of qname negatively cached by root and is root-nx-trust enabled?";
+
+ "Is the last label of qname negatively cached by root and is root-nx-trust enabled?" -> "Set res to NXDOMAIN, set RVARS to to this label|SOA" [label=yes];
+ "Is the last label of qname negatively cached by root and is root-nx-trust enabled?" -> "Do we have a negative entry from an auth?" [label=no];
+ "Do we have a negative entry from an auth?" -> "Set RCODE (NOERROR or NXDOMAIN), set RVARS to (smaller) qname|SOA" [label=yes];
+ "Set RCODE (NOERROR or NXDOMAIN), set RVARS to (smaller) qname|SOA" -> "Add DNSSEC proof from cache";
+ "Set res to NXDOMAIN, set RVARS to to this label|SOA" -> "Do we have a positive for RVARS from an auth?";
+
+ "Do we have a negative entry from an auth?" -> "Do we have a positive for RVARS from an auth?" [label=no];
+
+ "Do we have a positive for RVARS from an auth?" -> "Did we have a positive or negative answer?" [label=no];
+ "Add DNSSEC proof from cache" -> "Do we have a positive for RVARS from an auth?";
+
+ "Do we have a positive for RVARS from an auth?" -> "Add positive answers to ret if not expired" [label=yes];
+ "Add positive answers to ret if not expired" -> "Add DNSSEC data to ret";
+ "Add DNSSEC data to ret" -> "Did we have a positive or negative answer?";
+ }
+
+ subgraph cluster_getBestNSFromCache {
+ label="SyncRes::getBestNSFromCache(const DNSName &qname, const QType& qtype, vector<DNSRecord>& bestns, bool* flawedNSSet, unsigned int depth, set<GetBestNSAnswer>& beenthere)\nFills the bestns vector with the 'closest' nameservers for qname\nflawedNSSet will be true if the NSSet has no glue.\nbeenthere contains the list of nameservers already visited during this recursion.";
+
+ getBestNSFromCache_return [label="return", color=green];
+
+ getBestNSFromCache_chopoff_continue -> "Get NS-records for domain from cache" -> "Get one record from the records" -> "Has the TTL expired?";
+ "Get one record from the records" -> "Are there records in bestns?" [label="Checked all records"];
+
+ "Has the TTL expired?" -> "Get one record from the records" [label=yes];
+ "Has the TTL expired?" -> "Is the NS RDATA part of the domain &&\nDo we have A and/or AAAA records for it?" [label=no];
+ "Is the NS RDATA part of the domain &&\nDo we have A and/or AAAA records for it?" -> "Set flawednsset=true" [label=no];
+
+ "Is the NS RDATA part of the domain &&\nDo we have A and/or AAAA records for it?" -> "Add the NS-record to bestns" [label=yes];
+ "Add the NS-record to bestns" -> "Get one record from the records";
+
+ "Set flawednsset=true" -> "Get one record from the records";
+
+ "Are there records in bestns?" -> getBestNSFromCache_chopoff_continue [label=no];
+ "Are there records in bestns?" -> "Is any of the NS records in bestns in beenthere?" [label=yes];
+
+
+ "Is any of the NS records in bestns in beenthere?" -> "Add these records to beenthere" [label=no];
+ "Add these records to beenthere" -> getBestNSFromCache_return;
+
+ "Is any of the NS records in bestns in beenthere?" -> "Clear bestns" [label=yes];
+ "Clear bestns" -> "Was this the root domain?";
+ "Was this the root domain?" -> getBestNSFromCache_chopoff_continue [label=no];
+ "Was this the root domain?" -> "Re-prime the root" [label=yes];
+ "Re-prime the root" -> getBestNSFromCache_return;
+ getBestNSFromCache_chopoff_continue [label="chopoff left-most label"];
+
+ {rank=sink; getBestNSFromCache_chopoff_continue; getBestNSFromCache_return}
+ }
+
+ subgraph cluster_doOOBResolve {
+ label="SyncRes::doOOBResolve(const DNSName &qname, const QType &qtype, vector<DNSRecord>&ret, unsigned int depth, int& res)\nReturns true if data came from local auth-store.\nvector<DNSRecord> ret is filled with answers";
+
+ doOOBResolve_getBestAuthZone [label="SyncRes::getBestAuthZone()", color=red];
+ doOOBResolve_return_false [label="return false", color=green];
+ doOOBResolve_return_true [label="return true", color=green];
+
+ doOOBResolve_getBestAuthZone -> doOOBResolve_return_false [label="returned iterator to end of authstorage"];
+ doOOBResolve_getBestAuthZone -> "Add auth-records matching qname+qtype || CNAME || NS to ret" [label="returned iterator in the authstorage"];
+ "Add auth-records matching qname+qtype || CNAME || NS to ret" -> doOOBResolve_return_true [label="records were added to ret"]
+ "Add auth-records matching qname+qtype || CNAME || NS to ret" -> "Did we have any data for the qname?" [label="no records were added to ret"];
+
+ "Did we have any data for the qname?" -> "Add SOA to AUTHORITY in ret" [label="yes (empty NOERROR)"];
+ "Add SOA to AUTHORITY in ret" -> "Set res to NOERROR" -> doOOBResolve_return_true;
+
+ "Did we have any data for the qname?" -> "Is there a wildcard match?" [label=no];
+ "Is there a wildcard match?" -> "Add auth-records from wildcard to ret" [label=yes];
+ "Add auth-records from wildcard to ret" -> "Set res to NOERROR";
+
+ "Is there a wildcard match?" -> "Add NS-records from auth-zone" [label=no];
+
+ "Add NS-records from auth-zone" -> "Set res to NOERROR" [label="NS record were added"];
+ "Add NS-records from auth-zone" -> "Try to add SOA" [label="No NS record were added"];
+
+ "Try to add SOA" -> "Set res to NXDOMAIN" -> doOOBResolve_return_true;
+ }
+
+ subgraph cluster_asyncresolveWrapper {
+ label="SyncRes::asyncresolveWrapper(const ComboAddress& ip, bool ednsMANDATORY, const DNSName& domain, int type, bool doTCP, bool sendRDQuery, struct timeval* now, boost::optional<Netmask>& srcmask, LWResult* res\nWraps asyncresolve() from lwres.cc to do EDNS probing.";
+
+ {rank=min; "Get current EDNSStatus for ip"}
+
+ asyncresolveWrapper_asyncresolve [label="asyncresolve()", color=red];
+ asyncresolveWrapper_return_minus_1 [label="return -1 (transport error)", color=green];
+ asyncresolveWrapper_return_minus_2 [label="return -2 (OS limits error)", color=green];
+ asyncresolveWrapper_return_0 [label="return 0 (timeout)", color=green];
+ asyncresolveWrapper_return_1 [label="return 1 (success)", color=green];
+ asyncresolveWrapper_setEDNSLevel_0 [label="Set EDNSLevel=0"]
+ asyncresolveWrapper_setEDNSLevel_0 -> "Is EDNSStatus NOEDNS?";
+
+ "Get current EDNSStatus for ip" -> "Is the current EDNSStatus older than an hour?";
+ "Is the current EDNSStatus older than an hour?" -> "Set EDNSStatus to Unknown" [label=yes];
+ "Set EDNSStatus to Unknown" -> asyncresolveWrapper_setEDNSLevel_0;
+ "Is the current EDNSStatus older than an hour?" -> asyncresolveWrapper_setEDNSLevel_0 [label=no];
+
+ "Is EDNSStatus NOEDNS?" -> "Set EDNSLevel=0" [label=yes]
+ "Set EDNSLevel=0" -> asyncresolveWrapper_asyncresolve;
+
+ "Is EDNSStatus NOEDNS?" -> "Is EDNSStatus UNKNOWN, EDNSOK, EDSIGNORANT or is EDNS mandatory?" [label=no]
+ "Is EDNSStatus UNKNOWN, EDNSOK, EDSIGNORANT or is EDNS mandatory?" -> "Set EDNSLevel=1" [label=yes]
+ "Set EDNSLevel=1" -> asyncresolveWrapper_asyncresolve;
+ "Is EDNSStatus UNKNOWN, EDNSOK, EDSIGNORANT or is EDNS mandatory?" -> asyncresolveWrapper_asyncresolve [label=no];
+
+ asyncresolveWrapper_asyncresolve -> asyncresolveWrapper_return_minus_1 [label="transport error"];
+ asyncresolveWrapper_asyncresolve -> asyncresolveWrapper_return_minus_2 [label="OS limits error"];
+ asyncresolveWrapper_asyncresolve -> asyncresolveWrapper_return_0 [label="timeout error"];
+ asyncresolveWrapper_asyncresolve -> "Is the EDNSStatus UNKNOWN||EDNSOK||EDNSIGNORANT?" [label="resolve OK!"];
+
+ "Is the EDNSStatus UNKNOWN||EDNSOK||EDNSIGNORANT?" -> "Was the RCODE FORMERR||NOTIMP?" [label=yes];
+ "Was the RCODE FORMERR||NOTIMP?" -> "set EDNSStatus to NOEDNS" [label=yes];
+ "set EDNSStatus to NOEDNS" -> "Is EDNSStatus NOEDNS?";
+
+ "Was the RCODE FORMERR||NOTIMP?" -> "Did the remote server respond with EDNS?" [label=no];
+ "Did the remote server respond with EDNS?" -> "Set EDNSStatus to EDNSOK" [label=yes];
+ "Set EDNSStatus to EDNSOK" -> "Is the original EDNSStatus different from the new?";
+
+ "Did the remote server respond with EDNS?" -> "Set EDNSStatus to EDNSIGNORANT" [label=no];
+ "Set EDNSStatus to EDNSIGNORANT" -> "Is the original EDNSStatus different from the new?";
+
+ "Is the EDNSStatus UNKNOWN||EDNSOK||EDNSIGNORANT?" -> "Is the original EDNSStatus different from the new?" [label=no];
+ "Is the original EDNSStatus different from the new?" -> "Save new EDNSStatus" [label=yes];
+ "Is the original EDNSStatus different from the new?" -> asyncresolveWrapper_return_1 [label=no];
+ "Save new EDNSStatus" -> asyncresolveWrapper_return_1;
+ }
+
+ subgraph cluster_doResolveAt {
+ label="SyncRes::doResolveAt(NsSet &nameservers, DNSName auth, bool flawedNSSet, const DNSName &qname, const QType &qtype, vector<DNSRecord>&ret, unsigned int depth, set<GetBestNSAnswer>&beenthere)\nreturns -1 in case of no results, -2 when a FilterEngine Policy was hit, rcode otherwise";
+
+ doResolveAt_nameServersBlockedByRPZ [label="SyncRes::nameserversBlockedByRPZ()", color=red];
+ doResolveAt_doOOBResolve_for_NS [label="SyncRes::doOOBResolve()", color=red];
+ doResolveAt_retrieveAddressesForNS [label="SyncRes::retrieveAddressesForNS()", color=red];
+ doResolveAt_nameserverIPBlockedByRPZ [label="SyncRes::nameserverIPBlockedByRPZ()", color=red];
+ doResolveAt_Lua_preoutquery [label="Lua preoutquery", color=red];
+ doResolveAt_asyncresolveWrapper [label="SyncRes::asyncresolveWrapper()", color=red];
+ doResolveAt_processRecords [label="SyncRes::processRecords()", color=red];
+ doResolveAt_doResolve [label="SyncRes::doResolve()", color=red];
+
+ doResolveAt_return_minus_2 [label="return -2", color=green];
+ doResolveAt_return_minus_1 [label="return -1", color=green];
+ doResolveAt_return_0 [label="return 0", color=green];
+ doResolveAt_return_rcode [label="return rcode", color=green];
+ doResolveAt_return_servfail [label="return SERVFAIL", color=green];
+ doResolveAt_return_nxdomain [label="return NXDOMAIN", color=green];
+
+ doResolveAt_mainloop_continue [label="continue"];
+ doResolveAt_mainloop_continue -> "Get nameserver from nameservers";
+
+ doResolveAt_nsiploop_continue [label="continue"];
+ doResolveAt_nsiploop_continue -> "Get IP from IPs";
+
+ doResolveAt_nameServersBlockedByRPZ -> doResolveAt_return_minus_2 [label="RPZ NSDNAME hit"];
+ doResolveAt_nameServersBlockedByRPZ -> "Get nameserver from nameservers" [lhead=cluster_doResolveAt_mainloop, label="RPZ NSDNAME not hit"];
+
+ doResolveAt_ImmediateServFailException [label="throw ImmediateServFailException", color=green];
+
+ "Get nameserver from nameservers" -> doResolveAt_mainloop_continue [label="qname == nsname"];
+ "Get nameserver from nameservers" -> doResolveAt_return_minus_1 [label="All nameservers tried"];
+ "Get nameserver from nameservers" -> "Is the nameserver out of band?";
+ "Is the nameserver out of band?" -> doResolveAt_doOOBResolve_for_NS [label=yes];
+ doResolveAt_doOOBResolve_for_NS -> "updateCacheFromRecords()";
+ "Is the nameserver out of band?" -> doResolveAt_retrieveAddressesForNS [label=no];
+ doResolveAt_retrieveAddressesForNS -> doResolveAt_mainloop_continue [label="No IPs returned"];
+ doResolveAt_retrieveAddressesForNS -> doResolveAt_nameserverIPBlockedByRPZ [label="IPs returned"];
+ doResolveAt_nameserverIPBlockedByRPZ -> doResolveAt_return_minus_2 [label="RPZ NSIP hit"];
+ doResolveAt_nameserverIPBlockedByRPZ -> "Get IP from IPs" [label="RPZ NSIP not hit"];
+
+ "Get IP from IPs" -> doResolveAt_nsiploop_continue [label="IP is throttled"];
+ "Get IP from IPs" -> doResolveAt_ImmediateServFailException [label="Too many queries sent while resolving"];
+ "Get IP from IPs" -> doResolveAt_ImmediateServFailException [label="Resolving took too long"];
+ "Get IP from IPs" -> doResolveAt_mainloop_continue [label="No IP address worked"];
+ "Get IP from IPs" -> doResolveAt_Lua_preoutquery;
+
+ doResolveAt_Lua_preoutquery -> "Check resolveret" [label="true"];
+ doResolveAt_Lua_preoutquery -> doResolveAt_getEDNSSubnetMask [label="false"];
+ doResolveAt_getEDNSSubnetMask -> doResolveAt_asyncresolveWrapper;
+ doResolveAt_asyncresolveWrapper -> "Check resolveret";
+ "Check resolveret" -> doResolveAt_ImmediateServFailException [label="resolveret == -3\n(kill query)"];
+ "Check resolveret" -> "resolveret == 1";
+ "resolveret == 1" -> doResolveAt_nsiploop_continue [label="nameserver returned\nSERVFAIL || REFUSED"];
+ "resolveret == 1" -> "updateCacheFromRecords()";
+ "updateCacheFromRecords()" -> doResolveAt_return_rcode [label="rcode != NOERROR"]; // line 1473
+ "updateCacheFromRecords()" -> doResolveAt_processRecords; // line 1484
+ doResolveAt_processRecords -> doResolveAt_return_0 [label="done == true"];
+ doResolveAt_processRecords -> "Have newtarget?";
+
+ "Have newtarget?" -> "qname == newtarget || depth > 10?" [label=yes];
+ "qname == newtarget || depth > 10?" -> doResolveAt_return_servfail [label=yes];
+ "qname == newtarget || depth > 10?" -> doResolveAt_doResolve [label=no];
+ doResolveAt_doResolve -> doResolveAt_return_rcode;
+
+ "Have newtarget?" -> "Have NXDOMAIN from upstream?" [label=no];
+ "Have NXDOMAIN from upstream?" -> "Add NSECs if needed" [label=yes];
+ "Add NSECs if needed" -> doResolveAt_return_nxdomain;
+
+ "Have NXDOMAIN from upstream?" -> "Have empty NOERROR?" [label=no];
+ "Have empty NOERROR?" -> "Add NSEC records if needed" [label=yes];
+ "Add NSEC records if needed" -> doResolveAt_return_0;
+
+ "Have empty NOERROR?" -> "Have realreferral?" [label=no];
+ "Have realreferral?" -> "Was a server in nsset blocker by RPZNSDNAME?" [label=yes];
+ "Was a server in nsset blocker by RPZNSDNAME?" -> doResolveAt_return_minus_2 [label=yes];
+ "Was a server in nsset blocker by RPZNSDNAME?" -> "Get nameserver from nameservers" [label=no];
+
+ "Have realreferral?" -> "Was this an OOB nameserver?" [label=no];
+ "Was this an OOB nameserver?" -> "Get nameserver from nameservers" [label="no, nameserver was lame"];
+ "Was this an OOB nameserver?" -> "Get nameserver from nameservers" [label=yes];
+ }
+
+ subgraph cluster_processRecords {
+ label="SyncRes::processRecords(const std::string& prefix, const DNSName& qname, const QType& qtype, const DNSName& auth, LWResult& lwr, const bool sendRDQuery, vector<DNSRecord>& ret, set<DNSName>& nsset, DNSName& newtarget, DNSName& newauth, bool& realreferral, bool& negindic, bool& sawDS)\nreturns true is this level of recursion is done";
+
+// { rank=same; "Get record from lwr.d_records" processRecords_return_done}
+
+// { rank=same; "Is this a proper CNAME referral?" "Is this a DNSSEC record in the ANSWER section?" "Is this the actual answer?" "Is this an NS record in the AUTHORITY?" "Is this a DS in the AUTHORITY?" "Is this a proper NXDOMAIN?" "Are we not done && is this a NOERROR && is this a SOA in the AUTHORITY?"}
+
+ "Get record from lwr.d_records";
+ "Get record from lwr.d_records" -> "Is this a proper NXDOMAIN?"; // line 1177
+ "Get record from lwr.d_records" -> processRecords_return_done [label="All records checked"];
+ "Get record from lwr.d_records" -> "Get record from lwr.d_records" [label="type != OPT &&\nclass != IN"];
+
+ "Is this a proper NXDOMAIN?" -> "is newtarget empty?" [label=yes]; // note, we have a CNAME chasing bug here issue #679
+ "is newtarget empty?" -> processRecords_wasVariable [label=no];
+ "is newtarget empty?" -> "Add this SOA to ret" [label=yes];
+ processRecords_wasVariable [label="SyncRes::wasVariable", color=red]
+ "Add this SOA to ret" -> processRecords_wasVariable;
+ processRecords_wasVariable -> "Set negindic to true" [label="was indeed variable"];
+ processRecords_wasVariable -> "Add to negative cache" [label="was not variable"];
+ "Add to negative cache" -> "If s_rootNXTrust && auth.isRoot()";
+ "If s_rootNXTrust && auth.isRoot()" -> "Set negindic to true" [label=no];
+ "If s_rootNXTrust && auth.isRoot()" -> "Add tld label to negative cache" [label=yes];
+ "Add tld label to negative cache" -> "Set negindic to true";
+ "Set negindic to true" -> "Get record from lwr.d_records";
+
+ "Is this a proper NXDOMAIN?" -> "Is this a proper CNAME referral?" [label=no];
+ "Is this a proper CNAME referral?" -> "Add CNAME record to ret" [label=yes];
+ "Add CNAME record to ret" -> "Set newtarget to this CNAME" -> "Get record from lwr.d_records";
+
+ "Is this a proper CNAME referral?" -> "Is this a DNSSEC record in the ANSWER section?" [label=no];
+ "Is this a DNSSEC record in the ANSWER section?" -> "Is the record.qtype not RRSIG and is the record's qname the qname we want?"[label=yes];
+ "Is the record.qtype not RRSIG and is the record's qname the qname we want?" -> "Add this record to ret" [label=yes];
+ "Add this record to ret" -> "Get record from lwr.d_records";
+ "Is the record.qtype not RRSIG and is the record's qname the qname we want?" -> "Get record from lwr.d_records" [label=no];
+
+ "Is this a DNSSEC record in the ANSWER section?" -> "Is this the actual answer?" [label=no];
+ "Is this the actual answer?" -> "Set done=true" [label=yes];
+ "Set done=true" -> "Add answer record to ret" -> "Get record from lwr.d_records";
+
+ "Is this the actual answer?" -> "Is this an NS record in the AUTHORITY?" [label=no];
+ "Is this an NS record in the AUTHORITY?" -> "Is record.d_name more specific than the our current auth?" [label=yes];
+ "Is record.d_name more specific than the our current auth?" -> "set newauth to record.d_name" [label=yes];
+ "set newauth to record.d_name" -> "set realreferral=true" -> "add NS record to ret";
+ "Is record.d_name more specific than the our current auth?" -> "add NS record to ret" [label=no];
+ "add NS record to ret" -> "Get record from lwr.d_records";
+
+ "Is this an NS record in the AUTHORITY?" -> "Is this a DS in the AUTHORITY?" [label=no];
+ "Is this a DS in the AUTHORITY?" -> "set sawDS=true" [label=yes];
+ "set sawDS=true" -> "Get record from lwr.d_records";
+
+ "Is this a DS in the AUTHORITY?" -> "Are we not done && is this a NOERROR && is this a SOA in the AUTHORITY?" [label=no];
+ "Are we not done && is this a NOERROR && is this a SOA in the AUTHORITY?" -> "is newtarget empty?" [label=yes];
+ "is newtarget empty?" -> "Harvest DNSSEC data and add to negative cache" [label=yes];
+ "is newtarget empty?" -> "Get record from lwr.d_records";
+ "Harvest DNSSEC data and add to negative cache" -> "Set negindic to true" -> "Get record from lwr.d_records";
+
+ "Are we not done && is this a NOERROR && is this a SOA in the AUTHORITY?" -> "Get record from lwr.d_records" [label=no];
+
+ processRecords_return_done [label="return done", color=green];
+ }
+
+ subgraph cluster_doCNAMECacheCheck {
+ label="SyncRes::doCNAMECacheCheck(const DNSName &qname, const QType &qtype, vector<DNSRecord>& ret, unsigned int depth, int &res)\nreturns true if this function handled the query";
+
+ doCNAMECacheCheck_return_true [label="return true", color=green];
+ doCNAMECacheCheck_return_false [label="return false", color=green];
+
+ doCNAMECacheCheck_servfail [label="Set res to SERVFAIL"];
+ doCNAMECacheCheck_servfail -> doCNAMECacheCheck_return_true;
+
+ doCNAMECacheCheck_doResolve [label="SyncRes::doResolve", color=red];
+
+ "Too deep recursion or CNAMEs?" -> doCNAMECacheCheck_servfail [label=yes];
+ "Too deep recursion or CNAMEs?" -> "Get cache entries for qname|CNAME" [label=no];
+ "Get cache entries for qname|CNAME" -> "get a record from the cache entries" -> "Is the TTL not expired?";
+ "Is the TTL not expired?" -> "get a record from the cache entries" [label=yes];
+ "Is the TTL not expired?" -> "Add record and RRSIGS to ret" [label=no];
+ "Add record and RRSIGS to ret" -> "is qtype CNAME?";
+ "is qtype CNAME?" -> doCNAMECacheCheck_doResolve [label=no];
+ doCNAMECacheCheck_doResolve -> "Set res to the result of doResolve" -> doCNAMECacheCheck_return_true;
+ "is qtype CNAME?" -> "Set res=0" [label=yes];
+ "Set res=0" -> doCNAMECacheCheck_return_true;
+ "Get cache entries for qname|CNAME" -> doCNAMECacheCheck_return_false [label="No cache entries"];
+ }
+}
SuffixMatchNode g_ednsdomains;
bool g_useIncomingECS;
-boost::optional<Netmask> getEDNSSubnetMask(const ComboAddress& local, const DNSName&dn, const ComboAddress& rem, boost::optional<const EDNSSubnetOpts&> incomingECS)
-{
- static uint8_t l_ipv4limit, l_ipv6limit;
- if(!l_ipv4limit) {
- l_ipv4limit = ::arg().asNum("ecs-ipv4-bits");
- l_ipv6limit = ::arg().asNum("ecs-ipv6-bits");
- }
- boost::optional<Netmask> result;
- ComboAddress trunc;
- uint8_t bits;
- if(incomingECS) {
- if (incomingECS->source.getBits() == 0) {
- /* RFC7871 says we MUST NOT send any ECS if the source scope is 0 */
- return result;
- }
- trunc = incomingECS->source.getMaskedNetwork();
- bits = incomingECS->source.getBits();
- }
- else if(!local.isIPv4() || local.sin4.sin_addr.s_addr) { // detect unset 'requestor'
- trunc = local;
- bits = local.isIPv4() ? 32 : 128;
- }
- else {
- /* nothing usable */
- return result;
- }
-
- if(g_ednsdomains.check(dn) || g_ednssubnets.match(rem)) {
- bits = std::min(bits, (trunc.isIPv4() ? l_ipv4limit : l_ipv6limit));
- trunc.truncate(bits);
- return boost::optional<Netmask>(Netmask(trunc, bits));
- }
- return result;
-}
-
void parseEDNSSubnetWhitelist(const std::string& wlist)
{
vector<string> parts;
--- /dev/null
+../ednscookies.cc
\ No newline at end of file
--- /dev/null
+../ednscookies.hh
\ No newline at end of file
--- /dev/null
+/*
+ * 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 "negcache.hh"
+#include "misc.hh"
+#include "cachecleaner.hh"
+
+/*!
+ * Set ne to the NegCacheEntry for the last label in qname and return true if there
+ * was one.
+ *
+ * \param qname The name to look up (only the last label is used)
+ * \param now A timeval with the current time, to check if an entry is expired
+ * \param ne A NegCacheEntry that is filled when there is a cache entry
+ * \return true if ne was filled out, false otherwise
+ */
+bool NegCache::getRootNXTrust(const DNSName& qname, const struct timeval& now, NegCacheEntry& ne) {
+ // Never deny the root.
+ if (qname.isRoot())
+ return false;
+
+ // An 'ENT' QType entry, used as "whole name" in the neg-cache context.
+ static const QType qtnull(0);
+ DNSName lastLabel = qname.getLastLabel();
+ negcache_t::const_iterator ni = d_negcache.find(tie(lastLabel, qtnull));
+
+ while (ni != d_negcache.end() &&
+ ni->d_name == lastLabel &&
+ ni->d_auth.isRoot() &&
+ ni->d_qtype == qtnull) {
+ // We have something
+ if ((uint32_t)now.tv_sec < ni->d_ttd) {
+ ne = *ni;
+ moveCacheItemToBack(d_negcache, ni);
+ return true;
+ }
+ moveCacheItemToFront(d_negcache, ni);
+ ni++;
+ }
+ return false;
+}
+
+/*!
+ * Set ne to the NegCacheEntry for the qname|qtype tuple and return true
+ *
+ * \param qname The name to look up
+ * \param qtype The qtype to look up
+ * \param now A timeval with the current time, to check if an entry is expired
+ * \param ne A NegCacheEntry that is filled when there is a cache entry
+ * \return true if ne was filled out, false otherwise
+ */
+bool NegCache::get(const DNSName& qname, const QType& qtype, const struct timeval& now, NegCacheEntry& ne) {
+ auto range = d_negcache.equal_range(tie(qname));
+ negcache_t::iterator ni = range.first;
+
+ while (ni != range.second) {
+ // We have an entry
+ if (ni->d_qtype.getCode() == 0 || ni->d_qtype == qtype) {
+ // We match the QType or the whole name is denied
+ if((uint32_t) now.tv_sec < ni->d_ttd) {
+ // Not expired
+ ne = *ni;
+ moveCacheItemToBack(d_negcache, ni);
+ return true;
+ }
+ // expired
+ moveCacheItemToFront(d_negcache, ni);
+ }
+ ni++;
+ }
+ return false;
+}
+
+/*!
+ * Places ne into the negative cache, possibly overriding an existing entry.
+ *
+ * \param ne The NegCacheEntry to add to the cache
+ */
+void NegCache::add(const NegCacheEntry& ne) {
+ replacing_insert(d_negcache, ne);
+}
+
+/*!
+ * Returns the amount of entries in the cache
+ *
+ * \param qname The name of the entries to be counted
+ */
+uint64_t NegCache::count(const DNSName& qname) const {
+ return d_negcache.count(tie(qname));
+}
+
+/*!
+ * Returns the amount of entries in the cache for qname+qtype
+ *
+ * \param qname The name of the entries to be counted
+ * \param qtype The type of the entries to be counted
+ */
+uint64_t NegCache::count(const DNSName& qname, const QType qtype) const {
+ return d_negcache.count(tie(qname, qtype));
+}
+
+/*!
+ * Remove all entries for name from the cache. If subtree is true, wipe all names
+ * underneath it.
+ *
+ * \param name The DNSName of the entries to wipe
+ * \param subtree Should all entries under name be removed?
+ */
+uint64_t NegCache::wipe(const DNSName& name, bool subtree) {
+ uint64_t ret(0);
+ if (subtree) {
+ for (auto i = d_negcache.lower_bound(tie(name)); i != d_negcache.end();) {
+ if(!i->d_name.isPartOf(name))
+ break;
+ i = d_negcache.erase(i);
+ ret++;
+ }
+ return ret;
+ }
+
+ ret = count(name);
+ auto range = d_negcache.equal_range(tie(name));
+ d_negcache.erase(range.first, range.second);
+ return ret;
+}
+
+/*!
+ * Clear the negative cache
+ */
+void NegCache::clear() {
+ d_negcache.clear();
+}
+
+/*!
+ * Perform some cleanup in the cache, removing stale entries
+ *
+ * \param maxEntries The maximum number of entries that may exist in the cache.
+ */
+void NegCache::prune(unsigned int maxEntries) {
+ pruneCollection(d_negcache, maxEntries, 200);
+}
+
+/*!
+ * Writes the whole negative cache to fp
+ *
+ * \param fp A pointer to an open FILE object
+ */
+uint64_t NegCache::dumpToFile(FILE* fp) {
+ uint64_t ret(0);
+ time_t now = time(0);
+ negcache_sequence_t& sidx = d_negcache.get<1>();
+ for(const NegCacheEntry& ne : sidx) {
+ ret++;
+ fprintf(fp, "%s %d IN %s VIA %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());
+ }
+ return ret;
+}
--- /dev/null
+/*
+ * 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 <boost/multi_index_container.hpp>
+#include "dnsparser.hh"
+#include "dnsname.hh"
+#include "dns.hh"
+
+using namespace ::boost::multi_index;
+
+/* FIXME should become part of the normal cache (I think) and shoudl become more like
+ * struct {
+ * vector<DNSRecord> records;
+ * vector<DNSRecord> signatures;
+ * } recsig_t;
+ *
+ * typedef vector<recsig_t> recordsAndSignatures;
+ */
+typedef struct {
+ vector<DNSRecord> records;
+ vector<DNSRecord> signatures;
+} recordsAndSignatures;
+
+class NegCache : public boost::noncopyable {
+ public:
+ struct NegCacheEntry {
+ DNSName d_name; // The denied name
+ QType d_qtype; // The denied type
+ DNSName d_auth; // The denying name (aka auth)
+ uint32_t d_ttd; // Timestamp when this entry should die
+ recordsAndSignatures authoritySOA; // The upstream SOA record and RRSIGs
+ recordsAndSignatures DNSSECRecords; // The upstream NSEC(3) and RRSIGs
+ uint32_t getTTD() const {
+ return d_ttd;
+ };
+ };
+
+ void add(const NegCacheEntry& ne);
+ bool get(const DNSName& qname, const QType& qtype, const struct timeval& now, NegCacheEntry& ne);
+ bool getRootNXTrust(const DNSName& qname, const struct timeval& now, NegCacheEntry& ne);
+ uint64_t count(const DNSName& qname) const;
+ uint64_t count(const DNSName& qname, const QType qtype) const;
+ void prune(unsigned int maxEntries);
+ void clear();
+ uint64_t dumpToFile(FILE* fd);
+ uint64_t wipe(const DNSName& name, bool subtree = false);
+
+ uint64_t size() {
+ return d_negcache.size();
+ };
+
+ private:
+ typedef boost::multi_index_container <
+ NegCacheEntry,
+ indexed_by <
+ ordered_unique <
+ composite_key <
+ NegCacheEntry,
+ member<NegCacheEntry, DNSName, &NegCacheEntry::d_name>,
+ member<NegCacheEntry, QType, &NegCacheEntry::d_qtype>
+ >,
+ composite_key_compare <
+ CanonDNSNameCompare, std::less<QType>
+ >
+ >,
+ sequenced<>
+ >
+ > negcache_t;
+
+ // Required for the cachecleaner
+ typedef negcache_t::nth_index<1>::type negcache_sequence_t;
+
+ // Stores the negative cache entries
+ negcache_t d_negcache;
+};
--- /dev/null
+../packetcache.hh
\ No newline at end of file
--- /dev/null
+#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 <utility>
+
+#include "dnsname.hh"
+#include "dnswriter.hh"
+#include "ednscookies.hh"
+#include "ednsoptions.hh"
+#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)
+{
+ DNSPacketWriter pw(query, name, QType::A, QClass::IN, 0);
+ pw.commit();
+
+ EDNSCookiesOpt cookiesOpt;
+ cookiesOpt.client = clientCookie;
+ cookiesOpt.server = serverCookie;
+ string cookiesOptionStr = makeEDNSCookiesOptString(cookiesOpt);
+ EDNSSubnetOpts ecsOpts;
+ ecsOpts.source = ecs;
+ string origECSOptionStr = makeEDNSSubnetOptsString(ecsOpts);
+ DNSPacketWriter::optvect_t opts;
+ opts.push_back(make_pair(EDNSOptionCode::COOKIE, cookiesOptionStr));
+ opts.push_back(make_pair(EDNSOptionCode::ECS, origECSOptionStr));
+ opts.push_back(make_pair(EDNSOptionCode::COOKIE, cookiesOptionStr));
+ pw.addOpt(512, 0, 0, opts);
+ pw.commit();
+}
+
+BOOST_AUTO_TEST_CASE(test_getEDNSOption) {
+ DNSName name("www.powerdns.com.");
+ Netmask ecs("127.0.0.1/32");
+ vector<uint8_t> query;
+
+ getRawQueryWithECSAndCookie(name, ecs, "deadbeef", "deadbeef", query);
+
+ const struct dnsheader* dh = reinterpret_cast<struct dnsheader*>(query.data());
+ size_t questionLen = query.size();
+ unsigned int consumed = 0;
+ DNSName dnsname = DNSName(reinterpret_cast<const char*>(query.data()), questionLen, sizeof(dnsheader), false, nullptr, nullptr, &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 */
+ BOOST_REQUIRE_EQUAL(ntohs(dh->arcount), 1);
+ BOOST_REQUIRE(questionLen > pos + 11);
+ /* OPT root label (1) followed by type (2) */
+ BOOST_REQUIRE_EQUAL(query.at(pos), 0);
+ BOOST_REQUIRE(query.at(pos+2) == QType::OPT);
+
+ char* ecsStart = nullptr;
+ size_t ecsLen = 0;
+ int res = getEDNSOption(reinterpret_cast<char*>(query.data())+pos+9, questionLen - pos - 9, EDNSOptionCode::ECS, &ecsStart, &ecsLen);
+ BOOST_CHECK_EQUAL(res, 0);
+
+ EDNSSubnetOpts eso;
+ BOOST_REQUIRE(getEDNSSubnetOptsFromString(ecsStart + 4, ecsLen - 4, &eso));
+
+ BOOST_CHECK(eso.source == ecs);
+}
+
+BOOST_AUTO_TEST_CASE(test_getEDNSOptions) {
+ DNSName name("www.powerdns.com.");
+ Netmask ecs("127.0.0.1/32");
+ vector<uint8_t> query;
+
+ getRawQueryWithECSAndCookie(name, ecs, "deadbeef", "deadbeef", query);
+
+ const struct dnsheader* dh = reinterpret_cast<struct dnsheader*>(query.data());
+ size_t questionLen = query.size();
+ unsigned int consumed = 0;
+ DNSName dnsname = DNSName(reinterpret_cast<const char*>(query.data()), questionLen, sizeof(dnsheader), false, nullptr, nullptr, &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 */
+ BOOST_REQUIRE_EQUAL(ntohs(dh->arcount), 1);
+ BOOST_REQUIRE(questionLen > pos + 11);
+ /* OPT root label (1) followed by type (2) */
+ BOOST_REQUIRE_EQUAL(query.at(pos), 0);
+ BOOST_REQUIRE(query.at(pos+2) == QType::OPT);
+
+ std::map<uint16_t, EDNSOptionView> options;
+ int res = getEDNSOptions(reinterpret_cast<char*>(query.data())+pos+9, questionLen - pos - 9, options);
+ BOOST_REQUIRE_EQUAL(res, 0);
+
+ /* 3 EDNS options but two of them are EDNS Cookie, so we only keep one */
+ BOOST_CHECK_EQUAL(options.size(), 2);
+
+ auto it = options.find(EDNSOptionCode::ECS);
+ BOOST_REQUIRE(it != options.end());
+ BOOST_REQUIRE(it->second.content != nullptr);
+ BOOST_REQUIRE_GT(it->second.size, 0);
+
+ EDNSSubnetOpts eso;
+ BOOST_REQUIRE(getEDNSSubnetOptsFromString(it->second.content, it->second.size, &eso));
+ BOOST_CHECK(eso.source == ecs);
+
+ it = options.find(EDNSOptionCode::COOKIE);
+ BOOST_REQUIRE(it != options.end());
+ BOOST_REQUIRE(it->second.content != nullptr);
+ BOOST_REQUIRE_GT(it->second.size, 0);
+}
+
+BOOST_AUTO_TEST_SUITE_END()
--- /dev/null
+#define BOOST_TEST_DYN_LINK
+#define BOOST_TEST_NO_MAIN
+#include <boost/test/unit_test.hpp>
+
+#include "negcache.hh"
+#include "dnsrecords.hh"
+#include "utility.hh"
+
+static recordsAndSignatures genRecsAndSigs(const DNSName& name, const uint16_t qtype, const string& content, bool sigs) {
+ recordsAndSignatures ret;
+
+ DNSRecord rec;
+ rec.d_name = name;
+ rec.d_type = qtype;
+ rec.d_ttl = 600;
+ rec.d_place = DNSResourceRecord::AUTHORITY;
+ rec.d_content = shared_ptr<DNSRecordContent>(DNSRecordContent::mastermake(qtype, QClass::IN, content));
+
+ ret.records.push_back(rec);
+
+ if (sigs) {
+ rec.d_type = QType::RRSIG;
+ rec.d_content = std::make_shared<RRSIGRecordContent>(QType(qtype).getName() + " 5 3 600 2100010100000000 2100010100000000 24567 dummy data");
+ ret.signatures.push_back(rec);
+ }
+
+ return ret;
+}
+
+static NegCache::NegCacheEntry genNegCacheEntry(const DNSName& name, const DNSName& auth, const struct timeval& now, const uint16_t qtype=0) {
+ NegCache::NegCacheEntry ret;
+
+ ret.d_name = name;
+ ret.d_qtype = QType(qtype);
+ ret.d_auth = auth;
+ ret.d_ttd = now.tv_sec + 600;
+ ret.authoritySOA = genRecsAndSigs(auth, QType::SOA, "ns1 hostmaster 1 2 3 4 5", true);
+ ret.DNSSECRecords = genRecsAndSigs(auth, QType::NSEC, "deadbeef", true);
+
+ return ret;
+}
+
+BOOST_AUTO_TEST_SUITE(negcache_cc)
+
+BOOST_AUTO_TEST_CASE(test_get_entry) {
+ /* Add a full name negative entry to the cache and attempt to get an entry for
+ * the A record. Should yield the full name does not exist entry
+ */
+ DNSName qname("www2.powerdns.com");
+ DNSName auth("powerdns.com");
+
+ struct timeval now;
+ Utility::gettimeofday(&now, 0);
+
+ NegCache cache;
+ cache.add(genNegCacheEntry(qname, auth, now));
+
+ BOOST_CHECK_EQUAL(cache.size(), 1);
+
+ NegCache::NegCacheEntry ne;
+ bool ret = cache.get(qname, QType(1), now, ne);
+
+ BOOST_CHECK(ret);
+ BOOST_CHECK_EQUAL(ne.d_name, qname);
+ BOOST_CHECK_EQUAL(ne.d_qtype.getName(), QType(0).getName());
+ BOOST_CHECK_EQUAL(ne.d_auth, auth);
+}
+
+BOOST_AUTO_TEST_CASE(test_get_NODATA_entry) {
+ DNSName qname("www2.powerdns.com");
+ DNSName auth("powerdns.com");
+
+ struct timeval now;
+ Utility::gettimeofday(&now, 0);
+
+ NegCache cache;
+ cache.add(genNegCacheEntry(qname, auth, now, 1));
+
+ BOOST_CHECK_EQUAL(cache.size(), 1);
+
+ NegCache::NegCacheEntry ne;
+ bool ret = cache.get(qname, QType(1), now, ne);
+
+ BOOST_CHECK(ret);
+ BOOST_CHECK_EQUAL(ne.d_name, qname);
+ BOOST_CHECK_EQUAL(ne.d_qtype.getName(), QType(1).getName());
+ BOOST_CHECK_EQUAL(ne.d_auth, auth);
+
+ NegCache::NegCacheEntry ne2;
+ ret = cache.get(qname, QType(16), now, ne2);
+ BOOST_CHECK_EQUAL(ret, false);
+}
+
+BOOST_AUTO_TEST_CASE(test_getRootNXTrust_entry) {
+ DNSName qname("com");
+ DNSName auth(".");
+
+ struct timeval now;
+ Utility::gettimeofday(&now, 0);
+
+ NegCache cache;
+ cache.add(genNegCacheEntry(qname, auth, now));
+
+ BOOST_CHECK_EQUAL(cache.size(), 1);
+
+ NegCache::NegCacheEntry ne;
+ bool ret = cache.getRootNXTrust(qname, now, ne);
+
+ BOOST_CHECK(ret);
+ BOOST_CHECK_EQUAL(ne.d_name, qname);
+ BOOST_CHECK_EQUAL(ne.d_qtype.getName(), QType(0).getName());
+ BOOST_CHECK_EQUAL(ne.d_auth, auth);
+}
+
+BOOST_AUTO_TEST_CASE(test_add_and_get_expired_entry) {
+ DNSName qname("www2.powerdns.com");
+ DNSName auth("powerdns.com");
+
+ struct timeval now;
+ Utility::gettimeofday(&now, 0);
+ now.tv_sec -= 1000;
+
+ NegCache cache;
+ cache.add(genNegCacheEntry(qname, auth, now));
+
+ BOOST_CHECK_EQUAL(cache.size(), 1);
+
+ NegCache::NegCacheEntry ne;
+
+ now.tv_sec += 1000;
+ bool ret = cache.get(qname, QType(1), now, ne);
+
+ BOOST_CHECK_EQUAL(ret, false);
+ BOOST_CHECK_EQUAL(ne.d_name, DNSName());
+ BOOST_CHECK_EQUAL(ne.d_auth, DNSName());
+ BOOST_CHECK(ne.authoritySOA.records.empty());
+}
+
+BOOST_AUTO_TEST_CASE(test_getRootNXTrust_expired_entry) {
+ DNSName qname("com");
+ DNSName auth(".");
+
+ struct timeval now;
+ Utility::gettimeofday(&now, 0);
+ now.tv_sec -= 1000;
+
+ NegCache cache;
+ cache.add(genNegCacheEntry(qname, auth, now));
+
+ BOOST_CHECK_EQUAL(cache.size(), 1);
+
+ NegCache::NegCacheEntry ne;
+
+ now.tv_sec += 1000;
+ bool ret = cache.getRootNXTrust(qname, now, ne);
+
+ BOOST_CHECK_EQUAL(ret, false);
+ BOOST_CHECK_EQUAL(ne.d_name, DNSName());
+ BOOST_CHECK_EQUAL(ne.d_auth, DNSName());
+ BOOST_CHECK(ne.authoritySOA.records.empty());
+}
+
+BOOST_AUTO_TEST_CASE(test_add_updated_entry) {
+ DNSName qname("www2.powerdns.com");
+ DNSName auth("powerdns.com");
+ DNSName auth2("com");
+
+ struct timeval now;
+ Utility::gettimeofday(&now, 0);
+
+ NegCache cache;
+ cache.add(genNegCacheEntry(qname, auth, now));
+ // Should override the existing entry for www2.powerdns.com
+ cache.add(genNegCacheEntry(qname, auth2, now));
+
+ BOOST_CHECK_EQUAL(cache.size(), 1);
+
+ NegCache::NegCacheEntry ne;
+ bool ret = cache.get(qname, QType(1), now, ne);
+
+ BOOST_CHECK(ret);
+ BOOST_CHECK_EQUAL(ne.d_name, qname);
+ BOOST_CHECK_EQUAL(ne.d_auth, auth2);
+}
+
+BOOST_AUTO_TEST_CASE(test_getRootNXTrust) {
+ DNSName qname("www2.powerdns.com");
+ DNSName auth("powerdns.com");
+ DNSName qname2("com");
+ DNSName auth2(".");
+
+ struct timeval now;
+ Utility::gettimeofday(&now, 0);
+
+ NegCache cache;
+ cache.add(genNegCacheEntry(qname, auth, now));
+ cache.add(genNegCacheEntry(qname2, auth2, now));
+
+ NegCache::NegCacheEntry ne;
+ bool ret = cache.getRootNXTrust(qname, now, ne);
+
+ BOOST_CHECK(ret);
+ BOOST_CHECK_EQUAL(ne.d_name, qname2);
+ BOOST_CHECK_EQUAL(ne.d_auth, auth2);
+}
+
+BOOST_AUTO_TEST_CASE(test_getRootNXTrust_full_domain_only) {
+ DNSName qname("www2.powerdns.com");
+ DNSName auth("powerdns.com");
+ DNSName qname2("com");
+ DNSName auth2(".");
+
+ struct timeval now;
+ Utility::gettimeofday(&now, 0);
+
+ NegCache cache;
+ cache.add(genNegCacheEntry(qname, auth, now));
+ cache.add(genNegCacheEntry(qname2, auth2, now, 1)); // Add the denial for COM|A
+
+ NegCache::NegCacheEntry ne;
+ bool ret = cache.getRootNXTrust(qname, now, ne);
+
+ BOOST_CHECK_EQUAL(ret, false);
+}
+
+BOOST_AUTO_TEST_CASE(test_prune) {
+ string qname(".powerdns.com");
+ DNSName auth("powerdns.com");
+
+ struct timeval now;
+ Utility::gettimeofday(&now, 0);
+
+ NegCache cache;
+ NegCache::NegCacheEntry ne;
+ for(int i = 0; i < 400; i++) {
+ ne = genNegCacheEntry(DNSName(std::to_string(i) + qname), auth, now);
+ cache.add(ne);
+ }
+
+ BOOST_CHECK_EQUAL(cache.size(), 400);
+
+ cache.prune(100);
+
+ BOOST_CHECK_EQUAL(cache.size(), 100);
+}
+
+BOOST_AUTO_TEST_CASE(test_wipe_single) {
+ string qname(".powerdns.com");
+ DNSName auth("powerdns.com");
+
+ struct timeval now;
+ Utility::gettimeofday(&now, 0);
+
+ NegCache cache;
+ NegCache::NegCacheEntry ne;
+ ne = genNegCacheEntry(auth, auth, now);
+ cache.add(ne);
+
+ for(int i = 0; i < 400; i++) {
+ ne = genNegCacheEntry(DNSName(std::to_string(i) + qname), auth, now);
+ cache.add(ne);
+ }
+
+ BOOST_CHECK_EQUAL(cache.size(), 401);
+
+ // Should only wipe the powerdns.com entry
+ cache.wipe(auth);
+ BOOST_CHECK_EQUAL(cache.size(), 400);
+
+ NegCache::NegCacheEntry ne2;
+ bool ret = cache.get(auth, QType(1), now, ne2);
+
+ BOOST_CHECK_EQUAL(ret, false);
+ BOOST_CHECK_EQUAL(ne2.d_auth, DNSName());
+ BOOST_CHECK_EQUAL(ne2.d_name, DNSName());
+
+ cache.wipe(DNSName("1.powerdns.com"));
+ BOOST_CHECK_EQUAL(cache.size(), 399);
+
+ NegCache::NegCacheEntry ne3;
+ ret = cache.get(auth, QType(1), now, ne3);
+
+ BOOST_CHECK_EQUAL(ret, false);
+ BOOST_CHECK_EQUAL(ne3.d_auth, DNSName());
+ BOOST_CHECK_EQUAL(ne3.d_name, DNSName());
+}
+
+BOOST_AUTO_TEST_CASE(test_wipe_subtree) {
+ string qname(".powerdns.com");
+ string qname2("powerdns.org");
+ DNSName auth("powerdns.com");
+
+ struct timeval now;
+ Utility::gettimeofday(&now, 0);
+
+ NegCache cache;
+ NegCache::NegCacheEntry ne;
+ ne = genNegCacheEntry(auth, auth, now);
+ cache.add(ne);
+
+ for(int i = 0; i < 400; i++) {
+ ne = genNegCacheEntry(DNSName(std::to_string(i) + qname), auth, now);
+ cache.add(ne);
+ ne = genNegCacheEntry(DNSName(std::to_string(i) + qname2), auth, now);
+ cache.add(ne);
+ }
+
+ BOOST_CHECK_EQUAL(cache.size(), 801);
+
+ // Should wipe all the *.powerdns.com and powerdns.com entries
+ cache.wipe(auth, true);
+ BOOST_CHECK_EQUAL(cache.size(), 400);
+}
+
+BOOST_AUTO_TEST_CASE(test_clear) {
+ string qname(".powerdns.com");
+ DNSName auth("powerdns.com");
+
+ struct timeval now;
+ Utility::gettimeofday(&now, 0);
+
+ NegCache cache;
+ NegCache::NegCacheEntry ne;
+
+ for(int i = 0; i < 400; i++) {
+ ne = genNegCacheEntry(DNSName(std::to_string(i) + qname), auth, now);
+ cache.add(ne);
+ }
+
+ BOOST_CHECK_EQUAL(cache.size(), 400);
+ cache.clear();
+ BOOST_CHECK_EQUAL(cache.size(), 0);
+}
+
+BOOST_AUTO_TEST_CASE(test_dumpToFile) {
+ NegCache cache;
+ vector<string> expected;
+ expected.push_back("www1.powerdns.com. 600 IN TYPE0 VIA powerdns.com.\n");
+ expected.push_back("www2.powerdns.com. 600 IN TYPE0 VIA powerdns.com.\n");
+
+ struct timeval now;
+ Utility::gettimeofday(&now, 0);
+
+ cache.add(genNegCacheEntry(DNSName("www1.powerdns.com"), DNSName("powerdns.com"), now));
+ cache.add(genNegCacheEntry(DNSName("www2.powerdns.com"), DNSName("powerdns.com"), now));
+
+ FILE* fp;
+ fp = tmpfile();
+ if (!fp)
+ BOOST_FAIL("Temporary file could not be opened");
+
+ cache.dumpToFile(fp);
+
+ rewind(fp);
+ char *line = nullptr;
+ size_t len = 0;
+ ssize_t read;
+
+ for (const auto& str : expected) {
+ read = getline(&line, &len, fp);
+ if (read == -1)
+ BOOST_FAIL("Unable to read a line from the temp file");
+ BOOST_CHECK_EQUAL(line, str);
+ }
+ fclose(fp);
+}
+
+BOOST_AUTO_TEST_CASE(test_count) {
+ string qname(".powerdns.com");
+ string qname2("powerdns.org");
+ DNSName auth("powerdns.com");
+
+ struct timeval now;
+ Utility::gettimeofday(&now, 0);
+
+ NegCache cache;
+ NegCache::NegCacheEntry ne;
+ ne = genNegCacheEntry(auth, auth, now);
+ cache.add(ne);
+
+ for(int i = 0; i < 400; i++) {
+ ne = genNegCacheEntry(DNSName(std::to_string(i) + qname), auth, now);
+ cache.add(ne);
+ ne = genNegCacheEntry(DNSName(std::to_string(i) + qname2), auth, now);
+ cache.add(ne);
+ }
+
+ uint64_t count;
+ count = cache.count(auth);
+ BOOST_CHECK_EQUAL(count, 1);
+ count = cache.count(auth, QType(1));
+ BOOST_CHECK_EQUAL(count, 0);
+}
+
+BOOST_AUTO_TEST_SUITE_END()
--- /dev/null
+#define BOOST_TEST_DYN_LINK
+#define BOOST_TEST_NO_MAIN
+#include <boost/test/unit_test.hpp>
+
+#include "arguments.hh"
+#include "lua-recursor4.hh"
+#include "namespaces.hh"
+#include "rec-lua-conf.hh"
+#include "root-dnssec.hh"
+#include "syncres.hh"
+#include "validate-recursor.hh"
+
+std::unordered_set<DNSName> g_delegationOnly;
+RecursorStats g_stats;
+GlobalStateHolder<LuaConfigItems> g_luaconfs;
+NetmaskGroup* g_dontQuery{nullptr};
+__thread MemRecursorCache* t_RC{nullptr};
+SyncRes::domainmap_t* g_initialDomainMap{nullptr};
+unsigned int g_numThreads = 1;
+
+/* Fake some required functions we didn't want the trouble to
+ link with */
+ArgvMap &arg()
+{
+ static ArgvMap theArg;
+ return theArg;
+}
+
+int getMTaskerTID()
+{
+ return 0;
+}
+
+bool RecursorLua4::preoutquery(const ComboAddress& ns, const ComboAddress& requestor, const DNSName& query, const QType& qtype, bool isTcp, vector<DNSRecord>& res, int& ret)
+{
+ return false;
+}
+
+int asyncresolve(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)
+{
+ return 0;
+}
+
+/* primeHints() is only here for now because it
+ was way too much trouble to link with the real one.
+ We should fix this, empty functions are one thing, but this is
+ bad.
+*/
+
+#include "root-addresses.hh"
+
+void primeHints(void)
+{
+ vector<DNSRecord> nsset;
+ if(!t_RC)
+ t_RC = new MemRecursorCache();
+
+ DNSRecord arr, aaaarr, nsrr;
+ nsrr.d_name=g_rootdnsname;
+ arr.d_type=QType::A;
+ aaaarr.d_type=QType::AAAA;
+ nsrr.d_type=QType::NS;
+ arr.d_ttl=aaaarr.d_ttl=nsrr.d_ttl=time(nullptr)+3600000;
+
+ for(char c='a';c<='m';++c) {
+ static char templ[40];
+ strncpy(templ,"a.root-servers.net.", sizeof(templ) - 1);
+ templ[sizeof(templ)-1] = '\0';
+ *templ=c;
+ aaaarr.d_name=arr.d_name=DNSName(templ);
+ nsrr.d_content=std::make_shared<NSRecordContent>(DNSName(templ));
+ 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>>(), 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>>(), true);
+ }
+
+ nsset.push_back(nsrr);
+ }
+ t_RC->replace(time(0), g_rootdnsname, QType(QType::NS), nsset, vector<std::shared_ptr<RRSIGRecordContent>>(), false); // and stuff in the cache
+}
+
+LuaConfigItems::LuaConfigItems()
+{
+ for (const auto &dsRecord : rootDSs) {
+ auto ds=unique_ptr<DSRecordContent>(dynamic_cast<DSRecordContent*>(DSRecordContent::make(dsRecord)));
+ dsAnchors[g_rootdnsname].insert(*ds);
+ }
+}
+
+/* Some helpers functions */
+
+static void init(bool debug=false)
+{
+ if (debug) {
+ L.setName("test");
+ L.setLoglevel((Logger::Urgency)(6)); // info and up
+ L.disableSyslog(true);
+ L.toConsole(Logger::Info);
+ }
+
+ seedRandom("/dev/urandom");
+ reportAllTypes();
+
+ if (g_dontQuery)
+ delete g_dontQuery;
+ g_dontQuery = new NetmaskGroup();
+
+ if (t_RC)
+ delete t_RC;
+ t_RC = new MemRecursorCache();
+
+ if (g_initialDomainMap)
+ delete g_initialDomainMap;
+ g_initialDomainMap = new SyncRes::domainmap_t(); // new threads needs this to be setup
+
+ SyncRes::s_maxqperq = 50;
+ SyncRes::s_maxtotusec = 1000*7000;
+ SyncRes::s_maxdepth = 40;
+ SyncRes::s_maxnegttl = 3600;
+ SyncRes::s_maxcachettl = 86400;
+ SyncRes::s_packetcachettl = 3600;
+ SyncRes::s_packetcacheservfailttl = 60;
+ SyncRes::s_serverdownmaxfails = 64;
+ SyncRes::s_serverdownthrottletime = 60;
+ SyncRes::s_doIPv6 = true;
+ SyncRes::s_ecsipv4limit = 24;
+ SyncRes::s_ecsipv6limit = 56;
+ SyncRes::s_rootNXTrust = true;
+ SyncRes::s_minimumTTL = 0;
+ SyncRes::s_serverID = "PowerDNS Unit Tests Server ID";
+
+ g_ednssubnets = NetmaskGroup();
+ g_ednsdomains = SuffixMatchNode();
+ g_useIncomingECS = false;
+ g_delegationOnly.clear();
+
+ auto luaconfsCopy = g_luaconfs.getCopy();
+ luaconfsCopy.dfe.clear();
+ g_luaconfs.setState(luaconfsCopy);
+
+ ::arg().set("version-string", "string reported on version.pdns or version.bind")="PowerDNS Unit Tests";
+}
+
+static void initSR(std::unique_ptr<SyncRes>& sr, bool edns0, bool dnssec, SyncRes::LogMode lm=SyncRes::LogNone, time_t fakeNow=0)
+{
+ struct timeval now;
+ if (fakeNow > 0) {
+ now.tv_sec = fakeNow;
+ now.tv_usec = 0;
+ }
+ else {
+ Utility::gettimeofday(&now, 0);
+ }
+
+ sr = std::unique_ptr<SyncRes>(new SyncRes(now));
+ sr->setDoEDNS0(edns0);
+ sr->setDoDNSSEC(dnssec);
+ sr->setLogMode(lm);
+ t_sstorage->domainmap = g_initialDomainMap;
+ t_sstorage->negcache.clear();
+ t_sstorage->nsSpeeds.clear();
+ t_sstorage->ednsstatus.clear();
+ t_sstorage->throttle.clear();
+ t_sstorage->fails.clear();
+ t_sstorage->dnssecmap.clear();
+}
+
+static void setLWResult(LWResult* res, int rcode, bool aa=false, bool tc=false, bool edns=false)
+{
+ res->d_rcode = rcode;
+ res->d_aabit = aa;
+ res->d_tcbit = tc;
+ res->d_haveEDNS = edns;
+}
+
+static void addRecordToList(std::vector<DNSRecord>& records, const DNSName& name, uint16_t type, const std::string& content, DNSResourceRecord::Place place, uint32_t ttl)
+{
+ DNSRecord rec;
+ rec.d_place = place;
+ rec.d_name = name;
+ rec.d_type = type;
+ rec.d_ttl = ttl;
+
+ if (type == QType::NS) {
+ rec.d_content = std::make_shared<NSRecordContent>(DNSName(content));
+ }
+ else if (type == QType::A) {
+ rec.d_content = std::make_shared<ARecordContent>(ComboAddress(content));
+ }
+ else if (type == QType::AAAA) {
+ rec.d_content = std::make_shared<AAAARecordContent>(ComboAddress(content));
+ }
+ else if (type == QType::CNAME) {
+ rec.d_content = std::make_shared<CNAMERecordContent>(DNSName(content));
+ }
+ else if (type == QType::OPT) {
+ rec.d_content = std::make_shared<OPTRecordContent>();
+ }
+ else {
+ rec.d_content = shared_ptr<DNSRecordContent>(DNSRecordContent::mastermake(type, QClass::IN, content));
+ }
+
+ records.push_back(rec);
+}
+
+static void addRecordToList(std::vector<DNSRecord>& records, const std::string& name, uint16_t type, const std::string& content, DNSResourceRecord::Place place, uint32_t ttl)
+{
+ addRecordToList(records, name, type, content, place, ttl);
+}
+
+static void addRecordToLW(LWResult* res, const DNSName& name, uint16_t type, const std::string& content, DNSResourceRecord::Place place=DNSResourceRecord::ANSWER, uint32_t ttl=60)
+{
+ addRecordToList(res->d_records, name, type, content, place, ttl);
+}
+
+static void addRecordToLW(LWResult* res, const std::string& name, uint16_t type, const std::string& content, DNSResourceRecord::Place place=DNSResourceRecord::ANSWER, uint32_t ttl=60)
+{
+ addRecordToLW(res, DNSName(name), type, content, place, ttl);
+}
+
+static bool isRootServer(const ComboAddress& ip)
+{
+ for (size_t idx = 0; idx < rootIps4Count; idx++) {
+ if (ip.toString() == rootIps4[idx]) {
+ return true;
+ }
+ }
+
+ for (size_t idx = 0; idx < rootIps6Count; idx++) {
+ if (ip.toString() == rootIps6[idx]) {
+ return true;
+ }
+ }
+ return false;
+}
+
+/* Real tests */
+
+BOOST_AUTO_TEST_SUITE(syncres_cc)
+
+BOOST_AUTO_TEST_CASE(test_root_primed) {
+ std::unique_ptr<SyncRes> sr;
+ init();
+ initSR(sr, true, false);
+
+ primeHints();
+
+ /* we are primed, we should be able to resolve NS . without any query */
+ vector<DNSRecord> ret;
+ int res = sr->beginResolve(DNSName("."), QType(QType::NS), QClass::IN, ret);
+ BOOST_CHECK_EQUAL(res, 0);
+ BOOST_CHECK_EQUAL(ret.size(), 13);
+}
+
+BOOST_AUTO_TEST_CASE(test_root_not_primed) {
+ std::unique_ptr<SyncRes> sr;
+ init(false);
+ initSR(sr, true, false);
+
+ size_t queriesCount = 0;
+
+ sr->setAsyncCallback([&queriesCount](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 (domain == g_rootdnsname && type == QType::NS) {
+ setLWResult(res, 0, true, false, true);
+ addRecordToLW(res, g_rootdnsname, QType::NS, "a.root-servers.net.", DNSResourceRecord::ANSWER, 3600);
+ addRecordToLW(res, "a.root-servers.net.", QType::A, "198.41.0.4", DNSResourceRecord::ADDITIONAL, 3600);
+ addRecordToLW(res, "a.root-servers.net.", QType::AAAA, "2001:503:ba3e::2:30", DNSResourceRecord::ADDITIONAL, 3600);
+
+ return 1;
+ }
+
+ return 0;
+ });
+
+ /* we are not primed yet, so SyncRes will have to call primeHints()
+ then call getRootNS(), for which at least one of the root servers needs to answer */
+ vector<DNSRecord> ret;
+ int res = sr->beginResolve(DNSName("."), QType(QType::NS), QClass::IN, ret);
+ BOOST_CHECK_EQUAL(res, 0);
+ BOOST_CHECK_EQUAL(ret.size(), 1);
+ BOOST_CHECK_EQUAL(queriesCount, 2);
+}
+
+BOOST_AUTO_TEST_CASE(test_root_not_primed_and_no_response) {
+ std::unique_ptr<SyncRes> sr;
+ init();
+ initSR(sr, true, false);
+ std::set<ComboAddress> downServers;
+
+ /* we are not primed yet, so SyncRes will have to call primeHints()
+ then call getRootNS(), for which at least one of the root servers needs to answer.
+ None will, so it should ServFail.
+ */
+ sr->setAsyncCallback([&downServers](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) {
+
+ downServers.insert(ip);
+ return 0;
+ });
+
+ vector<DNSRecord> ret;
+ int res = sr->beginResolve(DNSName("."), QType(QType::NS), QClass::IN, ret);
+ BOOST_CHECK_EQUAL(res, RCode::ServFail);
+ BOOST_CHECK_EQUAL(ret.size(), 0);
+ BOOST_CHECK(downServers.size() > 0);
+ /* we explicitly refuse to mark the root servers down */
+ for (const auto& server : downServers) {
+ BOOST_CHECK_EQUAL(t_sstorage->fails.value(server), 0);
+ }
+}
+
+BOOST_AUTO_TEST_CASE(test_edns_formerr_fallback) {
+ std::unique_ptr<SyncRes> sr;
+ init();
+ initSR(sr, true, false);
+
+ ComboAddress noEDNSServer;
+ size_t queriesWithEDNS = 0;
+ size_t queriesWithoutEDNS = 0;
+
+ sr->setAsyncCallback([&queriesWithEDNS, &queriesWithoutEDNS, &noEDNSServer](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 (EDNS0Level != 0) {
+ queriesWithEDNS++;
+ noEDNSServer = ip;
+
+ setLWResult(res, RCode::FormErr);
+ return 1;
+ }
+
+ queriesWithoutEDNS++;
+
+ if (domain == DNSName("powerdns.com") && type == QType::A && !doTCP) {
+ setLWResult(res, 0, true, false, false);
+ addRecordToLW(res, domain, QType::A, "192.0.2.1");
+ return 1;
+ }
+
+ return 0;
+ });
+
+ primeHints();
+
+ /* fake that the root NS doesn't handle EDNS, check that we fallback */
+ vector<DNSRecord> ret;
+ int res = sr->beginResolve(DNSName("powerdns.com."), QType(QType::A), QClass::IN, ret);
+ BOOST_CHECK_EQUAL(res, 0);
+ BOOST_CHECK_EQUAL(ret.size(), 1);
+ BOOST_CHECK_EQUAL(queriesWithEDNS, 1);
+ BOOST_CHECK_EQUAL(queriesWithoutEDNS, 1);
+ BOOST_CHECK_EQUAL(t_sstorage->ednsstatus.size(), 1);
+ BOOST_CHECK_EQUAL(t_sstorage->ednsstatus[noEDNSServer].mode, SyncRes::EDNSStatus::NOEDNS);
+}
+
+BOOST_AUTO_TEST_CASE(test_edns_notimp_fallback) {
+ std::unique_ptr<SyncRes> sr;
+ init();
+ initSR(sr, true, false);
+
+ size_t queriesWithEDNS = 0;
+ size_t queriesWithoutEDNS = 0;
+
+ sr->setAsyncCallback([&queriesWithEDNS, &queriesWithoutEDNS](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 (EDNS0Level != 0) {
+ queriesWithEDNS++;
+ setLWResult(res, RCode::NotImp);
+ return 1;
+ }
+
+ queriesWithoutEDNS++;
+
+ if (domain == DNSName("powerdns.com") && type == QType::A && !doTCP) {
+ setLWResult(res, 0, true, false, false);
+ addRecordToLW(res, domain, QType::A, "192.0.2.1");
+ return 1;
+ }
+
+ return 0;
+ });
+
+ primeHints();
+
+ /* fake that the NS doesn't handle EDNS, check that we fallback */
+ vector<DNSRecord> ret;
+ int res = sr->beginResolve(DNSName("powerdns.com."), QType(QType::A), QClass::IN, ret);
+ BOOST_CHECK_EQUAL(res, 0);
+ BOOST_CHECK_EQUAL(ret.size(), 1);
+ BOOST_CHECK_EQUAL(queriesWithEDNS, 1);
+ BOOST_CHECK_EQUAL(queriesWithoutEDNS, 1);
+}
+
+BOOST_AUTO_TEST_CASE(test_tc_fallback_to_tcp) {
+ std::unique_ptr<SyncRes> sr;
+ init();
+ initSR(sr, true, false);
+
+ sr->setAsyncCallback([](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 (!doTCP) {
+ setLWResult(res, 0, false, true, false);
+ return 1;
+ }
+ if (domain == DNSName("powerdns.com") && type == QType::A && doTCP) {
+ setLWResult(res, 0, true, false, false);
+ addRecordToLW(res, domain, QType::A, "192.0.2.1");
+ return 1;
+ }
+
+ return 0;
+ });
+
+ primeHints();
+
+ /* fake that the NS truncates every request over UDP, we should fallback to TCP */
+ vector<DNSRecord> ret;
+ int res = sr->beginResolve(DNSName("powerdns.com."), QType(QType::A), QClass::IN, ret);
+ BOOST_CHECK_EQUAL(res, 0);
+}
+
+BOOST_AUTO_TEST_CASE(test_all_nss_down) {
+ std::unique_ptr<SyncRes> sr;
+ init();
+ initSR(sr, true, false);
+ std::set<ComboAddress> downServers;
+
+ primeHints();
+
+ sr->setAsyncCallback([&downServers](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)) {
+ setLWResult(res, 0, true, false, true);
+ addRecordToLW(res, "com.", QType::NS, "a.gtld-servers.net.", DNSResourceRecord::AUTHORITY, 172800);
+ addRecordToLW(res, "a.gtld-servers.net.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600);
+ addRecordToLW(res, "a.gtld-servers.net.", QType::AAAA, "2001:DB8::1", DNSResourceRecord::ADDITIONAL, 3600);
+ return 1;
+ }
+ else if (ip == ComboAddress("192.0.2.1:53") || ip == ComboAddress("[2001:DB8::1]:53")) {
+ setLWResult(res, 0, true, false, true);
+ addRecordToLW(res, "powerdns.com.", QType::NS, "pdns-public-ns1.powerdns.com.", DNSResourceRecord::AUTHORITY, 172800);
+ addRecordToLW(res, "powerdns.com.", QType::NS, "pdns-public-ns2.powerdns.com.", DNSResourceRecord::AUTHORITY, 172800);
+ addRecordToLW(res, "pdns-public-ns1.powerdns.com.", QType::A, "192.0.2.2", DNSResourceRecord::ADDITIONAL, 172800);
+ addRecordToLW(res, "pdns-public-ns1.powerdns.com.", QType::AAAA, "2001:DB8::2", DNSResourceRecord::ADDITIONAL, 172800);
+ addRecordToLW(res, "pdns-public-ns2.powerdns.com.", QType::A, "192.0.2.3", DNSResourceRecord::ADDITIONAL, 172800);
+ addRecordToLW(res, "pdns-public-ns2.powerdns.com.", QType::AAAA, "2001:DB8::3", DNSResourceRecord::ADDITIONAL, 172800);
+ return 1;
+ }
+ else {
+ downServers.insert(ip);
+ return 0;
+ }
+ });
+
+ DNSName target("powerdns.com.");
+
+ vector<DNSRecord> ret;
+ int res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
+ BOOST_CHECK_EQUAL(res, RCode::ServFail);
+ BOOST_CHECK_EQUAL(ret.size(), 0);
+ BOOST_CHECK_EQUAL(downServers.size(), 4);
+
+ for (const auto& server : downServers) {
+ BOOST_CHECK_EQUAL(t_sstorage->fails.value(server), 1);
+ BOOST_CHECK(t_sstorage->throttle.shouldThrottle(time(nullptr), boost::make_tuple(server, target, QType::A)));
+ }
+}
+
+BOOST_AUTO_TEST_CASE(test_all_nss_network_error) {
+ std::unique_ptr<SyncRes> sr;
+ init();
+ initSR(sr, true, false);
+ std::set<ComboAddress> downServers;
+
+ primeHints();
+
+ sr->setAsyncCallback([&downServers](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)) {
+ setLWResult(res, 0, true, false, true);
+ addRecordToLW(res, "com.", QType::NS, "a.gtld-servers.net.", DNSResourceRecord::AUTHORITY, 172800);
+ addRecordToLW(res, "a.gtld-servers.net.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600);
+ addRecordToLW(res, "a.gtld-servers.net.", QType::AAAA, "2001:DB8::1", DNSResourceRecord::ADDITIONAL, 3600);
+ return 1;
+ }
+ else if (ip == ComboAddress("192.0.2.1:53") || ip == ComboAddress("[2001:DB8::1]:53")) {
+ setLWResult(res, 0, true, false, true);
+ addRecordToLW(res, "powerdns.com.", QType::NS, "pdns-public-ns1.powerdns.com.", DNSResourceRecord::AUTHORITY, 172800);
+ addRecordToLW(res, "powerdns.com.", QType::NS, "pdns-public-ns2.powerdns.com.", DNSResourceRecord::AUTHORITY, 172800);
+ addRecordToLW(res, "pdns-public-ns1.powerdns.com.", QType::A, "192.0.2.2", DNSResourceRecord::ADDITIONAL, 172800);
+ addRecordToLW(res, "pdns-public-ns1.powerdns.com.", QType::AAAA, "2001:DB8::2", DNSResourceRecord::ADDITIONAL, 172800);
+ addRecordToLW(res, "pdns-public-ns2.powerdns.com.", QType::A, "192.0.2.3", DNSResourceRecord::ADDITIONAL, 172800);
+ addRecordToLW(res, "pdns-public-ns2.powerdns.com.", QType::AAAA, "2001:DB8::3", DNSResourceRecord::ADDITIONAL, 172800);
+ return 1;
+ }
+ else {
+ downServers.insert(ip);
+ return -1;
+ }
+ });
+
+ /* exact same test than the previous one, except instead of a time out we fake a network error */
+ DNSName target("powerdns.com.");
+
+ vector<DNSRecord> ret;
+ int res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
+ BOOST_CHECK_EQUAL(res, RCode::ServFail);
+ BOOST_CHECK_EQUAL(ret.size(), 0);
+ BOOST_CHECK_EQUAL(downServers.size(), 4);
+
+ for (const auto& server : downServers) {
+ BOOST_CHECK_EQUAL(t_sstorage->fails.value(server), 1);
+ BOOST_CHECK(t_sstorage->throttle.shouldThrottle(time(nullptr), boost::make_tuple(server, target, QType::A)));
+ }
+}
+
+BOOST_AUTO_TEST_CASE(test_os_limit_errors) {
+ std::unique_ptr<SyncRes> sr;
+ init();
+ initSR(sr, true, false);
+ std::set<ComboAddress> downServers;
+
+ primeHints();
+
+ sr->setAsyncCallback([&downServers](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)) {
+ setLWResult(res, 0, true, false, true);
+ addRecordToLW(res, "com.", QType::NS, "a.gtld-servers.net.", DNSResourceRecord::AUTHORITY, 172800);
+ addRecordToLW(res, "a.gtld-servers.net.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600);
+ addRecordToLW(res, "a.gtld-servers.net.", QType::AAAA, "2001:DB8::1", DNSResourceRecord::ADDITIONAL, 3600);
+ return 1;
+ }
+ else if (ip == ComboAddress("192.0.2.1:53") || ip == ComboAddress("[2001:DB8::1]:53")) {
+ setLWResult(res, 0, true, false, true);
+ addRecordToLW(res, "powerdns.com.", QType::NS, "pdns-public-ns1.powerdns.com.", DNSResourceRecord::AUTHORITY, 172800);
+ addRecordToLW(res, "powerdns.com.", QType::NS, "pdns-public-ns2.powerdns.com.", DNSResourceRecord::AUTHORITY, 172800);
+ addRecordToLW(res, "pdns-public-ns1.powerdns.com.", QType::A, "192.0.2.2", DNSResourceRecord::ADDITIONAL, 172800);
+ addRecordToLW(res, "pdns-public-ns1.powerdns.com.", QType::AAAA, "2001:DB8::2", DNSResourceRecord::ADDITIONAL, 172800);
+ addRecordToLW(res, "pdns-public-ns2.powerdns.com.", QType::A, "192.0.2.3", DNSResourceRecord::ADDITIONAL, 172800);
+ addRecordToLW(res, "pdns-public-ns2.powerdns.com.", QType::AAAA, "2001:DB8::3", DNSResourceRecord::ADDITIONAL, 172800);
+ return 1;
+ }
+ else {
+ if (downServers.size() < 3) {
+ /* only the last one will answer */
+ downServers.insert(ip);
+ return -2;
+ }
+ else {
+ setLWResult(res, 0, true, false, true);
+ addRecordToLW(res, "powerdns.com.", QType::A, "192.0.2.42");
+ return 1;
+ }
+ }
+ });
+
+ DNSName target("powerdns.com.");
+
+ vector<DNSRecord> ret;
+ int res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
+ BOOST_CHECK_EQUAL(res, 0);
+ BOOST_CHECK_EQUAL(ret.size(), 1);
+ BOOST_CHECK_EQUAL(downServers.size(), 3);
+
+ /* Error is reported as "OS limit error" (-2) so the servers should _NOT_ be marked down */
+ for (const auto& server : downServers) {
+ BOOST_CHECK_EQUAL(t_sstorage->fails.value(server), 0);
+ BOOST_CHECK(!t_sstorage->throttle.shouldThrottle(time(nullptr), boost::make_tuple(server, target, QType::A)));
+ }
+}
+
+BOOST_AUTO_TEST_CASE(test_glued_referral) {
+ std::unique_ptr<SyncRes> sr;
+ init();
+ initSR(sr, true, false);
+
+ primeHints();
+
+ const DNSName target("powerdns.com.");
+
+ 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) {
+ /* this will cause issue with qname minimization if we ever implement it */
+ if (domain != target) {
+ return 0;
+ }
+
+ if (isRootServer(ip)) {
+ setLWResult(res, 0, true, false, true);
+ addRecordToLW(res, "com.", QType::NS, "a.gtld-servers.net.", DNSResourceRecord::AUTHORITY, 172800);
+ addRecordToLW(res, "a.gtld-servers.net.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600);
+ addRecordToLW(res, "a.gtld-servers.net.", QType::AAAA, "2001:DB8::1", DNSResourceRecord::ADDITIONAL, 3600);
+ return 1;
+ }
+ else if (ip == ComboAddress("192.0.2.1:53") || ip == ComboAddress("[2001:DB8::1]:53")) {
+ setLWResult(res, 0, true, false, true);
+ addRecordToLW(res, "powerdns.com.", QType::NS, "pdns-public-ns1.powerdns.com.", DNSResourceRecord::AUTHORITY, 172800);
+ addRecordToLW(res, "powerdns.com.", QType::NS, "pdns-public-ns2.powerdns.com.", DNSResourceRecord::AUTHORITY, 172800);
+ addRecordToLW(res, "pdns-public-ns1.powerdns.com.", QType::A, "192.0.2.2", DNSResourceRecord::ADDITIONAL, 172800);
+ addRecordToLW(res, "pdns-public-ns1.powerdns.com.", QType::AAAA, "2001:DB8::2", DNSResourceRecord::ADDITIONAL, 172800);
+ addRecordToLW(res, "pdns-public-ns2.powerdns.com.", QType::A, "192.0.2.3", DNSResourceRecord::ADDITIONAL, 172800);
+ addRecordToLW(res, "pdns-public-ns2.powerdns.com.", QType::AAAA, "2001:DB8::3", DNSResourceRecord::ADDITIONAL, 172800);
+ return 1;
+ }
+ else if (ip == ComboAddress("192.0.2.2:53") || ip == ComboAddress("192.0.2.3:53") || ip == ComboAddress("[2001:DB8::2]:53") || ip == ComboAddress("[2001:DB8::3]:53")) {
+ setLWResult(res, 0, true, false, true);
+ addRecordToLW(res, target, QType::A, "192.0.2.4");
+ return 1;
+ }
+ else {
+ return 0;
+ }
+ });
+
+ vector<DNSRecord> ret;
+ int res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
+ BOOST_CHECK_EQUAL(res, 0);
+ 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_glueless_referral) {
+ std::unique_ptr<SyncRes> sr;
+ init();
+ initSR(sr, true, false);
+
+ primeHints();
+
+ const DNSName target("powerdns.com.");
+
+ 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)) {
+ setLWResult(res, 0, true, false, true);
+
+ if (domain.isPartOf(DNSName("com."))) {
+ addRecordToLW(res, "com.", QType::NS, "a.gtld-servers.net.", DNSResourceRecord::AUTHORITY, 172800);
+ } else if (domain.isPartOf(DNSName("org."))) {
+ addRecordToLW(res, "org.", QType::NS, "a.gtld-servers.net.", DNSResourceRecord::AUTHORITY, 172800);
+ }
+ else {
+ setLWResult(res, RCode::NXDomain, false, false, true);
+ return 1;
+ }
+
+ addRecordToLW(res, "a.gtld-servers.net.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600);
+ addRecordToLW(res, "a.gtld-servers.net.", QType::AAAA, "2001:DB8::1", DNSResourceRecord::ADDITIONAL, 3600);
+ return 1;
+ }
+ else if (ip == ComboAddress("192.0.2.1:53") || ip == ComboAddress("[2001:DB8::1]:53")) {
+ if (domain == target) {
+ setLWResult(res, 0, true, false, true);
+ addRecordToLW(res, "powerdns.com.", QType::NS, "pdns-public-ns1.powerdns.org.", DNSResourceRecord::AUTHORITY, 172800);
+ addRecordToLW(res, "powerdns.com.", QType::NS, "pdns-public-ns2.powerdns.org.", DNSResourceRecord::AUTHORITY, 172800);
+ return 1;
+ }
+ else if (domain == DNSName("pdns-public-ns1.powerdns.org.")) {
+ setLWResult(res, 0, true, false, true);
+ addRecordToLW(res, "pdns-public-ns1.powerdns.org.", QType::A, "192.0.2.2");
+ addRecordToLW(res, "pdns-public-ns1.powerdns.org.", QType::AAAA, "2001:DB8::2");
+ return 1;
+ }
+ else if (domain == DNSName("pdns-public-ns2.powerdns.org.")) {
+ setLWResult(res, 0, true, false, true);
+ addRecordToLW(res, "pdns-public-ns2.powerdns.org.", QType::A, "192.0.2.3");
+ addRecordToLW(res, "pdns-public-ns2.powerdns.org.", QType::AAAA, "2001:DB8::3");
+ return 1;
+ }
+
+ setLWResult(res, RCode::NXDomain, false, false, true);
+ return 1;
+ }
+ else if (ip == ComboAddress("192.0.2.2:53") || ip == ComboAddress("192.0.2.3:53") || ip == ComboAddress("[2001:DB8::2]:53") || ip == ComboAddress("[2001:DB8::3]:53")) {
+ setLWResult(res, 0, true, false, true);
+ addRecordToLW(res, target, QType::A, "192.0.2.4");
+ return 1;
+ }
+ else {
+ return 0;
+ }
+ });
+
+ vector<DNSRecord> ret;
+ int res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
+ BOOST_CHECK_EQUAL(res, 0);
+ 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_edns_submask_by_domain) {
+ std::unique_ptr<SyncRes> sr;
+ init();
+ initSR(sr, true, false);
+
+ primeHints();
+
+ const DNSName target("powerdns.com.");
+ g_useIncomingECS = true;
+ g_ednsdomains.add(target);
+
+ EDNSSubnetOpts incomingECS;
+ incomingECS.source = Netmask("192.0.2.128/32");
+ sr->setIncomingECSFound(true);
+ sr->setIncomingECS(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) {
+
+ BOOST_REQUIRE(srcmask);
+ BOOST_CHECK_EQUAL(srcmask->toString(), "192.0.2.0/24");
+ return 0;
+ });
+
+ vector<DNSRecord> ret;
+ int res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
+ BOOST_CHECK_EQUAL(res, RCode::ServFail);
+}
+
+BOOST_AUTO_TEST_CASE(test_edns_submask_by_addr) {
+ std::unique_ptr<SyncRes> sr;
+ init();
+ initSR(sr, true, false);
+
+ primeHints();
+
+ const DNSName target("powerdns.com.");
+ g_useIncomingECS = true;
+ g_ednssubnets.addMask("192.0.2.1/32");
+
+ EDNSSubnetOpts incomingECS;
+ incomingECS.source = Netmask("2001:DB8::FF/128");
+ sr->setIncomingECSFound(true);
+ sr->setIncomingECS(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, true, 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(), "2001:db8::/56");
+
+ 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, 0);
+ 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;
+ init();
+ initSR(sr, true, false);
+
+ primeHints();
+
+ const DNSName target("cname.powerdns.com.");
+ const DNSName cnameTarget("cname-target.powerdns.com");
+
+ 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) {
+
+ if (isRootServer(ip)) {
+ setLWResult(res, 0, true, 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")) {
+
+ if (domain == target) {
+ setLWResult(res, 0, true, false, false);
+ addRecordToLW(res, domain, QType::CNAME, cnameTarget.toString());
+ return 1;
+ }
+ else if (domain == cnameTarget) {
+ 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, 0);
+ BOOST_REQUIRE_EQUAL(ret.size(), 2);
+ BOOST_CHECK(ret[0].d_type == QType::CNAME);
+ BOOST_CHECK_EQUAL(ret[0].d_name, target);
+ BOOST_CHECK(ret[1].d_type == QType::A);
+ BOOST_CHECK_EQUAL(ret[1].d_name, cnameTarget);
+}
+
+BOOST_AUTO_TEST_CASE(test_included_poisonous_cname) {
+ std::unique_ptr<SyncRes> sr;
+ init();
+ initSR(sr, true, false);
+
+ primeHints();
+
+ /* In this test we directly get the NS server for cname.powerdns.com.,
+ and we don't know whether it's also authoritative for
+ cname-target.powerdns.com or powerdns.com, so we shouldn't accept
+ the additional A record for cname-target.powerdns.com. */
+ const DNSName target("cname.powerdns.com.");
+ const DNSName cnameTarget("cname-target.powerdns.com");
+
+ 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) {
+
+ if (isRootServer(ip)) {
+
+ setLWResult(res, 0, true, 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")) {
+
+ if (domain == target) {
+ setLWResult(res, 0, true, false, false);
+ addRecordToLW(res, domain, QType::CNAME, cnameTarget.toString());
+ addRecordToLW(res, cnameTarget, QType::A, "192.0.2.2", DNSResourceRecord::ADDITIONAL);
+ return 1;
+ } else if (domain == cnameTarget) {
+ setLWResult(res, 0, true, false, false);
+ addRecordToLW(res, cnameTarget, QType::A, "192.0.2.3");
+ return 1;
+ }
+
+ return 1;
+ }
+
+ return 0;
+ });
+
+ vector<DNSRecord> ret;
+ int res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
+ BOOST_CHECK_EQUAL(res, 0);
+ BOOST_REQUIRE_EQUAL(ret.size(), 2);
+ BOOST_REQUIRE(ret[0].d_type == QType::CNAME);
+ BOOST_CHECK_EQUAL(ret[0].d_name, target);
+ BOOST_CHECK_EQUAL(getRR<CNAMERecordContent>(ret[0])->getTarget(), cnameTarget);
+ BOOST_REQUIRE(ret[1].d_type == QType::A);
+ BOOST_CHECK_EQUAL(ret[1].d_name, cnameTarget);
+ BOOST_CHECK(getRR<ARecordContent>(ret[1])->getCA() == ComboAddress("192.0.2.3"));
+}
+
+BOOST_AUTO_TEST_CASE(test_cname_loop) {
+ std::unique_ptr<SyncRes> sr;
+ init();
+ initSR(sr, true, false);
+
+ primeHints();
+
+ size_t count = 0;
+ const DNSName target("cname.powerdns.com.");
+
+ sr->setAsyncCallback([target,&count](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) {
+
+ count++;
+
+ if (isRootServer(ip)) {
+
+ setLWResult(res, 0, true, 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")) {
+
+ if (domain == target) {
+ setLWResult(res, 0, true, false, false);
+ addRecordToLW(res, domain, QType::CNAME, domain.toString());
+ return 1;
+ }
+
+ return 1;
+ }
+
+ return 0;
+ });
+
+ vector<DNSRecord> ret;
+ int res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
+ BOOST_CHECK_EQUAL(res, RCode::ServFail);
+ BOOST_CHECK_GT(ret.size(), 0);
+ BOOST_CHECK_EQUAL(count, 2);
+}
+
+BOOST_AUTO_TEST_CASE(test_cname_depth) {
+ std::unique_ptr<SyncRes> sr;
+ init();
+ initSR(sr, true, false);
+
+ primeHints();
+
+ size_t depth = 0;
+ const DNSName target("cname.powerdns.com.");
+
+ sr->setAsyncCallback([target,&depth](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)) {
+
+ setLWResult(res, 0, true, 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")) {
+
+ setLWResult(res, 0, true, false, false);
+ addRecordToLW(res, domain, QType::CNAME, std::to_string(depth) + "-cname.powerdns.com");
+ depth++;
+ return 1;
+ }
+
+ return 0;
+ });
+
+ vector<DNSRecord> ret;
+ int res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
+ BOOST_CHECK_EQUAL(res, RCode::ServFail);
+ BOOST_CHECK_EQUAL(ret.size(), depth);
+ /* we have an arbitrary limit at 10 when following a CNAME chain */
+ BOOST_CHECK_EQUAL(depth, 10 + 2);
+}
+
+BOOST_AUTO_TEST_CASE(test_time_limit) {
+ std::unique_ptr<SyncRes> sr;
+ init();
+ initSR(sr, true, false);
+
+ primeHints();
+
+ size_t queries = 0;
+ const DNSName target("cname.powerdns.com.");
+
+ sr->setAsyncCallback([target,&queries](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) {
+
+ queries++;
+
+ if (isRootServer(ip)) {
+ setLWResult(res, 0, true, false, true);
+ /* Pretend that this query took 2000 ms */
+ res->d_usec = 2000;
+
+ 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")) {
+
+ setLWResult(res, 0, true, false, false);
+ addRecordToLW(res, domain, QType::A, "192.0.2.2");
+ return 1;
+ }
+
+ return 0;
+ });
+
+ /* Set the maximum time to 1 ms */
+ SyncRes::s_maxtotusec = 1000;
+
+ try {
+ vector<DNSRecord> ret;
+ sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
+ BOOST_CHECK(false);
+ }
+ catch(const ImmediateServFailException& e) {
+ }
+ BOOST_CHECK_EQUAL(queries, 1);
+}
+
+BOOST_AUTO_TEST_CASE(test_referral_depth) {
+ std::unique_ptr<SyncRes> sr;
+ init();
+ initSR(sr, true, false);
+
+ primeHints();
+
+ size_t queries = 0;
+ const DNSName target("www.powerdns.com.");
+
+ sr->setAsyncCallback([target,&queries](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) {
+
+ queries++;
+
+ if (isRootServer(ip)) {
+ setLWResult(res, 0, true, false, true);
+
+ if (domain == DNSName("www.powerdns.com.")) {
+ addRecordToLW(res, domain, QType::NS, "ns.powerdns.com.", DNSResourceRecord::AUTHORITY, 172800);
+ }
+ else if (domain == DNSName("ns.powerdns.com.")) {
+ addRecordToLW(res, domain, QType::NS, "ns1.powerdns.org.", DNSResourceRecord::AUTHORITY, 172800);
+ }
+ else if (domain == DNSName("ns1.powerdns.org.")) {
+ addRecordToLW(res, domain, QType::NS, "ns2.powerdns.org.", DNSResourceRecord::AUTHORITY, 172800);
+ }
+ else if (domain == DNSName("ns2.powerdns.org.")) {
+ addRecordToLW(res, domain, QType::NS, "ns3.powerdns.org.", DNSResourceRecord::AUTHORITY, 172800);
+ }
+ else if (domain == DNSName("ns3.powerdns.org.")) {
+ addRecordToLW(res, domain, QType::NS, "ns4.powerdns.org.", DNSResourceRecord::AUTHORITY, 172800);
+ }
+ else if (domain == DNSName("ns4.powerdns.org.")) {
+ addRecordToLW(res, domain, QType::NS, "ns5.powerdns.org.", DNSResourceRecord::AUTHORITY, 172800);
+ addRecordToLW(res, domain, QType::A, "192.0.2.1", DNSResourceRecord::AUTHORITY, 172800);
+ }
+
+ return 1;
+ } else if (ip == ComboAddress("192.0.2.1:53")) {
+
+ setLWResult(res, 0, true, false, false);
+ addRecordToLW(res, domain, QType::A, "192.0.2.2");
+ return 1;
+ }
+
+ return 0;
+ });
+
+ /* Set the maximum depth low */
+ SyncRes::s_maxdepth = 10;
+
+ try {
+ vector<DNSRecord> ret;
+ sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
+ BOOST_CHECK(false);
+ }
+ catch(const ImmediateServFailException& e) {
+ }
+}
+
+BOOST_AUTO_TEST_CASE(test_cname_qperq) {
+ std::unique_ptr<SyncRes> sr;
+ init();
+ initSR(sr, true, false);
+
+ primeHints();
+
+ size_t queries = 0;
+ const DNSName target("cname.powerdns.com.");
+
+ sr->setAsyncCallback([target,&queries](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) {
+
+ queries++;
+
+ if (isRootServer(ip)) {
+
+ setLWResult(res, 0, true, 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")) {
+
+ setLWResult(res, 0, true, false, false);
+ addRecordToLW(res, domain, QType::CNAME, std::to_string(queries) + "-cname.powerdns.com");
+ return 1;
+ }
+
+ return 0;
+ });
+
+ /* Set the maximum number of questions very low */
+ SyncRes::s_maxqperq = 5;
+
+ try {
+ vector<DNSRecord> ret;
+ sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
+ BOOST_CHECK(false);
+ }
+ catch(const ImmediateServFailException& e) {
+ BOOST_CHECK_EQUAL(queries, SyncRes::s_maxqperq);
+ }
+}
+
+BOOST_AUTO_TEST_CASE(test_throttled_server) {
+ std::unique_ptr<SyncRes> sr;
+ init();
+ initSR(sr, true, false);
+
+ primeHints();
+
+ const DNSName target("throttled.powerdns.com.");
+ const ComboAddress ns("192.0.2.1:53");
+ size_t queriesToNS = 0;
+
+ sr->setAsyncCallback([target,ns,&queriesToNS](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)) {
+
+ setLWResult(res, 0, true, false, true);
+ addRecordToLW(res, domain, QType::NS, "a.gtld-servers.net.", DNSResourceRecord::AUTHORITY, 172800);
+ addRecordToLW(res, "a.gtld-servers.net.", QType::A, ns.toString(), DNSResourceRecord::ADDITIONAL, 3600);
+ return 1;
+ } else if (ip == ns) {
+
+ queriesToNS++;
+
+ setLWResult(res, 0, true, false, false);
+ addRecordToLW(res, domain, QType::A, "192.0.2.2");
+
+ return 1;
+ }
+
+ return 0;
+ });
+
+ /* mark ns as down */
+ t_sstorage->throttle.throttle(time(nullptr), boost::make_tuple(ns, "", 0), SyncRes::s_serverdownthrottletime, 10000);
+
+ vector<DNSRecord> ret;
+ int res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
+ BOOST_CHECK_EQUAL(res, RCode::ServFail);
+ BOOST_CHECK_EQUAL(ret.size(), 0);
+ /* we should not have sent any queries to ns */
+ BOOST_CHECK_EQUAL(queriesToNS, 0);
+}
+
+BOOST_AUTO_TEST_CASE(test_throttled_server_count) {
+ std::unique_ptr<SyncRes> sr;
+ init();
+ initSR(sr, true, false);
+
+ primeHints();
+
+ const ComboAddress ns("192.0.2.1:53");
+
+ const size_t blocks = 10;
+ /* mark ns as down for 'blocks' queries */
+ t_sstorage->throttle.throttle(time(nullptr), boost::make_tuple(ns, "", 0), SyncRes::s_serverdownthrottletime, blocks);
+
+ for (size_t idx = 0; idx < blocks; idx++) {
+ BOOST_CHECK(t_sstorage->throttle.shouldThrottle(time(nullptr), boost::make_tuple(ns, "", 0)));
+ }
+
+ /* we have been throttled 'blocks' times, we should not be throttled anymore */
+ BOOST_CHECK(!t_sstorage->throttle.shouldThrottle(time(nullptr), boost::make_tuple(ns, "", 0)));
+}
+
+BOOST_AUTO_TEST_CASE(test_throttled_server_time) {
+ std::unique_ptr<SyncRes> sr;
+ init();
+ initSR(sr, true, false);
+
+ primeHints();
+
+ const ComboAddress ns("192.0.2.1:53");
+
+ const size_t seconds = 1;
+ /* mark ns as down for 'seconds' seconds */
+ t_sstorage->throttle.throttle(time(nullptr), boost::make_tuple(ns, "", 0), seconds, 10000);
+ BOOST_CHECK(t_sstorage->throttle.shouldThrottle(time(nullptr), boost::make_tuple(ns, "", 0)));
+
+ sleep(seconds + 1);
+
+ /* we should not be throttled anymore */
+ BOOST_CHECK(!t_sstorage->throttle.shouldThrottle(time(nullptr), boost::make_tuple(ns, "", 0)));
+}
+
+BOOST_AUTO_TEST_CASE(test_dont_query_server) {
+ std::unique_ptr<SyncRes> sr;
+ init();
+ initSR(sr, true, false);
+
+ primeHints();
+
+ const DNSName target("throttled.powerdns.com.");
+ const ComboAddress ns("192.0.2.1:53");
+ size_t queriesToNS = 0;
+
+ sr->setAsyncCallback([target,ns,&queriesToNS](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)) {
+
+ setLWResult(res, 0, true, false, true);
+ addRecordToLW(res, domain, QType::NS, "a.gtld-servers.net.", DNSResourceRecord::AUTHORITY, 172800);
+ addRecordToLW(res, "a.gtld-servers.net.", QType::A, ns.toString(), DNSResourceRecord::ADDITIONAL, 3600);
+ return 1;
+ } else if (ip == ns) {
+
+ queriesToNS++;
+
+ setLWResult(res, 0, true, false, false);
+ addRecordToLW(res, domain, QType::A, "192.0.2.2");
+
+ return 1;
+ }
+
+ return 0;
+ });
+
+ /* prevent querying this NS */
+ g_dontQuery->addMask(Netmask(ns));
+
+ vector<DNSRecord> ret;
+ int res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
+ BOOST_CHECK_EQUAL(res, RCode::ServFail);
+ BOOST_CHECK_EQUAL(ret.size(), 0);
+ /* we should not have sent any queries to ns */
+ BOOST_CHECK_EQUAL(queriesToNS, 0);
+}
+
+BOOST_AUTO_TEST_CASE(test_root_nx_trust) {
+ std::unique_ptr<SyncRes> sr;
+ init();
+ initSR(sr, true, false);
+
+ primeHints();
+
+ const DNSName target1("powerdns.com.");
+ const DNSName target2("notpowerdns.com.");
+ const ComboAddress ns("192.0.2.1:53");
+ size_t queriesCount = 0;
+
+ sr->setAsyncCallback([target1, target2, ns, &queriesCount](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 (isRootServer(ip)) {
+
+ if (domain == target1) {
+ setLWResult(res, RCode::NXDomain, true, false, true);
+ addRecordToLW(res, ".", QType::SOA, "a.root-servers.net. nstld.verisign-grs.com. 2017032800 1800 900 604800 86400", DNSResourceRecord::AUTHORITY, 86400);
+ }
+ else {
+ setLWResult(res, 0, true, false, true);
+ addRecordToLW(res, domain, QType::NS, "a.gtld-servers.net.", DNSResourceRecord::AUTHORITY, 172800);
+ addRecordToLW(res, "a.gtld-servers.net.", QType::A, ns.toString(), DNSResourceRecord::ADDITIONAL, 3600);
+ }
+
+ return 1;
+ } else if (ip == ns) {
+
+ 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(target1, QType(QType::A), QClass::IN, ret);
+ BOOST_CHECK_EQUAL(res, RCode::NXDomain);
+ BOOST_CHECK_EQUAL(ret.size(), 1);
+ /* one for target1 and one for the entire TLD */
+ BOOST_CHECK_EQUAL(t_sstorage->negcache.size(), 2);
+
+ ret.clear();
+ res = sr->beginResolve(target2, QType(QType::A), QClass::IN, ret);
+ BOOST_CHECK_EQUAL(res, RCode::NXDomain);
+ BOOST_CHECK_EQUAL(ret.size(), 1);
+ /* one for target1 and one for the entire TLD */
+ BOOST_CHECK_EQUAL(t_sstorage->negcache.size(), 2);
+
+ /* we should have sent only one query */
+ BOOST_CHECK_EQUAL(queriesCount, 1);
+}
+
+BOOST_AUTO_TEST_CASE(test_root_nx_dont_trust) {
+ std::unique_ptr<SyncRes> sr;
+ init();
+ initSR(sr, true, false);
+
+ primeHints();
+
+ const DNSName target1("powerdns.com.");
+ const DNSName target2("notpowerdns.com.");
+ const ComboAddress ns("192.0.2.1:53");
+ size_t queriesCount = 0;
+
+ sr->setAsyncCallback([target1, target2, ns, &queriesCount](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 (isRootServer(ip)) {
+
+ if (domain == target1) {
+ setLWResult(res, RCode::NXDomain, true, false, true);
+ addRecordToLW(res, ".", QType::SOA, "a.root-servers.net. nstld.verisign-grs.com. 2017032800 1800 900 604800 86400", DNSResourceRecord::AUTHORITY, 86400);
+ }
+ else {
+ setLWResult(res, 0, true, false, true);
+ addRecordToLW(res, domain, QType::NS, "a.gtld-servers.net.", DNSResourceRecord::AUTHORITY, 172800);
+ addRecordToLW(res, "a.gtld-servers.net.", QType::A, ns.toString(), DNSResourceRecord::ADDITIONAL, 3600);
+ }
+
+ return 1;
+ } else if (ip == ns) {
+
+ setLWResult(res, 0, true, false, false);
+ addRecordToLW(res, domain, QType::A, "192.0.2.2");
+
+ return 1;
+ }
+
+ return 0;
+ });
+
+ SyncRes::s_rootNXTrust = false;
+
+ vector<DNSRecord> ret;
+ int res = sr->beginResolve(target1, QType(QType::A), QClass::IN, ret);
+ BOOST_CHECK_EQUAL(res, RCode::NXDomain);
+ BOOST_CHECK_EQUAL(ret.size(), 1);
+ /* one for target1 */
+ BOOST_CHECK_EQUAL(t_sstorage->negcache.size(), 1);
+
+ ret.clear();
+ res = sr->beginResolve(target2, QType(QType::A), QClass::IN, ret);
+ BOOST_CHECK_EQUAL(res, 0);
+ BOOST_CHECK_EQUAL(ret.size(), 1);
+ /* one for target1 */
+ BOOST_CHECK_EQUAL(t_sstorage->negcache.size(), 1);
+
+ /* we should have sent three queries */
+ BOOST_CHECK_EQUAL(queriesCount, 3);
+}
+
+BOOST_AUTO_TEST_CASE(test_skip_negcache_for_variable_response) {
+ std::unique_ptr<SyncRes> sr;
+ init();
+ initSR(sr, true, false);
+
+ primeHints();
+
+ const DNSName target("www.powerdns.com.");
+ const DNSName cnameTarget("cname.powerdns.com.");
+
+ g_useIncomingECS = true;
+ g_ednsdomains.add(DNSName("powerdns.com."));
+
+ EDNSSubnetOpts incomingECS;
+ incomingECS.source = Netmask("192.0.2.128/32");
+ sr->setIncomingECSFound(true);
+ sr->setIncomingECS(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) {
+
+ BOOST_REQUIRE(srcmask);
+ BOOST_CHECK_EQUAL(srcmask->toString(), "192.0.2.0/24");
+
+ if (isRootServer(ip)) {
+ setLWResult(res, 0, true, false, true);
+ addRecordToLW(res, "powerdns.com.", QType::NS, "pdns-public-ns1.powerdns.com.", DNSResourceRecord::AUTHORITY, 172800);
+ addRecordToLW(res, "pdns-public-ns1.powerdns.com.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600);
+
+ return 1;
+ } else if (ip == ComboAddress("192.0.2.1:53")) {
+ if (domain == target) {
+ /* Type 2 NXDOMAIN (rfc2308 section-2.1) */
+ setLWResult(res, RCode::NXDomain, true, false, true);
+ addRecordToLW(res, domain, QType::CNAME, cnameTarget.toString());
+ addRecordToLW(res, "powerdns.com", QType::SOA, "pdns-public-ns1.powerdns.com. pieter\\.lexis.powerdns.com. 2017032301 10800 3600 604800 3600", DNSResourceRecord::AUTHORITY, 3600);
+ }
+ else if (domain == cnameTarget) {
+ /* we shouldn't get there since the Type NXDOMAIN should have been enough,
+ but we might if we still chase the CNAME. */
+ setLWResult(res, RCode::NXDomain, true, false, true);
+ addRecordToLW(res, "powerdns.com", QType::SOA, "pdns-public-ns1.powerdns.com. pieter\\.lexis.powerdns.com. 2017032301 10800 3600 604800 3600", DNSResourceRecord::AUTHORITY, 3600);
+ }
+
+ return 1;
+ }
+
+ return 0;
+ });
+
+ vector<DNSRecord> ret;
+ int res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
+ BOOST_CHECK_EQUAL(res, RCode::NXDomain);
+ BOOST_CHECK_EQUAL(ret.size(), 2);
+ /* no negative cache entry because the response was variable */
+ BOOST_CHECK_EQUAL(t_sstorage->negcache.size(), 0);
+}
+
+BOOST_AUTO_TEST_CASE(test_ns_speed) {
+ std::unique_ptr<SyncRes> sr;
+ init();
+ initSR(sr, true, false);
+
+ primeHints();
+
+ const DNSName target("powerdns.com.");
+
+ std::map<ComboAddress, uint64_t> nsCounts;
+
+ sr->setAsyncCallback([target,&nsCounts](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)) {
+ setLWResult(res, 0, true, false, true);
+ addRecordToLW(res, domain, QType::NS, "pdns-public-ns1.powerdns.com.", DNSResourceRecord::AUTHORITY, 172800);
+ addRecordToLW(res, domain, QType::NS, "pdns-public-ns2.powerdns.com.", DNSResourceRecord::AUTHORITY, 172800);
+ addRecordToLW(res, domain, QType::NS, "pdns-public-ns3.powerdns.com.", DNSResourceRecord::AUTHORITY, 172800);
+
+ addRecordToLW(res, "pdns-public-ns1.powerdns.com.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600);
+ addRecordToLW(res, "pdns-public-ns1.powerdns.com.", QType::AAAA, "2001:DB8::1", DNSResourceRecord::ADDITIONAL, 3600);
+ addRecordToLW(res, "pdns-public-ns2.powerdns.com.", QType::A, "192.0.2.2", DNSResourceRecord::ADDITIONAL, 3600);
+ addRecordToLW(res, "pdns-public-ns2.powerdns.com.", QType::AAAA, "2001:DB8::2", DNSResourceRecord::ADDITIONAL, 3600);
+ addRecordToLW(res, "pdns-public-ns3.powerdns.com.", QType::A, "192.0.2.3", DNSResourceRecord::ADDITIONAL, 3600);
+ addRecordToLW(res, "pdns-public-ns3.powerdns.com.", QType::AAAA, "2001:DB8::3", DNSResourceRecord::ADDITIONAL, 3600);
+
+ return 1;
+ } else {
+ nsCounts[ip]++;
+
+ if (ip == ComboAddress("[2001:DB8::2]:53") || ip == ComboAddress("192.0.2.2:53")) {
+ BOOST_CHECK_LT(nsCounts.size(), 3);
+
+ /* let's time out on pdns-public-ns2.powerdns.com. */
+ return 0;
+ }
+ else if (ip == ComboAddress("192.0.2.1:53")) {
+ BOOST_CHECK_EQUAL(nsCounts.size(), 3);
+
+ setLWResult(res, 0, true, false, true);
+ addRecordToLW(res, domain, QType::A, "192.0.2.254");
+ return 1;
+ }
+
+ return 0;
+ }
+
+ return 0;
+ });
+
+ struct timeval now;
+ gettimeofday(&now, 0);
+
+ /* 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 */
+ t_sstorage->nsSpeeds[DNSName("pdns-public-ns1.powerdns.com.")].submit(ComboAddress("192.0.2.1:53"), 100, &now);
+ t_sstorage->nsSpeeds[DNSName("pdns-public-ns1.powerdns.com.")].submit(ComboAddress("[2001:DB8::1]:53"), 10000, &now);
+ t_sstorage->nsSpeeds[DNSName("pdns-public-ns2.powerdns.com.")].submit(ComboAddress("192.0.2.2:53"), 10, &now);
+ t_sstorage->nsSpeeds[DNSName("pdns-public-ns2.powerdns.com.")].submit(ComboAddress("[2001:DB8::2]:53"), 1, &now);
+ t_sstorage->nsSpeeds[DNSName("pdns-public-ns3.powerdns.com.")].submit(ComboAddress("192.0.2.3:53"), 10000, &now);
+ t_sstorage->nsSpeeds[DNSName("pdns-public-ns3.powerdns.com.")].submit(ComboAddress("[2001:DB8::3]:53"), 10000, &now);
+
+ vector<DNSRecord> ret;
+ int res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
+ BOOST_CHECK_EQUAL(res, 0);
+ BOOST_CHECK_EQUAL(ret.size(), 1);
+ BOOST_CHECK_EQUAL(nsCounts.size(), 3);
+ BOOST_CHECK_EQUAL(nsCounts[ComboAddress("192.0.2.1:53")], 1);
+ BOOST_CHECK_EQUAL(nsCounts[ComboAddress("192.0.2.2:53")], 1);
+ BOOST_CHECK_EQUAL(nsCounts[ComboAddress("[2001:DB8::2]:53")], 1);
+}
+
+BOOST_AUTO_TEST_CASE(test_flawed_nsset) {
+ std::unique_ptr<SyncRes> sr;
+ init();
+ initSR(sr, true, false);
+
+ primeHints();
+
+ const DNSName target("powerdns.com.");
+
+ 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)) {
+ setLWResult(res, 0, true, false, true);
+ addRecordToLW(res, domain, QType::NS, "pdns-public-ns1.powerdns.com.", DNSResourceRecord::AUTHORITY, 172800);
+
+ addRecordToLW(res, "pdns-public-ns1.powerdns.com.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600);
+
+ return 1;
+ } else if (ip == ComboAddress("192.0.2.1:53")) {
+ setLWResult(res, 0, true, false, true);
+ addRecordToLW(res, domain, QType::A, "192.0.2.254");
+ return 1;
+ }
+
+ return 0;
+ });
+
+ /* we populate the cache with a flawed NSset, i.e. there is a NS entry but no corresponding glue */
+ time_t now = time(nullptr);
+ std::vector<DNSRecord> records;
+ std::vector<shared_ptr<RRSIGRecordContent> > sigs;
+ addRecordToList(records, target, QType::NS, "pdns-public-ns1.powerdns.com.", DNSResourceRecord::AUTHORITY, now + 3600);
+
+ t_RC->replace(now, target, QType(QType::NS), records, sigs, true, boost::optional<Netmask>());
+
+ vector<DNSRecord> ret;
+ int res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
+ BOOST_CHECK_EQUAL(res, 0);
+ BOOST_CHECK_EQUAL(ret.size(), 1);
+}
+
+BOOST_AUTO_TEST_CASE(test_cache_hit) {
+ std::unique_ptr<SyncRes> sr;
+ init();
+ initSR(sr, true, false);
+
+ primeHints();
+
+ const DNSName target("powerdns.com.");
+
+ 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) {
+
+ return 0;
+ });
+
+ /* we populate the cache with eveything we need */
+ time_t now = time(nullptr);
+ std::vector<DNSRecord> records;
+ std::vector<shared_ptr<RRSIGRecordContent> > sigs;
+
+ addRecordToList(records, target, QType::A, "192.0.2.1", DNSResourceRecord::ANSWER, now + 3600);
+ t_RC->replace(now, target , QType(QType::A), records, sigs, true, boost::optional<Netmask>());
+
+ vector<DNSRecord> ret;
+ int res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
+ BOOST_CHECK_EQUAL(res, 0);
+ BOOST_CHECK_EQUAL(ret.size(), 1);
+}
+
+BOOST_AUTO_TEST_CASE(test_no_rd) {
+ std::unique_ptr<SyncRes> sr;
+ init();
+ initSR(sr, true, false);
+
+ primeHints();
+
+ const DNSName target("powerdns.com.");
+ size_t queriesCount = 0;
+
+ sr->setCacheOnly();
+
+ sr->setAsyncCallback([target,&queriesCount](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++;
+ return 0;
+ });
+
+ vector<DNSRecord> ret;
+ int res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
+ BOOST_CHECK_EQUAL(res, 0);
+ BOOST_CHECK_EQUAL(ret.size(), 0);
+ BOOST_CHECK_EQUAL(queriesCount, 0);
+}
+
+BOOST_AUTO_TEST_CASE(test_cache_min_max_ttl) {
+ std::unique_ptr<SyncRes> sr;
+ init(false);
+ initSR(sr, true, false);
+
+ primeHints();
+
+ const DNSName target("cachettl.powerdns.com.");
+ const ComboAddress ns("192.0.2.1:53");
+
+ sr->setAsyncCallback([target,ns](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)) {
+
+ setLWResult(res, 0, true, false, true);
+ addRecordToLW(res, domain, QType::NS, "a.gtld-servers.net.", DNSResourceRecord::AUTHORITY, 172800);
+ addRecordToLW(res, "a.gtld-servers.net.", QType::A, ns.toString(), DNSResourceRecord::ADDITIONAL, 7200);
+ return 1;
+ } else if (ip == ns) {
+
+ setLWResult(res, 0, true, false, false);
+ addRecordToLW(res, domain, QType::A, "192.0.2.2", DNSResourceRecord::ANSWER, 10);
+
+ return 1;
+ }
+
+ return 0;
+ });
+
+ SyncRes::s_minimumTTL = 60;
+ SyncRes::s_maxcachettl = 3600;
+
+ vector<DNSRecord> ret;
+ int res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
+ BOOST_CHECK_EQUAL(res, 0);
+ BOOST_REQUIRE_EQUAL(ret.size(), 1);
+ BOOST_CHECK_EQUAL(ret[0].d_ttl, SyncRes::s_minimumTTL);
+
+ const ComboAddress who;
+ vector<DNSRecord> cached;
+ const time_t now = time(nullptr);
+ BOOST_REQUIRE_GT(t_RC->get(now, target, QType(QType::A), &cached, who), 0);
+ BOOST_REQUIRE_EQUAL(cached.size(), 1);
+ BOOST_REQUIRE_GT(cached[0].d_ttl, now);
+ BOOST_CHECK_EQUAL((cached[0].d_ttl - now), SyncRes::s_minimumTTL);
+
+ cached.clear();
+ BOOST_REQUIRE_GT(t_RC->get(now, target, QType(QType::NS), &cached, who), 0);
+ BOOST_REQUIRE_EQUAL(cached.size(), 1);
+ BOOST_REQUIRE_GT(cached[0].d_ttl, now);
+ BOOST_CHECK_LE((cached[0].d_ttl - now), SyncRes::s_maxcachettl);
+}
+
+BOOST_AUTO_TEST_CASE(test_cache_expired_ttl) {
+ std::unique_ptr<SyncRes> sr;
+ init(false);
+ initSR(sr, true, false);
+
+ primeHints();
+
+ const DNSName target("powerdns.com.");
+
+ 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)) {
+ setLWResult(res, 0, true, false, true);
+ addRecordToLW(res, domain, QType::NS, "pdns-public-ns1.powerdns.com.", DNSResourceRecord::AUTHORITY, 172800);
+
+ addRecordToLW(res, "pdns-public-ns1.powerdns.com.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600);
+
+ return 1;
+ } else if (ip == ComboAddress("192.0.2.1:53")) {
+ setLWResult(res, 0, true, false, true);
+ addRecordToLW(res, domain, QType::A, "192.0.2.2");
+ return 1;
+ }
+
+ return 0;
+ });
+
+ /* we populate the cache with entries that expired 60s ago*/
+ time_t now = time(nullptr);
+ std::vector<DNSRecord> records;
+ std::vector<shared_ptr<RRSIGRecordContent> > sigs;
+ addRecordToList(records, target, QType::A, "192.0.2.42", DNSResourceRecord::ANSWER, now - 60);
+
+ t_RC->replace(now - 3600, target, QType(QType::A), records, sigs, true, boost::optional<Netmask>());
+
+ vector<DNSRecord> ret;
+ int res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
+ BOOST_CHECK_EQUAL(res, 0);
+ BOOST_REQUIRE_EQUAL(ret.size(), 1);
+ BOOST_REQUIRE(ret[0].d_type == QType::A);
+ BOOST_CHECK_EQUAL(getRR<ARecordContent>(ret[0])->getCA().toStringWithPort(), ComboAddress("192.0.2.2").toStringWithPort());
+}
+
+BOOST_AUTO_TEST_CASE(test_delegation_only) {
+ std::unique_ptr<SyncRes> sr;
+ init();
+ initSR(sr, true, false);
+
+ primeHints();
+
+ /* Thanks, Verisign */
+ g_delegationOnly.insert(DNSName("com."));
+ g_delegationOnly.insert(DNSName("net."));
+
+ const DNSName target("nx-powerdns.com.");
+
+ 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)) {
+ setLWResult(res, 0, true, false, true);
+ addRecordToLW(res, "com.", 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")) {
+
+ setLWResult(res, 0, true, false, true);
+ addRecordToLW(res, domain, QType::A, "192.0.2.42");
+ return 1;
+ }
+
+ return 0;
+ });
+
+ vector<DNSRecord> ret;
+ int res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
+ BOOST_CHECK_EQUAL(res, RCode::NXDomain);
+ BOOST_CHECK_EQUAL(ret.size(), 0);
+}
+
+BOOST_AUTO_TEST_CASE(test_unauth_any) {
+ std::unique_ptr<SyncRes> sr;
+ init();
+ initSR(sr, true, false);
+
+ primeHints();
+
+ const DNSName target("powerdns.com.");
+
+ 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)) {
+ setLWResult(res, 0, true, false, true);
+ addRecordToLW(res, "com.", 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")) {
+
+ setLWResult(res, 0, false, false, true);
+ addRecordToLW(res, domain, QType::A, "192.0.2.42");
+ return 1;
+ }
+
+ return 0;
+ });
+
+ vector<DNSRecord> ret;
+ int res = sr->beginResolve(target, QType(QType::ANY), QClass::IN, ret);
+ BOOST_CHECK_EQUAL(res, RCode::ServFail);
+ BOOST_CHECK_EQUAL(ret.size(), 0);
+}
+
+BOOST_AUTO_TEST_CASE(test_no_data) {
+ std::unique_ptr<SyncRes> sr;
+ init();
+ initSR(sr, true, false);
+
+ primeHints();
+
+ const DNSName target("powerdns.com.");
+
+ 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) {
+
+ setLWResult(res, 0, true, false, true);
+ return 1;
+ });
+
+ vector<DNSRecord> ret;
+ int res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
+ BOOST_CHECK_EQUAL(res, 0);
+ BOOST_CHECK_EQUAL(ret.size(), 0);
+}
+
+BOOST_AUTO_TEST_CASE(test_skip_opt_any) {
+ std::unique_ptr<SyncRes> sr;
+ init();
+ initSR(sr, true, false);
+
+ primeHints();
+
+ const DNSName target("powerdns.com.");
+
+ 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) {
+
+ setLWResult(res, 0, true, false, true);
+ addRecordToLW(res, domain, QType::A, "192.0.2.42");
+ addRecordToLW(res, domain, QType::ANY, "0 0");
+ addRecordToLW(res, domain, QType::OPT, "");
+ return 1;
+ });
+
+ vector<DNSRecord> ret;
+ int res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
+ BOOST_CHECK_EQUAL(res, 0);
+ BOOST_CHECK_EQUAL(ret.size(), 1);
+}
+
+BOOST_AUTO_TEST_CASE(test_nodata_nsec_nodnssec) {
+ std::unique_ptr<SyncRes> sr;
+ init();
+ initSR(sr, true, false);
+
+ primeHints();
+
+ const DNSName target("powerdns.com.");
+
+ 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) {
+
+ setLWResult(res, 0, true, false, true);
+ addRecordToLW(res, domain, QType::SOA, "pdns-public-ns1.powerdns.com. pieter\\.lexis.powerdns.com. 2017032301 10800 3600 604800 3600", DNSResourceRecord::AUTHORITY, 3600);
+ /* the NSEC and RRSIG contents are complete garbage, please ignore them */
+ addRecordToLW(res, domain, QType::NSEC, "deadbeef", DNSResourceRecord::AUTHORITY);
+ addRecordToLW(res, domain, QType::RRSIG, "NSEC 5 3 600 2100010100000000 2100010100000000 24567 dummy data", DNSResourceRecord::AUTHORITY);
+ addRecordToLW(res, domain, QType::RRSIG, "SOA 5 3 600 2100010100000000 2100010100000000 24567 dummy data", DNSResourceRecord::AUTHORITY);
+ return 1;
+ });
+
+ vector<DNSRecord> ret;
+ int res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
+ BOOST_CHECK_EQUAL(res, 0);
+ BOOST_CHECK_EQUAL(ret.size(), 1);
+}
+
+BOOST_AUTO_TEST_CASE(test_nodata_nsec_dnssec) {
+ std::unique_ptr<SyncRes> sr;
+ init();
+ initSR(sr, true, true);
+
+ primeHints();
+
+ const DNSName target("powerdns.com.");
+
+ 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) {
+
+ setLWResult(res, 0, true, false, true);
+ addRecordToLW(res, domain, QType::SOA, "pdns-public-ns1.powerdns.com. pieter\\.lexis.powerdns.com. 2017032301 10800 3600 604800 3600", DNSResourceRecord::AUTHORITY, 3600);
+ /* the NSEC and RRSIG contents are complete garbage, please ignore them */
+ addRecordToLW(res, domain, QType::NSEC, "deadbeef", DNSResourceRecord::AUTHORITY);
+ addRecordToLW(res, domain, QType::RRSIG, "NSEC 5 3 600 2100010100000000 2100010100000000 24567 dummy data", DNSResourceRecord::AUTHORITY);
+ addRecordToLW(res, domain, QType::RRSIG, "SOA 5 3 600 2100010100000000 2100010100000000 24567 dummy data", DNSResourceRecord::AUTHORITY);
+ return 1;
+ });
+
+ vector<DNSRecord> ret;
+ int res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
+ BOOST_CHECK_EQUAL(res, 0);
+ BOOST_CHECK_EQUAL(ret.size(), 4);
+}
+
+BOOST_AUTO_TEST_CASE(test_nx_nsec_nodnssec) {
+ std::unique_ptr<SyncRes> sr;
+ init();
+ initSR(sr, true, false);
+
+ primeHints();
+
+ const DNSName target("powerdns.com.");
+
+ 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) {
+
+ setLWResult(res, RCode::NXDomain, true, false, true);
+ addRecordToLW(res, domain, QType::SOA, "pdns-public-ns1.powerdns.com. pieter\\.lexis.powerdns.com. 2017032301 10800 3600 604800 3600", DNSResourceRecord::AUTHORITY, 3600);
+ /* the NSEC and RRSIG contents are complete garbage, please ignore them */
+ addRecordToLW(res, domain, QType::NSEC, "deadbeef", DNSResourceRecord::AUTHORITY);
+ addRecordToLW(res, domain, QType::RRSIG, "NSEC 5 3 600 2100010100000000 2100010100000000 24567 dummy data", DNSResourceRecord::AUTHORITY);
+ addRecordToLW(res, domain, QType::RRSIG, "SOA 5 3 600 2100010100000000 2100010100000000 24567 dummy data", DNSResourceRecord::AUTHORITY);
+ return 1;
+ });
+
+ vector<DNSRecord> ret;
+ int res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
+ BOOST_CHECK_EQUAL(res, RCode::NXDomain);
+ BOOST_CHECK_EQUAL(ret.size(), 1);
+}
+
+BOOST_AUTO_TEST_CASE(test_nx_nsec_dnssec) {
+ std::unique_ptr<SyncRes> sr;
+ init();
+ initSR(sr, true, true);
+
+ primeHints();
+
+ const DNSName target("powerdns.com.");
+
+ 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) {
+
+ setLWResult(res, RCode::NXDomain, true, false, true);
+ addRecordToLW(res, domain, QType::SOA, "pdns-public-ns1.powerdns.com. pieter\\.lexis.powerdns.com. 2017032301 10800 3600 604800 3600", DNSResourceRecord::AUTHORITY, 3600);
+ /* the NSEC and RRSIG contents are complete garbage, please ignore them */
+ addRecordToLW(res, domain, QType::NSEC, "deadbeef", DNSResourceRecord::AUTHORITY);
+ addRecordToLW(res, domain, QType::RRSIG, "NSEC 5 3 600 2100010100000000 2100010100000000 24567 dummy data", DNSResourceRecord::AUTHORITY);
+ addRecordToLW(res, domain, QType::RRSIG, "SOA 5 3 600 2100010100000000 2100010100000000 24567 dummy data", DNSResourceRecord::AUTHORITY);
+ return 1;
+ });
+
+ vector<DNSRecord> ret;
+ int res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
+ BOOST_CHECK_EQUAL(res, RCode::NXDomain);
+ BOOST_CHECK_EQUAL(ret.size(), 4);
+}
+
+BOOST_AUTO_TEST_CASE(test_qclass_none) {
+ std::unique_ptr<SyncRes> sr;
+ init();
+ initSR(sr, true, true);
+
+ primeHints();
+
+ /* apart from special names and QClass::ANY, anything else than QClass::IN should be rejected right away */
+ size_t queriesCount = 0;
+
+ sr->setAsyncCallback([&queriesCount](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++;
+ return 0;
+ });
+
+ const DNSName target("powerdns.com.");
+ vector<DNSRecord> ret;
+ int res = sr->beginResolve(target, QType(QType::A), QClass::NONE, ret);
+ BOOST_CHECK_EQUAL(res, -1);
+ BOOST_CHECK_EQUAL(ret.size(), 0);
+ BOOST_CHECK_EQUAL(queriesCount, 0);
+}
+
+BOOST_AUTO_TEST_CASE(test_xfr) {
+ std::unique_ptr<SyncRes> sr;
+ init();
+ initSR(sr, true, true);
+
+ primeHints();
+
+ /* {A,I}XFR should be rejected right away */
+ size_t queriesCount = 0;
+
+ sr->setAsyncCallback([&queriesCount](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) {
+
+ cerr<<"asyncresolve called to ask "<<ip.toStringWithPort()<<" about "<<domain.toString()<<" / "<<QType(type).getName()<<" over "<<(doTCP ? "TCP" : "UDP")<<" (rd: "<<sendRDQuery<<", EDNS0 level: "<<EDNS0Level<<")"<<endl;
+ queriesCount++;
+ return 0;
+ });
+
+ const DNSName target("powerdns.com.");
+ vector<DNSRecord> ret;
+ int res = sr->beginResolve(target, QType(QType::AXFR), QClass::IN, ret);
+ BOOST_CHECK_EQUAL(res, -1);
+ BOOST_CHECK_EQUAL(ret.size(), 0);
+ BOOST_CHECK_EQUAL(queriesCount, 0);
+
+ res = sr->beginResolve(target, QType(QType::IXFR), QClass::IN, ret);
+ BOOST_CHECK_EQUAL(res, -1);
+ BOOST_CHECK_EQUAL(ret.size(), 0);
+ BOOST_CHECK_EQUAL(queriesCount, 0);
+}
+
+BOOST_AUTO_TEST_CASE(test_special_names) {
+ std::unique_ptr<SyncRes> sr;
+ init();
+ initSR(sr, true, true);
+
+ primeHints();
+
+ /* special names should be handled internally */
+
+ size_t queriesCount = 0;
+
+ sr->setAsyncCallback([&queriesCount](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++;
+ return 0;
+ });
+
+ vector<DNSRecord> ret;
+ int res = sr->beginResolve(DNSName("1.0.0.127.in-addr.arpa."), QType(QType::PTR), QClass::IN, ret);
+ BOOST_CHECK_EQUAL(res, 0);
+ BOOST_REQUIRE_EQUAL(ret.size(), 1);
+ BOOST_CHECK(ret[0].d_type == QType::PTR);
+ BOOST_CHECK_EQUAL(queriesCount, 0);
+
+ ret.clear();
+ res = sr->beginResolve(DNSName("1.0.0.127.in-addr.arpa."), QType(QType::ANY), QClass::IN, ret);
+ BOOST_CHECK_EQUAL(res, 0);
+ BOOST_REQUIRE_EQUAL(ret.size(), 1);
+ BOOST_CHECK(ret[0].d_type == QType::PTR);
+ BOOST_CHECK_EQUAL(queriesCount, 0);
+
+ ret.clear();
+ res = sr->beginResolve(DNSName("1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.ip6.arpa."), QType(QType::PTR), QClass::IN, ret);
+ BOOST_CHECK_EQUAL(res, 0);
+ BOOST_REQUIRE_EQUAL(ret.size(), 1);
+ BOOST_CHECK(ret[0].d_type == QType::PTR);
+ BOOST_CHECK_EQUAL(queriesCount, 0);
+
+ ret.clear();
+ res = sr->beginResolve(DNSName("1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.ip6.arpa."), QType(QType::ANY), QClass::IN, ret);
+ BOOST_CHECK_EQUAL(res, 0);
+ BOOST_REQUIRE_EQUAL(ret.size(), 1);
+ BOOST_CHECK(ret[0].d_type == QType::PTR);
+ BOOST_CHECK_EQUAL(queriesCount, 0);
+
+ ret.clear();
+ res = sr->beginResolve(DNSName("localhost."), QType(QType::A), QClass::IN, ret);
+ BOOST_CHECK_EQUAL(res, 0);
+ BOOST_REQUIRE_EQUAL(ret.size(), 1);
+ BOOST_CHECK(ret[0].d_type == QType::A);
+ BOOST_CHECK_EQUAL(getRR<ARecordContent>(ret[0])->getCA().toString(), "127.0.0.1");
+ BOOST_CHECK_EQUAL(queriesCount, 0);
+
+ ret.clear();
+ res = sr->beginResolve(DNSName("localhost."), QType(QType::AAAA), QClass::IN, ret);
+ BOOST_CHECK_EQUAL(res, 0);
+ BOOST_REQUIRE_EQUAL(ret.size(), 1);
+ BOOST_CHECK(ret[0].d_type == QType::AAAA);
+ BOOST_CHECK_EQUAL(getRR<AAAARecordContent>(ret[0])->getCA().toString(), "::1");
+ BOOST_CHECK_EQUAL(queriesCount, 0);
+
+ ret.clear();
+ res = sr->beginResolve(DNSName("localhost."), QType(QType::ANY), QClass::IN, ret);
+ BOOST_CHECK_EQUAL(res, 0);
+ BOOST_REQUIRE_EQUAL(ret.size(), 2);
+ for (const auto& rec : ret) {
+ BOOST_REQUIRE((rec.d_type == QType::A) || rec.d_type == QType::AAAA);
+ if (rec.d_type == QType::A) {
+ BOOST_CHECK_EQUAL(getRR<ARecordContent>(rec)->getCA().toString(), "127.0.0.1");
+ }
+ else {
+ BOOST_CHECK_EQUAL(getRR<AAAARecordContent>(rec)->getCA().toString(), "::1");
+ }
+ }
+ BOOST_CHECK_EQUAL(queriesCount, 0);
+
+ ret.clear();
+ res = sr->beginResolve(DNSName("version.bind."), QType(QType::TXT), QClass::CHAOS, ret);
+ BOOST_CHECK_EQUAL(res, 0);
+ BOOST_REQUIRE_EQUAL(ret.size(), 1);
+ BOOST_CHECK(ret[0].d_type == QType::TXT);
+ BOOST_CHECK_EQUAL(getRR<TXTRecordContent>(ret[0])->d_text, "\"PowerDNS Unit Tests\"");
+ BOOST_CHECK_EQUAL(queriesCount, 0);
+
+ ret.clear();
+ res = sr->beginResolve(DNSName("version.bind."), QType(QType::ANY), QClass::CHAOS, ret);
+ BOOST_CHECK_EQUAL(res, 0);
+ BOOST_REQUIRE_EQUAL(ret.size(), 1);
+ BOOST_CHECK(ret[0].d_type == QType::TXT);
+ BOOST_CHECK_EQUAL(getRR<TXTRecordContent>(ret[0])->d_text, "\"PowerDNS Unit Tests\"");
+ BOOST_CHECK_EQUAL(queriesCount, 0);
+
+ ret.clear();
+ res = sr->beginResolve(DNSName("version.pdns."), QType(QType::TXT), QClass::CHAOS, ret);
+ BOOST_CHECK_EQUAL(res, 0);
+ BOOST_REQUIRE_EQUAL(ret.size(), 1);
+ BOOST_CHECK(ret[0].d_type == QType::TXT);
+ BOOST_CHECK_EQUAL(getRR<TXTRecordContent>(ret[0])->d_text, "\"PowerDNS Unit Tests\"");
+ BOOST_CHECK_EQUAL(queriesCount, 0);
+
+ ret.clear();
+ res = sr->beginResolve(DNSName("version.pdns."), QType(QType::ANY), QClass::CHAOS, ret);
+ BOOST_CHECK_EQUAL(res, 0);
+ BOOST_REQUIRE_EQUAL(ret.size(), 1);
+ BOOST_CHECK(ret[0].d_type == QType::TXT);
+ BOOST_CHECK_EQUAL(getRR<TXTRecordContent>(ret[0])->d_text, "\"PowerDNS Unit Tests\"");
+ BOOST_CHECK_EQUAL(queriesCount, 0);
+
+ ret.clear();
+ res = sr->beginResolve(DNSName("id.server."), QType(QType::TXT), QClass::CHAOS, ret);
+ BOOST_CHECK_EQUAL(res, 0);
+ BOOST_REQUIRE_EQUAL(ret.size(), 1);
+ BOOST_CHECK(ret[0].d_type == QType::TXT);
+ BOOST_CHECK_EQUAL(getRR<TXTRecordContent>(ret[0])->d_text, "\"PowerDNS Unit Tests Server ID\"");
+ BOOST_CHECK_EQUAL(queriesCount, 0);
+
+ ret.clear();
+ res = sr->beginResolve(DNSName("id.server."), QType(QType::ANY), QClass::CHAOS, ret);
+ BOOST_CHECK_EQUAL(res, 0);
+ BOOST_REQUIRE_EQUAL(ret.size(), 1);
+ BOOST_CHECK(ret[0].d_type == QType::TXT);
+ BOOST_CHECK_EQUAL(getRR<TXTRecordContent>(ret[0])->d_text, "\"PowerDNS Unit Tests Server ID\"");
+ BOOST_CHECK_EQUAL(queriesCount, 0);
+}
+
+BOOST_AUTO_TEST_CASE(test_nameserver_ipv4_rpz) {
+ std::unique_ptr<SyncRes> sr;
+ init();
+ initSR(sr, true, false);
+
+ primeHints();
+
+ const DNSName target("rpz.powerdns.com.");
+ const ComboAddress ns("192.0.2.1:53");
+
+ sr->setAsyncCallback([target,ns](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)) {
+ setLWResult(res, 0, true, false, true);
+ addRecordToLW(res, "com.", QType::NS, "a.gtld-servers.net.", DNSResourceRecord::AUTHORITY, 172800);
+ addRecordToLW(res, "a.gtld-servers.net.", QType::A, ns.toString(), DNSResourceRecord::ADDITIONAL, 3600);
+ return 1;
+ } else if (ip == ns) {
+
+ setLWResult(res, 0, true, false, true);
+ addRecordToLW(res, domain, QType::A, "192.0.2.42");
+ return 1;
+ }
+
+ return 0;
+ });
+
+ DNSFilterEngine::Policy pol;
+ pol.d_kind = DNSFilterEngine::PolicyKind::Drop;
+ auto luaconfsCopy = g_luaconfs.getCopy();
+ luaconfsCopy.dfe.setPolicyName(0, "Unit test policy 0");
+ luaconfsCopy.dfe.addNSIPTrigger(Netmask(ns, 32), pol, 0);
+ g_luaconfs.setState(luaconfsCopy);
+
+ vector<DNSRecord> ret;
+ int res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
+ BOOST_CHECK_EQUAL(res, -2);
+ BOOST_CHECK_EQUAL(ret.size(), 0);
+}
+
+BOOST_AUTO_TEST_CASE(test_nameserver_ipv6_rpz) {
+ std::unique_ptr<SyncRes> sr;
+ init();
+ initSR(sr, true, false);
+
+ primeHints();
+
+ const DNSName target("rpz.powerdns.com.");
+ const ComboAddress ns("[2001:DB8::42]:53");
+
+ sr->setAsyncCallback([target,ns](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)) {
+ setLWResult(res, 0, true, false, true);
+ addRecordToLW(res, "com.", QType::NS, "a.gtld-servers.net.", DNSResourceRecord::AUTHORITY, 172800);
+ addRecordToLW(res, "a.gtld-servers.net.", QType::AAAA, ns.toString(), DNSResourceRecord::ADDITIONAL, 3600);
+ return 1;
+ } else if (ip == ns) {
+
+ setLWResult(res, 0, true, false, true);
+ addRecordToLW(res, domain, QType::A, "192.0.2.42");
+ return 1;
+ }
+
+ return 0;
+ });
+
+ DNSFilterEngine::Policy pol;
+ pol.d_kind = DNSFilterEngine::PolicyKind::Drop;
+ auto luaconfsCopy = g_luaconfs.getCopy();
+ luaconfsCopy.dfe.setPolicyName(0, "Unit test policy 0");
+ luaconfsCopy.dfe.addNSIPTrigger(Netmask(ns, 128), pol, 0);
+ g_luaconfs.setState(luaconfsCopy);
+
+ vector<DNSRecord> ret;
+ int res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
+ BOOST_CHECK_EQUAL(res, -2);
+ BOOST_CHECK_EQUAL(ret.size(), 0);
+}
+
+BOOST_AUTO_TEST_CASE(test_nameserver_name_rpz) {
+ std::unique_ptr<SyncRes> sr;
+ init();
+ initSR(sr, true, false);
+
+ primeHints();
+
+ const DNSName target("rpz.powerdns.com.");
+ const ComboAddress ns("192.0.2.1:53");
+ const DNSName nsName("ns1.powerdns.com.");
+
+ sr->setAsyncCallback([target,ns,nsName](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)) {
+ setLWResult(res, 0, true, false, true);
+ addRecordToLW(res, domain, QType::NS, nsName.toString(), DNSResourceRecord::AUTHORITY, 172800);
+ addRecordToLW(res, nsName, QType::A, ns.toString(), DNSResourceRecord::ADDITIONAL, 3600);
+ return 1;
+ } else if (ip == ns) {
+
+ setLWResult(res, 0, true, false, true);
+ addRecordToLW(res, domain, QType::A, "192.0.2.42");
+ return 1;
+ }
+
+ return 0;
+ });
+
+ DNSFilterEngine::Policy pol;
+ pol.d_kind = DNSFilterEngine::PolicyKind::Drop;
+ auto luaconfsCopy = g_luaconfs.getCopy();
+ luaconfsCopy.dfe.setPolicyName(0, "Unit test policy 0");
+ luaconfsCopy.dfe.addNSTrigger(nsName, pol, 0);
+ g_luaconfs.setState(luaconfsCopy);
+
+ vector<DNSRecord> ret;
+ int res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
+ BOOST_CHECK_EQUAL(res, -2);
+ BOOST_CHECK_EQUAL(ret.size(), 0);
+}
+
+BOOST_AUTO_TEST_CASE(test_nameserver_name_rpz_disabled) {
+ std::unique_ptr<SyncRes> sr;
+ init();
+ initSR(sr, true, false);
+
+ primeHints();
+
+ const DNSName target("rpz.powerdns.com.");
+ const ComboAddress ns("192.0.2.1:53");
+ const DNSName nsName("ns1.powerdns.com.");
+
+ sr->setAsyncCallback([target,ns,nsName](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)) {
+ setLWResult(res, 0, true, false, true);
+ addRecordToLW(res, domain, QType::NS, nsName.toString(), DNSResourceRecord::AUTHORITY, 172800);
+ addRecordToLW(res, nsName, QType::A, ns.toString(), DNSResourceRecord::ADDITIONAL, 3600);
+ return 1;
+ } else if (ip == ns) {
+
+ setLWResult(res, 0, true, false, true);
+ addRecordToLW(res, domain, QType::A, "192.0.2.42");
+ return 1;
+ }
+
+ return 0;
+ });
+
+ DNSFilterEngine::Policy pol;
+ pol.d_kind = DNSFilterEngine::PolicyKind::Drop;
+ auto luaconfsCopy = g_luaconfs.getCopy();
+ luaconfsCopy.dfe.setPolicyName(0, "Unit test policy 0");
+ luaconfsCopy.dfe.addNSIPTrigger(Netmask(ns, 128), pol, 0);
+ luaconfsCopy.dfe.addNSTrigger(nsName, pol, 0);
+ g_luaconfs.setState(luaconfsCopy);
+
+ /* RPZ is disabled for this query, we should not be blocked */
+ sr->setWantsRPZ(false);
+
+ vector<DNSRecord> ret;
+ int res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
+ BOOST_CHECK_EQUAL(res, 0);
+ BOOST_CHECK_EQUAL(ret.size(), 1);
+}
+
+BOOST_AUTO_TEST_CASE(test_forward_zone_nord) {
+ std::unique_ptr<SyncRes> sr;
+ init();
+ initSR(sr, true, false);
+
+ primeHints();
+
+ const DNSName target("powerdns.com.");
+ const ComboAddress ns("192.0.2.1:53");
+ const ComboAddress forwardedNS("192.0.2.42:53");
+
+ SyncRes::AuthDomain ad;
+ ad.d_rdForward = false;
+ ad.d_servers.push_back(forwardedNS);
+ (*t_sstorage->domainmap)[target] = ad;
+
+ sr->setAsyncCallback([forwardedNS](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 (ip == forwardedNS) {
+ setLWResult(res, 0, true, false, true);
+ addRecordToLW(res, domain, QType::A, "192.0.2.42");
+ return 1;
+ }
+
+ return 0;
+ });
+
+ /* simulate a no-RD query */
+ sr->setCacheOnly();
+
+ vector<DNSRecord> ret;
+ int res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
+ BOOST_CHECK_EQUAL(res, 0);
+ BOOST_CHECK_EQUAL(ret.size(), 1);
+}
+
+BOOST_AUTO_TEST_CASE(test_forward_zone_rd) {
+ std::unique_ptr<SyncRes> sr;
+ init();
+ initSR(sr, true, false);
+
+ primeHints();
+
+ const DNSName target("powerdns.com.");
+ const ComboAddress ns("192.0.2.1:53");
+ const ComboAddress forwardedNS("192.0.2.42:53");
+
+ SyncRes::AuthDomain ad;
+ ad.d_rdForward = false;
+ ad.d_servers.push_back(forwardedNS);
+ (*t_sstorage->domainmap)[target] = ad;
+
+ sr->setAsyncCallback([forwardedNS](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 (ip == forwardedNS) {
+ setLWResult(res, 0, true, false, true);
+ addRecordToLW(res, domain, QType::A, "192.0.2.42");
+ return 1;
+ }
+
+ return 0;
+ });
+
+ vector<DNSRecord> ret;
+ int res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
+ BOOST_CHECK_EQUAL(res, 0);
+ BOOST_CHECK_EQUAL(ret.size(), 1);
+}
+
+BOOST_AUTO_TEST_CASE(test_forward_zone_recurse_nord) {
+ std::unique_ptr<SyncRes> sr;
+ init();
+ initSR(sr, true, false);
+
+ primeHints();
+
+ const DNSName target("powerdns.com.");
+ const ComboAddress ns("192.0.2.1:53");
+ const ComboAddress forwardedNS("192.0.2.42:53");
+
+ SyncRes::AuthDomain ad;
+ ad.d_rdForward = true;
+ ad.d_servers.push_back(forwardedNS);
+ (*t_sstorage->domainmap)[target] = ad;
+
+ sr->setAsyncCallback([forwardedNS](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 (ip == forwardedNS) {
+ setLWResult(res, 0, true, false, true);
+ addRecordToLW(res, domain, QType::A, "192.0.2.42");
+ return 1;
+ }
+
+ return 0;
+ });
+
+ /* simulate a no-RD query */
+ sr->setCacheOnly();
+
+ vector<DNSRecord> ret;
+ int res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
+ BOOST_CHECK_EQUAL(res, 0);
+ BOOST_CHECK_EQUAL(ret.size(), 1);
+}
+
+BOOST_AUTO_TEST_CASE(test_forward_zone_recurse_rd) {
+ std::unique_ptr<SyncRes> sr;
+ init();
+ initSR(sr, true, false);
+
+ primeHints();
+
+ const DNSName target("powerdns.com.");
+ const ComboAddress ns("192.0.2.1:53");
+ const ComboAddress forwardedNS("192.0.2.42:53");
+
+ SyncRes::AuthDomain ad;
+ ad.d_rdForward = true;
+ ad.d_servers.push_back(forwardedNS);
+ (*t_sstorage->domainmap)[target] = ad;
+
+ sr->setAsyncCallback([forwardedNS](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 (ip == forwardedNS) {
+ setLWResult(res, 0, true, false, true);
+ addRecordToLW(res, domain, QType::A, "192.0.2.42");
+ return 1;
+ }
+
+ return 0;
+ });
+
+ vector<DNSRecord> ret;
+ int res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
+ BOOST_CHECK_EQUAL(res, 0);
+ BOOST_CHECK_EQUAL(ret.size(), 1);
+}
+
+/*
+// cerr<<"asyncresolve called to ask "<<ip.toStringWithPort()<<" about "<<domain.toString()<<" / "<<QType(type).getName()<<" over "<<(doTCP ? "TCP" : "UDP")<<" (rd: "<<sendRDQuery<<", EDNS0 level: "<<EDNS0Level<<")"<<endl;
+
+- check out of band support
+
+- check preoutquery
+
+*/
+
+BOOST_AUTO_TEST_SUITE_END()
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#pragma once
+#ifdef HAVE_CONFIG_H
#include "config.h"
+#endif
#include <atomic>
#include <condition_variable>
#include "packethandler.hh"
#include "qtype.hh"
#include "dnspacket.hh"
-#include "packetcache.hh"
+#include "auth-caches.hh"
+#include "statbag.hh"
#include "dnsseckeeper.hh"
#include "base64.hh"
#include "base32.hh"
#include "dns_random.hh"
#include "backends/gsql/ssql.hh"
-extern PacketCache PC;
extern StatBag S;
pthread_mutex_t PacketHandler::s_rfc2136lock=PTHREAD_MUTEX_INITIALIZER;
// Purge the records!
string zone(di.zone.toString());
zone.append("$");
- PC.purge(zone);
+ purgeAuthCaches(zone);
L<<Logger::Info<<msgPrefix<<"Update completed, "<<changedRecords<<" changed records committed."<<endl;
} else {
*/
#pragma once
-static const char*rootIps4[]={"198.41.0.4", // a.root-servers.net.
- "192.228.79.201", // b.root-servers.net.
- "192.33.4.12", // c.root-servers.net.
- "199.7.91.13", // d.root-servers.net.
- "192.203.230.10", // e.root-servers.net.
- "192.5.5.241", // f.root-servers.net.
- "192.112.36.4", // g.root-servers.net.
- "198.97.190.53", // h.root-servers.net.
- "192.36.148.17", // i.root-servers.net.
- "192.58.128.30", // j.root-servers.net.
- "193.0.14.129", // k.root-servers.net.
- "199.7.83.42", // l.root-servers.net.
- "202.12.27.33" // m.root-servers.net.
- };
+static const char* const rootIps4[]={"198.41.0.4", // a.root-servers.net.
+ "192.228.79.201", // b.root-servers.net.
+ "192.33.4.12", // c.root-servers.net.
+ "199.7.91.13", // d.root-servers.net.
+ "192.203.230.10", // e.root-servers.net.
+ "192.5.5.241", // f.root-servers.net.
+ "192.112.36.4", // g.root-servers.net.
+ "198.97.190.53", // h.root-servers.net.
+ "192.36.148.17", // i.root-servers.net.
+ "192.58.128.30", // j.root-servers.net.
+ "193.0.14.129", // k.root-servers.net.
+ "199.7.83.42", // l.root-servers.net.
+ "202.12.27.33" // m.root-servers.net.
+ };
+static size_t const rootIps4Count = sizeof(rootIps4) / sizeof(*rootIps4);
-static const char*rootIps6[]={"2001:503:ba3e::2:30", // a.root-servers.net.
- "2001:500:84::b", // b.root-servers.net.
- "2001:500:2::c", // c.root-servers.net.
- "2001:500:2d::d", // d.root-servers.net.
- "2001:500:a8::e", // e.root-servers.net.
- "2001:500:2f::f", // f.root-servers.net.
- "2001:500:12::d0d", // g.root-servers.net.
- "2001:500:1::53", // h.root-servers.net.
- "2001:7fe::53", // i.root-servers.net.
- "2001:503:c27::2:30", // j.root-servers.net.
- "2001:7fd::1", // k.root-servers.net.
- "2001:500:9f::42", // l.root-servers.net.
- "2001:dc3::35" // m.root-servers.net.
- };
+static const char* const rootIps6[]={"2001:503:ba3e::2:30", // a.root-servers.net.
+ "2001:500:84::b", // b.root-servers.net.
+ "2001:500:2::c", // c.root-servers.net.
+ "2001:500:2d::d", // d.root-servers.net.
+ "2001:500:a8::e", // e.root-servers.net.
+ "2001:500:2f::f", // f.root-servers.net.
+ "2001:500:12::d0d", // g.root-servers.net.
+ "2001:500:1::53", // h.root-servers.net.
+ "2001:7fe::53", // i.root-servers.net.
+ "2001:503:c27::2:30", // j.root-servers.net.
+ "2001:7fd::1", // k.root-servers.net.
+ "2001:500:9f::42", // l.root-servers.net.
+ "2001:dc3::35" // m.root-servers.net.
+ };
+static size_t const rootIps6Count = sizeof(rootIps6) / sizeof(*rootIps6);
#pragma once
-static const char*rootDSs[]={"19036 8 2 49aac11d7b6f6446702e54a1607371607a1a41855200fd2ce1cdde32f24e8fb5",
- "20326 8 2 e06d44b80b8f1d39a95c0b0d7c65d08458e880409bbc683457104237c7f8ec8d"};
+static const char* const rootDSs[]={"19036 8 2 49aac11d7b6f6446702e54a1607371607a1a41855200fd2ce1cdde32f24e8fb5",
+ "20326 8 2 e06d44b80b8f1d39a95c0b0d7c65d08458e880409bbc683457104237c7f8ec8d"};
gettimeofday(&now, 0);
SyncRes sr(now);
if (g_dnssecmode != DNSSECMode::Off)
- sr.d_doDNSSEC=true;
+ sr.setDoDNSSEC(true);
vector<DNSRecord> ret;
string version = "recursor-" +pkgv;
DNSSECKeeper dk;
UeberBackend db("key-only");
- chunk_t* chunk;
+ chunk_t* chunk = nullptr;
int res;
for(;;) {
res = readn(fd, &chunk, sizeof(chunk));
break;
if(res < 0)
unixDie("reading object pointer to sign from pdns");
- set<DNSName> authSet;
- authSet.insert(d_signer);
- addRRSigs(dk, db, authSet, *chunk);
- ++d_signed;
-
- writen2(fd, &chunk, sizeof(chunk));
+ try {
+ set<DNSName> authSet;
+ authSet.insert(d_signer);
+ addRRSigs(dk, db, authSet, *chunk);
+ ++d_signed;
+
+ writen2(fd, &chunk, sizeof(chunk));
+ chunk = nullptr;
+ }
+ catch(const PDNSException& pe) {
+ delete chunk;
+ throw;
+ }
+ catch(const std::exception& e) {
+ delete chunk;
+ throw;
+ }
}
close(fd);
}
-catch(PDNSException& pe)
+catch(const PDNSException& pe)
{
L<<Logger::Error<<"Signing thread died because of PDNSException: "<<pe.reason<<endl;
close(fd);
}
-catch(std::exception& e)
+catch(const std::exception& e)
{
L<<Logger::Error<<"Signing thread died because of std::exception: "<<e.what()<<endl;
close(fd);
void run()
{
#ifdef HAVE_NET_SNMP
- d_thread = std::move(std::thread(&SNMPAgent::worker, this));
+ d_thread = std::thread(&SNMPAgent::worker, this);
#endif /* HAVE_NET_SNMP */
}
#include "statbag.hh"
#include "stubresolver.hh"
-// s_stubresolvers contains the ComboAddresses that are used by
+// s_resolversForStub contains the ComboAddresses that are used by
// stubDoResolve
-static vector<ComboAddress> s_stubresolvers;
+static vector<ComboAddress> s_resolversForStub;
-/** Parse /etc/resolv.conf and add the nameservers to the vector
- * s_stubresolvers.
+/*
+ * Returns false if no resolvers are configured, while emitting a warning about this
+ */
+bool resolversDefined()
+{
+ if (s_resolversForStub.empty()) {
+ L<<Logger::Warning<<"No upstream resolvers configured, stub resolving (including secpoll and ALIAS) impossible."<<endl;
+ return false;
+ }
+ return true;
+}
+
+/*
+ * Fill the s_resolversForStub vector with addresses for the upstream resolvers.
+ * First, parse the `resolver` configuration option for IP addresses to use.
+ * If that doesn't work, parse /etc/resolv.conf and add those nameservers to
+ * s_resolversForStub.
*/
void stubParseResolveConf()
{
- ifstream ifs("/etc/resolv.conf");
- if(!ifs)
- return;
-
- string line;
- while(std::getline(ifs, line)) {
- boost::trim_right_if(line, is_any_of(" \r\n\x1a"));
- boost::trim_left(line); // leading spaces, let's be nice
-
- string::size_type tpos = line.find_first_of(";#");
- if(tpos != string::npos)
- line.resize(tpos);
-
- if(boost::starts_with(line, "nameserver ") || boost::starts_with(line, "nameserver\t")) {
- vector<string> parts;
- stringtok(parts, line, " \t,"); // be REALLY nice
- for(vector<string>::const_iterator iter = parts.begin()+1; iter != parts.end(); ++iter) {
- try {
- s_stubresolvers.push_back(ComboAddress(*iter, 53));
- }
- catch(...)
- {
+ if(::arg().mustDo("resolver")) {
+ vector<string> parts;
+ stringtok(parts, ::arg()["resolver"], " ,\t");
+ for (const auto& addr : parts)
+ s_resolversForStub.push_back(ComboAddress(addr, 53));
+ }
+
+ if (s_resolversForStub.empty()) {
+ ifstream ifs("/etc/resolv.conf");
+ if(!ifs)
+ return;
+
+ string line;
+ while(std::getline(ifs, line)) {
+ boost::trim_right_if(line, is_any_of(" \r\n\x1a"));
+ boost::trim_left(line); // leading spaces, let's be nice
+
+ string::size_type tpos = line.find_first_of(";#");
+ if(tpos != string::npos)
+ line.resize(tpos);
+
+ if(boost::starts_with(line, "nameserver ") || boost::starts_with(line, "nameserver\t")) {
+ vector<string> parts;
+ stringtok(parts, line, " \t,"); // be REALLY nice
+ for(vector<string>::const_iterator iter = parts.begin()+1; iter != parts.end(); ++iter) {
+ try {
+ s_resolversForStub.push_back(ComboAddress(*iter, 53));
+ }
+ catch(...)
+ {
+ }
}
}
}
}
-
- if(::arg().mustDo("resolver"))
- s_stubresolvers.push_back(ComboAddress(::arg()["resolver"], 53));
-
- // Last resort, add 127.0.0.1
- if(s_stubresolvers.empty()) {
- s_stubresolvers.push_back(ComboAddress("127.0.0.1", 53));
- }
+ // Emit a warning if there are no stubs.
+ resolversDefined();
}
-// s_stubresolvers contains the ComboAddresses that are used to resolve the
+// s_resolversForStub contains the ComboAddresses that are used to resolve the
int stubDoResolve(const DNSName& qname, uint16_t qtype, vector<DNSZoneRecord>& ret)
{
+ if (!resolversDefined())
+ return RCode::ServFail;
+
vector<uint8_t> packet;
DNSPacketWriter pw(packet, qname, qtype);
pw.getHeader()->id=dns_random(0xffff);
pw.getHeader()->rd=1;
- if (s_stubresolvers.empty()) {
- L<<Logger::Warning<<"No recursors set, stub resolving (including secpoll and ALIAS) impossible."<<endl;
- return RCode::ServFail;
- }
string msg ="Doing stub resolving, using resolvers: ";
- for (const auto& server : s_stubresolvers) {
+ for (const auto& server : s_resolversForStub) {
msg += server.toString() + ", ";
}
L<<Logger::Debug<<msg.substr(0, msg.length() - 2)<<endl;
- for(const ComboAddress& dest : s_stubresolvers) {
+ for(const ComboAddress& dest : s_resolversForStub) {
Socket sock(dest.sin4.sin_family, SOCK_DGRAM);
sock.setNonBlocking();
sock.connect(dest);
#include "dnsparser.hh"
void stubParseResolveConf();
+bool resolversDefined();
int stubDoResolve(const DNSName& qname, uint16_t qtype, vector<DNSZoneRecord>& ret);
std::atomic<uint64_t> SyncRes::s_dontqueries;
std::atomic<uint64_t> SyncRes::s_nodelegated;
std::atomic<uint64_t> SyncRes::s_unreachables;
+uint8_t SyncRes::s_ecsipv4limit;
+uint8_t SyncRes::s_ecsipv6limit;
unsigned int SyncRes::s_minimumTTL;
bool SyncRes::s_doIPv6;
bool SyncRes::s_nopacketcache;
SyncRes::SyncRes(const struct timeval& now) : d_outqueries(0), d_tcpoutqueries(0), d_throttledqueries(0), d_timeouts(0), d_unreachables(0),
- d_totUsec(0), d_doDNSSEC(false), d_now(now),
- d_cacheonly(false), d_nocache(false), d_doEDNS0(false), d_lm(s_lm)
+ d_totUsec(0), d_now(now),
+ d_cacheonly(false), d_nocache(false), d_doDNSSEC(false), d_doEDNS0(false), d_lm(s_lm)
{
if(!t_sstorage) {
d_wasVariable=false;
d_wasOutOfBand=false;
- if( (qtype.getCode() == QType::AXFR))
- return -1;
-
- static const DNSName arpa("1.0.0.127.in-addr.arpa."), localhost("localhost."),
- versionbind("version.bind."), idserver("id.server."), versionpdns("version.pdns.");
-
- if( (qtype.getCode()==QType::PTR && qname==arpa) ||
- (qtype.getCode()==QType::A && qname==localhost)) {
- ret.clear();
- DNSRecord dr;
- dr.d_name=qname;
- dr.d_place = DNSResourceRecord::ANSWER;
- dr.d_type=qtype.getCode();
- dr.d_class=QClass::IN;
- dr.d_ttl=86400;
- if(qtype.getCode()==QType::PTR)
- dr.d_content=shared_ptr<DNSRecordContent>(DNSRecordContent::mastermake(QType::PTR, 1, "localhost."));
- else
- dr.d_content=shared_ptr<DNSRecordContent>(DNSRecordContent::mastermake(QType::A, 1, "127.0.0.1"));
- ret.push_back(dr);
- d_wasOutOfBand=true;
+ if (doSpecialNamesResolve(qname, qtype, qclass, ret))
return 0;
- }
- if(qclass==QClass::CHAOS && qtype.getCode()==QType::TXT &&
- (qname==versionbind || qname==idserver || qname==versionpdns )
- ) {
- ret.clear();
- DNSRecord dr;
- dr.d_name=qname;
- dr.d_type=qtype.getCode();
- dr.d_class=qclass;
- dr.d_ttl=86400;
- dr.d_place = DNSResourceRecord::ANSWER;
- if(qname==versionbind || qname==versionpdns)
- dr.d_content=shared_ptr<DNSRecordContent>(DNSRecordContent::mastermake(QType::TXT, 3, "\""+::arg()["version-string"]+"\""));
- else
- dr.d_content=shared_ptr<DNSRecordContent>(DNSRecordContent::mastermake(QType::TXT, 3, "\""+s_serverID+"\""));
-
- ret.push_back(dr);
- d_wasOutOfBand=true;
- return 0;
- }
+ if( (qtype.getCode() == QType::AXFR) || (qtype.getCode() == QType::IXFR))
+ return -1;
if(qclass==QClass::ANY)
qclass=QClass::IN;
return res;
}
+/*! Handles all special, built-in names
+ * Fills ret with an answer and returns true if it handled the query.
+ *
+ * Handles the following queries (and their ANY variants):
+ *
+ * - localhost. IN A
+ * - localhost. IN AAAA
+ * - 1.0.0.127.in-addr.arpa. IN PTR
+ * - 1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.ip6.arpa. IN PTR
+ * - version.bind. CH TXT
+ * - version.pdns. CH TXT
+ * - id.server. CH TXT
+ */
+bool SyncRes::doSpecialNamesResolve(const DNSName &qname, const QType &qtype, const uint16_t &qclass, vector<DNSRecord> &ret)
+{
+ static const DNSName arpa("1.0.0.127.in-addr.arpa."), ip6_arpa("1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.ip6.arpa."),
+ localhost("localhost."), versionbind("version.bind."), idserver("id.server."), versionpdns("version.pdns.");
+
+ bool handled = false;
+ vector<pair<QType::typeenum, string> > answers;
+
+ if ((qname == arpa || qname == ip6_arpa) &&
+ qclass == QClass::IN) {
+ handled = true;
+ if (qtype == QType::PTR || qtype == QType::ANY)
+ answers.push_back({QType::PTR, "localhost."});
+ }
+
+ if (qname == localhost &&
+ qclass == QClass::IN) {
+ handled = true;
+ if (qtype == QType::A || qtype == QType::ANY)
+ answers.push_back({QType::A, "127.0.0.1"});
+ if (qtype == QType::AAAA || qtype == QType::ANY)
+ answers.push_back({QType::AAAA, "::1"});
+ }
+
+ if ((qname == versionbind || qname == idserver || qname == versionpdns) &&
+ qclass == QClass::CHAOS) {
+ handled = true;
+ if (qtype == QType::TXT || qtype == QType::ANY) {
+ if(qname == versionbind || qname == versionpdns)
+ answers.push_back({QType::TXT, "\""+::arg()["version-string"]+"\""});
+ else
+ answers.push_back({QType::TXT, "\""+s_serverID+"\""});
+ }
+ }
+
+ if (handled && !answers.empty()) {
+ ret.clear();
+ d_wasOutOfBand=true;
+
+ DNSRecord dr;
+ dr.d_name = qname;
+ dr.d_place = DNSResourceRecord::ANSWER;
+ dr.d_class = qclass;
+ dr.d_ttl = 86400;
+ for (const auto& ans : answers) {
+ dr.d_type = ans.first;
+ dr.d_content = shared_ptr<DNSRecordContent>(DNSRecordContent::mastermake(ans.first, qclass, ans.second));
+ ret.push_back(dr);
+ }
+ }
+
+ return handled;
+}
+
+
//! This is the 'out of band resolver', in other words, the authoritative server
bool SyncRes::doOOBResolve(const DNSName &qname, const QType &qtype, vector<DNSRecord>&ret, unsigned int depth, int& res)
{
*/
SyncRes::EDNSStatus* ednsstatus;
- ednsstatus = &t_sstorage->ednsstatus[ip]; // does this include port?
+ ednsstatus = &t_sstorage->ednsstatus[ip]; // does this include port? YES
if(ednsstatus->modeSetAt && ednsstatus->modeSetAt + 3600 < d_now.tv_sec) {
*ednsstatus=SyncRes::EDNSStatus();
}
else if(ednsMANDATORY || mode==EDNSStatus::UNKNOWN || mode==EDNSStatus::EDNSOK || mode==EDNSStatus::EDNSIGNORANT)
EDNSLevel = 1;
-
- ret=asyncresolve(ip, domain, type, doTCP, sendRDQuery, EDNSLevel, now, srcmask, ctx, luaconfsLocal->outgoingProtobufServer, res);
+
+ if (d_asyncResolve) {
+ ret = d_asyncResolve(ip, domain, type, doTCP, sendRDQuery, EDNSLevel, now, srcmask, ctx, luaconfsLocal->outgoingProtobufServer, res);
+ }
+ else {
+ ret=asyncresolve(ip, domain, type, doTCP, sendRDQuery, EDNSLevel, now, srcmask, ctx, luaconfsLocal->outgoingProtobufServer, res);
+ }
if(ret < 0) {
return ret; // transport error, nothing to learn here
}
return ret;
}
+/*! This function will check the cache and go out to the internet if the answer is not in cache
+ *
+ * \param qname The name we need an answer for
+ * \param qtype
+ * \param ret The vector of DNSRecords we need to fill with the answers
+ * \param depth The recursion depth we are in
+ * \param beenthere
+ * \return DNS RCODE or -1 (Error) or -2 (RPZ hit)
+ */
int SyncRes::doResolve(const DNSName &qname, const QType &qtype, vector<DNSRecord>&ret, unsigned int depth, set<GetBestNSAnswer>& beenthere)
{
string prefix;
throw ImmediateServFailException("More than "+std::to_string(s_maxdepth)+" (max-recursion-depth) levels of recursion needed while resolving "+qname.toLogString());
int res=0;
+
+ // This is a difficult way of expressing "this is a normal query", i.e. not getRootNS.
if(!(d_nocache && qtype.getCode()==QType::NS && qname.isRoot())) {
if(d_cacheonly) { // very limited OOB support
LWResult lwr;
const ComboAddress remoteIP = servers.front();
LOG(prefix<<qname<<": forwarding query to hardcoded nameserver '"<< remoteIP.toStringWithPort()<<"' for zone '"<<authname<<"'"<<endl);
- boost::optional<Netmask> nm;
+ boost::optional<Netmask> nm;
res=asyncresolveWrapper(remoteIP, d_doDNSSEC, qname, qtype.getCode(), false, false, &d_now, nm, &lwr);
// filter out the good stuff from lwr.result()
-
- for(const auto& rec : lwr.d_records) {
- if(rec.d_place == DNSResourceRecord::ANSWER)
- ret.push_back(rec);
+ if (res == 1) {
+ for(const auto& rec : lwr.d_records) {
+ if(rec.d_place == DNSResourceRecord::ANSWER)
+ ret.push_back(rec);
+ }
+ return 0;
+ }
+ else {
+ return RCode::ServFail;
}
- return res;
}
}
}
// We lost the root NS records
primeHints();
LOG(prefix<<qname<<": reprimed the root"<<endl);
- getRootNS();
+ getRootNS(d_now, d_asyncResolve);
}
}while(subdomain.chopOff());
}
ret.push_back(sigdr);
}
- if(!(qtype==QType(QType::CNAME))) { // perhaps they really wanted a CNAME!
+ if(qtype != QType::CNAME) { // perhaps they really wanted a CNAME!
set<GetBestNSAnswer>beenthere;
res=doResolve(std::dynamic_pointer_cast<CNAMERecordContent>(j->d_content)->getTarget(), qtype, ret, depth+1, beenthere);
}
return false;
}
-static const DNSName getLastLabel(const DNSName& qname)
-{
- DNSName ret(qname);
- ret.trimToLabels(1);
- return ret;
+/*!
+ * Convience function to push the records from records into ret with a new TTL
+ *
+ * \param records DNSRecords that need to go into ret
+ * \param ttl The new TTL for these records
+ * \param ret The vector of DNSRecords that should contian the records with the modified TTL
+ */
+static void addTTLModifiedRecords(const vector<DNSRecord>& records, const uint32_t ttl, vector<DNSRecord>& ret) {
+ for (const auto& rec : records) {
+ DNSRecord r(rec);
+ r.d_ttl = ttl;
+ ret.push_back(r);
+ }
}
prefix.append(depth, ' ');
}
+ // sqname and sqtype are used contain 'higher' names if we have them (e.g. powerdns.com|SOA when we find a negative entry for doesnotexists.powerdns.com|A)
DNSName sqname(qname);
QType sqt(qtype);
uint32_t sttl=0;
// cout<<"Lookup for '"<<qname<<"|"<<qtype.getName()<<"' -> "<<getLastLabel(qname)<<endl;
- pair<negcache_t::const_iterator, negcache_t::const_iterator> range;
- QType qtnull(0);
-
DNSName authname(qname);
bool wasForwardedOrAuth = (getBestAuthZone(&authname) != t_sstorage->domainmap->end());
+ NegCache::NegCacheEntry ne;
if(s_rootNXTrust &&
- (range.first=t_sstorage->negcache.find(tie(getLastLabel(qname), qtnull))) != t_sstorage->negcache.end() &&
- !(wasForwardedOrAuth && !authname.isRoot()) && // when forwarding, the root may only neg-cache if it was forwarded to.
- range.first->d_qname.isRoot() && (uint32_t)d_now.tv_sec < range.first->d_ttd) {
- sttl=range.first->d_ttd - d_now.tv_sec;
-
- LOG(prefix<<qname<<": Entire name '"<<qname<<"', is negatively cached via '"<<range.first->d_name<<"' & '"<<range.first->d_qname<<"' for another "<<sttl<<" seconds"<<endl);
+ t_sstorage->negcache.getRootNXTrust(qname, d_now, ne) &&
+ ne.d_auth.isRoot() &&
+ !(wasForwardedOrAuth && !authname.isRoot())) { // when forwarding, the root may only neg-cache if it was forwarded to.
+ sttl = ne.d_ttd - d_now.tv_sec;
+ LOG(prefix<<qname<<": Entire name '"<<qname<<"', is negatively cached via '"<<ne.d_auth<<"' & '"<<ne.d_name<<"' for another "<<sttl<<" seconds"<<endl);
res = RCode::NXDomain;
- sqname=range.first->d_qname;
- sqt=QType::SOA;
- moveCacheItemToBack(t_sstorage->negcache, range.first);
-
- giveNegative=true;
+ giveNegative = true;
}
- else {
- range=t_sstorage->negcache.equal_range(tie(qname));
- negcache_t::iterator ni;
- for(ni=range.first; ni != range.second; ni++) {
- // we have something
- if(!(wasForwardedOrAuth && ni->d_qname != authname) && // Only the authname nameserver can neg cache entries
- (ni->d_qtype.getCode() == 0 || ni->d_qtype == qtype)) {
- res=0;
- if((uint32_t)d_now.tv_sec < ni->d_ttd) {
- sttl=ni->d_ttd - d_now.tv_sec;
- if(ni->d_qtype.getCode()) {
- LOG(prefix<<qname<<": "<<qtype.getName()<<" is negatively cached via '"<<ni->d_qname<<"' for another "<<sttl<<" seconds"<<endl);
- res = RCode::NoError;
- }
- else {
- LOG(prefix<<qname<<": Entire name '"<<qname<<"', is negatively cached via '"<<ni->d_qname<<"' for another "<<sttl<<" seconds"<<endl);
- res= RCode::NXDomain;
- }
- giveNegative=true;
- sqname=ni->d_qname;
- sqt=QType::SOA;
- if(d_doDNSSEC) {
- for(const auto& p : ni->d_dnssecProof) {
- for(const auto& rec: p.second.records)
- ret.push_back(rec);
- for(const auto& rec: p.second.signatures)
- ret.push_back(rec);
- }
- }
- moveCacheItemToBack(t_sstorage->negcache, ni);
- break;
- }
- else {
- LOG(prefix<<qname<<": Entire name '"<<qname<<"' or type was negatively cached, but entry expired"<<endl);
- moveCacheItemToFront(t_sstorage->negcache, ni);
- }
- }
+ else if (t_sstorage->negcache.get(qname, qtype, d_now, ne) &&
+ !(wasForwardedOrAuth && ne.d_auth != authname)) { // Only the authname nameserver can neg cache entries
+ res = 0;
+ sttl = ne.d_ttd - d_now.tv_sec;
+ giveNegative = true;
+ if(ne.d_qtype.getCode()) {
+ LOG(prefix<<qname<<": "<<qtype.getName()<<" is negatively cached via '"<<ne.d_auth<<"' for another "<<sttl<<" seconds"<<endl);
+ res = RCode::NoError;
}
+ else {
+ LOG(prefix<<qname<<": Entire name '"<<qname<<"', is negatively cached via '"<<ne.d_auth<<"' for another "<<sttl<<" seconds"<<endl);
+ res = RCode::NXDomain;
+ }
+ if(d_doDNSSEC) {
+ addTTLModifiedRecords(ne.DNSSECRecords.records, sttl, ret);
+ addTTLModifiedRecords(ne.DNSSECRecords.signatures, sttl, ret);
+ }
+ }
+
+ if (giveNegative) {
+ // Transplant SOA to the returned packet
+ addTTLModifiedRecords(ne.authoritySOA.records, sttl, ret);
+ if(d_doDNSSEC)
+ addTTLModifiedRecords(ne.authoritySOA.signatures, sttl, ret);
+ return true;
}
+
vector<DNSRecord> cset;
bool found=false, expired=false;
vector<std::shared_ptr<RRSIGRecordContent>> signatures;
if(j->d_ttl>(unsigned int) d_now.tv_sec) {
DNSRecord dr=*j;
ttl = (dr.d_ttl-=d_now.tv_sec);
- if(giveNegative) {
- dr.d_place=DNSResourceRecord::AUTHORITY;
- dr.d_ttl=sttl;
- }
ret.push_back(dr);
LOG("[ttl="<<dr.d_ttl<<"] ");
found=true;
dr.d_name=sqname;
dr.d_ttl=ttl;
dr.d_content=signature;
- dr.d_place= giveNegative ? DNSResourceRecord::AUTHORITY : DNSResourceRecord::ANSWER;
+ dr.d_place = DNSResourceRecord::ANSWER;
dr.d_class=1;
ret.push_back(dr);
}
return answer.getCode() == QType::A || answer.getCode() == QType::AAAA;
}
-
-static recsig_t harvestRecords(const vector<DNSRecord>& records, const set<uint16_t>& types)
-{
- recsig_t ret;
+/* Fills the authoritySOA and DNSSECRecords fields from ne with those found in the records
+ *
+ * \param records The records to parse for the authority SOA and NSEC(3) records
+ * \param ne The NegCacheEntry to be filled out (will not be cleared, only appended to
+ */
+static void harvestNXRecords(const vector<DNSRecord>& records, NegCache::NegCacheEntry& ne) {
+ static const set<uint16_t> nsecTypes = {QType::NSEC, QType::NSEC3};
for(const auto& rec : records) {
+ if(rec.d_place != DNSResourceRecord::AUTHORITY)
+ // RFC 4035 section 3.1.3. indicates that NSEC records MUST be placed in
+ // the AUTHORITY section. Section 3.1.1 indicates that that RRSIGs for
+ // records MUST be in the same section as the records they cover.
+ // Hence, we ignore all records outside of the AUTHORITY section.
+ continue;
+
if(rec.d_type == QType::RRSIG) {
- auto rrs=getRR<RRSIGRecordContent>(rec);
- if(rrs && types.count(rrs->d_type))
- ret[make_pair(rec.d_name, rrs->d_type)].signatures.push_back(rec);
+ auto rrsig = getRR<RRSIGRecordContent>(rec);
+ if(rrsig) {
+ if(rrsig->d_type == QType::SOA) {
+ ne.authoritySOA.signatures.push_back(rec);
+ }
+ if(nsecTypes.count(rrsig->d_type)) {
+ ne.DNSSECRecords.signatures.push_back(rec);
+ }
+ }
+ continue;
+ }
+ if(rec.d_type == QType::SOA) {
+ ne.authoritySOA.records.push_back(rec);
+ continue;
+ }
+ if(nsecTypes.count(rec.d_type)) {
+ ne.DNSSECRecords.records.push_back(rec);
+ continue;
}
- else if(types.count(rec.d_type))
- ret[make_pair(rec.d_name, rec.d_type)].records.push_back(rec);
}
- return ret;
}
+// TODO remove after processRecords is fixed!
+// Adds the RRSIG for the SOA and the NSEC(3) + RRSIGs to ret
static void addNXNSECS(vector<DNSRecord>&ret, const vector<DNSRecord>& records)
{
- auto csp = harvestRecords(records, {QType::NSEC, QType::NSEC3, QType::SOA});
- for(const auto& c : csp) {
- if(c.first.second == QType::NSEC || c.first.second == QType::NSEC3 || c.first.second == QType::SOA) {
- if(c.first.second !=QType::SOA) {
- for(const auto& rec : c.second.records)
- ret.push_back(rec);
- }
- for(const auto& rec : c.second.signatures)
- ret.push_back(rec);
- }
- }
+ NegCache::NegCacheEntry ne;
+ harvestNXRecords(records, ne);
+ ret.insert(ret.end(), ne.authoritySOA.signatures.begin(), ne.authoritySOA.signatures.end());
+ ret.insert(ret.end(), ne.DNSSECRecords.records.begin(), ne.DNSSECRecords.records.end());
+ ret.insert(ret.end(), ne.DNSSECRecords.signatures.begin(), ne.DNSSECRecords.signatures.end());
}
bool SyncRes::nameserversBlockedByRPZ(const DNSFilterEngine& dfe, const NsSet& nameservers)
if(newtarget.empty()) // only add a SOA if we're not going anywhere after this
ret.push_back(rec);
if(!wasVariable()) {
- NegCacheEntry ne;
-
- ne.d_qname=rec.d_name;
- ne.d_ttd=d_now.tv_sec + rec.d_ttl;
- ne.d_name=qname;
- ne.d_qtype=QType(0); // this encodes 'whole record'
- ne.d_dnssecProof = harvestRecords(lwr.d_records, {QType::NSEC, QType::NSEC3});
- replacing_insert(t_sstorage->negcache, ne);
- if(s_rootNXTrust && auth.isRoot()) {
- ne.d_name = getLastLabel(ne.d_name);
- replacing_insert(t_sstorage->negcache, ne);
+ NegCache::NegCacheEntry ne;
+
+ ne.d_name = rec.d_name;
+ ne.d_ttd = d_now.tv_sec + rec.d_ttl;
+ ne.d_name = qname;
+ ne.d_qtype = QType(0); // this encodes 'whole record'
+ ne.d_auth = auth;
+ harvestNXRecords(lwr.d_records, ne);
+ t_sstorage->negcache.add(ne);
+ if(s_rootNXTrust && auth.isRoot()) { // We should check if it was forwarded here, see issue #5107
+ ne.d_name = ne.d_name.getLastLabel();
+ t_sstorage->negcache.add(ne);
}
}
rec.d_ttl = min(s_maxnegttl, rec.d_ttl);
ret.push_back(rec);
if(!wasVariable()) {
- NegCacheEntry ne;
- ne.d_qname=rec.d_name;
- ne.d_ttd=d_now.tv_sec + rec.d_ttl;
- ne.d_name=qname;
- ne.d_qtype=qtype;
- ne.d_dnssecProof = harvestRecords(lwr.d_records, {QType::NSEC, QType::NSEC3});
+ NegCache::NegCacheEntry ne;
+ ne.d_auth = rec.d_name;
+ ne.d_ttd = d_now.tv_sec + rec.d_ttl;
+ ne.d_name = qname;
+ ne.d_qtype = qtype;
+ harvestNXRecords(lwr.d_records, ne);
if(qtype.getCode()) { // prevents us from blacking out a whole domain
- replacing_insert(t_sstorage->negcache, ne);
+ t_sstorage->negcache.add(ne);
}
}
negindic=true;
LOG(prefix<<qname<<": query handled by Lua"<<endl);
}
else {
- ednsmask=getEDNSSubnetMask(d_requestor, qname, *remoteIP, d_incomingECSFound ? d_incomingECS : boost::none);
+ ednsmask=getEDNSSubnetMask(d_requestor, qname, *remoteIP);
if(ednsmask) {
LOG(prefix<<qname<<": Adding EDNS Client Subnet Mask "<<ednsmask->toString()<<" to query"<<endl);
}
return -1;
}
+boost::optional<Netmask> SyncRes::getEDNSSubnetMask(const ComboAddress& local, const DNSName&dn, const ComboAddress& rem)
+{
+ boost::optional<Netmask> result;
+ ComboAddress trunc;
+ uint8_t bits;
+ if(d_incomingECSFound) {
+ if (d_incomingECS->source.getBits() == 0) {
+ /* RFC7871 says we MUST NOT send any ECS if the source scope is 0 */
+ return result;
+ }
+ trunc = d_incomingECS->source.getMaskedNetwork();
+ 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;
+ }
+ else {
+ /* nothing usable */
+ return result;
+ }
+
+ if(g_ednsdomains.check(dn) || g_ednssubnets.match(rem)) {
+ bits = std::min(bits, (trunc.isIPv4() ? s_ecsipv4limit : s_ecsipv6limit));
+ trunc.truncate(bits);
+ return boost::optional<Netmask>(Netmask(trunc, bits));
+ }
+
+ return result;
+}
// 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)
return res;
}
+
+#include "validate-recursor.hh"
+
+int SyncRes::getRootNS(struct timeval now, asyncresolve_t asyncCallback) {
+ SyncRes sr(now);
+ sr.setDoEDNS0(true);
+ sr.setNoCache();
+ sr.setDoDNSSEC(g_dnssecmode != DNSSECMode::Off);
+ sr.setAsyncCallback(asyncCallback);
+
+ vector<DNSRecord> ret;
+ int res=-1;
+ try {
+ res=sr.beginResolve(g_rootdnsname, QType(QType::NS), 1, ret);
+ if (g_dnssecmode != DNSSECMode::Off && g_dnssecmode != DNSSECMode::ProcessNoValidate) {
+ ResolveContext ctx;
+ auto state = validateRecords(ctx, ret);
+ if (state == Bogus)
+ throw PDNSException("Got Bogus validation result for .|NS");
+ }
+ return res;
+ }
+ catch(PDNSException& e)
+ {
+ L<<Logger::Error<<"Failed to update . records, got an exception: "<<e.reason<<endl;
+ }
+
+ catch(std::exception& e)
+ {
+ L<<Logger::Error<<"Failed to update . records, got an exception: "<<e.what()<<endl;
+ }
+
+ catch(...)
+ {
+ L<<Logger::Error<<"Failed to update . records, got an exception"<<endl;
+ }
+ if(!res) {
+ L<<Logger::Notice<<"Refreshed . records"<<endl;
+ }
+ else
+ L<<Logger::Error<<"Failed to update . records, RCODE="<<res<<endl;
+ return res;
+}
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
-#ifndef PDNS_SYNCRES_HH
-#define PDNS_SYNCRES_HH
+#pragma once
#include <string>
#include <atomic>
#include "utility.hh"
#include "validate.hh"
#include "ednssubnet.hh"
#include "filterpo.hh"
+#include "negcache.hh"
+#ifdef HAVE_CONFIG_H
#include "config.h"
+#endif
+
#ifdef HAVE_PROTOBUF
#include <boost/uuid/uuid.hpp>
#include <boost/uuid/uuid_generators.hpp>
#endif
void primeHints(void);
-int getRootNS(void);
-class RecursorLua4;
-
-struct BothRecordsAndSignatures
-{
- vector<DNSRecord> records;
- vector<DNSRecord> signatures;
-};
-typedef map<pair<DNSName,uint16_t>, BothRecordsAndSignatures> recsig_t;
-struct NegCacheEntry
-{
- DNSName d_name;
- QType d_qtype;
- DNSName d_qname;
- uint32_t d_ttd;
- uint32_t getTTD() const
- {
- return d_ttd;
- }
- recsig_t d_dnssecProof;
-};
+class RecursorLua4;
typedef map<
DNSName,
typename cont_t::iterator i=d_cont.find(t);
if(i==d_cont.end())
return false;
- if(now > i->second.ttd || i->second.count-- < 0) {
+ if(now > i->second.ttd || i->second.count == 0) {
d_cont.erase(i);
return false;
}
+ i->second.count--;
return true; // still listed, still blocked
}
d_cont[t]=e;
}
- unsigned int size()
+ unsigned int size() const
{
return (unsigned int)d_cont.size();
}
+
+ void clear()
+ {
+ d_cont.clear();
+ }
private:
unsigned int d_limit;
time_t d_ttl;
return d_val*=factor;
}
- double peek(void)
+ double peek(void) const
{
return d_val;
}
Counters()
{
}
- unsigned long value(const Thing& t)
+ unsigned long value(const Thing& t) const
{
- typename cont_t::iterator i=d_cont.find(t);
+ typename cont_t::const_iterator i=d_cont.find(t);
if(i==d_cont.end()) {
return 0;
d_cont.erase(i);
}
}
- size_t size()
+ void clear()
+ {
+ d_cont.clear();
+ }
+ size_t size() const
{
return d_cont.size();
}
explicit SyncRes(const struct timeval& now);
+ typedef std::function<int(const ComboAddress& ip, const DNSName& qdomain, int qtype, bool doTCP, bool sendRDQuery, int EDNS0Level, struct timeval* now, boost::optional<Netmask>& srcmask, boost::optional<const ResolveContext&> context, std::shared_ptr<RemoteLogger> outgoingLogger, LWResult *lwr)> asyncresolve_t;
+
int beginResolve(const DNSName &qname, const QType &qtype, uint16_t qclass, vector<DNSRecord>&ret);
void setId(int id)
{
d_doEDNS0=state;
}
+ void setDoDNSSEC(bool state=true)
+ {
+ d_doDNSSEC=state;
+ }
+
+ void setWantsRPZ(bool state=true)
+ {
+ d_wantsRPZ=state;
+ }
+
+ bool getWantsRPZ() const
+ {
+ return d_wantsRPZ;
+ }
+
+ void setIncomingECSFound(bool state=true)
+ {
+ d_incomingECSFound=state;
+ }
+
string getTrace() const
{
return d_trace.str();
return d_wasOutOfBand;
}
+ struct timeval getNow() const
+ {
+ return d_now;
+ }
+
void setSkipCNAMECheck(bool skip = false)
{
d_skipCNAMECheck = skip;
}
- 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;
+ void setIncomingECS(boost::optional<const EDNSSubnetOpts&> incomingECS)
+ {
+ d_incomingECS = incomingECS;
+ }
+
+#ifdef HAVE_PROTOBUF
+ void setInitialRequestId(boost::optional<const boost::uuids::uuid&> initialRequestId)
+ {
+ d_initialRequestId = initialRequestId;
+ }
+#endif
+
+ void setAsyncCallback(asyncresolve_t func)
+ {
+ d_asyncResolve = func;
+ }
static void doEDNSDumpAndClose(int fd);
+ static int getRootNS(struct timeval now, asyncresolve_t asyncCallback);
static std::atomic<uint64_t> s_queries;
static std::atomic<uint64_t> s_outgoingtimeouts;
static unsigned int s_maxdepth;
std::unordered_map<std::string,bool> d_discardedPolicies;
DNSFilterEngine::Policy d_appliedPolicy;
- boost::optional<const EDNSSubnetOpts&> d_incomingECS;
-#ifdef HAVE_PROTOBUF
- boost::optional<const boost::uuids::uuid&> d_initialRequestId;
-#endif
unsigned int d_outqueries;
unsigned int d_tcpoutqueries;
unsigned int d_throttledqueries;
unsigned int d_unreachables;
unsigned int d_totUsec;
ComboAddress d_requestor;
- bool d_doDNSSEC;
-
- bool d_wasVariable{false};
- bool d_wasOutOfBand{false};
- bool d_wantsRPZ{true};
- bool d_skipCNAMECheck{false};
- bool d_incomingECSFound{false};
-
- typedef multi_index_container <
- NegCacheEntry,
- indexed_by <
- ordered_unique<
- composite_key<
- NegCacheEntry,
- member<NegCacheEntry, DNSName, &NegCacheEntry::d_name>,
- member<NegCacheEntry, QType, &NegCacheEntry::d_qtype>
- >,
- composite_key_compare<CanonDNSNameCompare, std::less<QType> >
- >,
- sequenced<>
- >
- > negcache_t;
//! This represents a number of decaying Ewmas, used to store performance per nameserver-name.
/** Modelled to work mostly like the underlying DecayingEwma. After you've called get,
typedef Counters<ComboAddress> fails_t;
- struct timeval d_now;
static unsigned int s_maxnegttl;
static unsigned int s_maxcachettl;
static unsigned int s_packetcachettl;
static unsigned int s_packetcacheservfailttl;
static unsigned int s_serverdownmaxfails;
static unsigned int s_serverdownthrottletime;
+ static uint8_t s_ecsipv4limit;
+ static uint8_t s_ecsipv6limit;
static bool s_nopacketcache;
static string s_serverID;
struct StaticStorage {
- negcache_t negcache;
nsspeeds_t nsSpeeds;
ednsstatus_t ednsstatus;
throttle_t throttle;
fails_t fails;
domainmap_t* domainmap;
map<DNSName, bool> dnssecmap;
+ NegCache negcache;
};
private:
RCode::rcodes_ updateCacheFromRecords(const std::string& prefix, LWResult& lwr, const DNSName& qname, const DNSName& auth, NsSet& nameservers, const DNSName& tns, const boost::optional<Netmask>);
bool processRecords(const std::string& prefix, const DNSName& qname, const QType& qtype, const DNSName& auth, LWResult& lwr, const bool sendRDQuery, vector<DNSRecord>& ret, set<DNSName>& nsset, DNSName& newtarget, DNSName& newauth, bool& realreferral, bool& negindic, bool& sawDS);
-private:
+ bool doSpecialNamesResolve(const DNSName &qname, const QType &qtype, const uint16_t &qclass, vector<DNSRecord> &ret);
+
+ 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);
+
ostringstream d_trace;
shared_ptr<RecursorLua4> d_pdl;
+ boost::optional<const EDNSSubnetOpts&> d_incomingECS;
+#ifdef HAVE_PROTOBUF
+ boost::optional<const boost::uuids::uuid&> d_initialRequestId;
+#endif
+ asyncresolve_t d_asyncResolve{nullptr};
+ struct timeval d_now;
string d_prefix;
+
+ /* When d_cacheonly is set to true, we will only check the cache.
+ * This is set when the RD bit is unset in the incoming query
+ */
bool d_cacheonly;
+ /* d_nocache is *only* set in getRootNS() (in pdns_recursor.cc).
+ * It forces us to not look in the cache or local auth.
+ */
bool d_nocache;
- bool d_doEDNS0;
+ bool d_doDNSSEC;
+ bool d_doEDNS0{true};
+ bool d_incomingECSFound{false};
+ bool d_skipCNAMECheck{false};
+ bool d_wantsRPZ{true};
+ bool d_wasOutOfBand{false};
+ bool d_wasVariable{false};
static LogMode s_lm;
LogMode d_lm;
TCPConnection(int fd, const ComboAddress& addr);
~TCPConnection();
- int getFD()
+ int getFD() const
{
return d_fd;
}
uint64_t* pleaseWipePacketCache(const DNSName& canon, bool subtree);
uint64_t* pleaseWipeAndCountNegCache(const DNSName& canon, bool subtree=false);
void doCarbonDump(void*);
-boost::optional<Netmask> getEDNSSubnetMask(const ComboAddress& local, const DNSName&dn, const ComboAddress& rem, boost::optional<const EDNSSubnetOpts&> incomingECS);
void parseEDNSSubnetWhitelist(const std::string& wlist);
extern __thread struct timeval g_now;
#ifdef HAVE_PROTOBUF
extern __thread boost::uuids::random_generator* t_uuidGenerator;
#endif
-
-#endif
#include "config.h"
#endif
#include <boost/algorithm/string.hpp>
-#include "packetcache.hh"
+#include "auth-packetcache.hh"
#include "utility.hh"
#include "dnssecinfra.hh"
#include "dnsseckeeper.hh"
#include <cstring>
#include <cstdlib>
#include <sys/types.h>
+#include <netinet/tcp.h>
#include <iostream>
#include <string>
#include "tcpreceiver.hh"
#include "namespaces.hh"
#include "signingpipe.hh"
#include "stubresolver.hh"
-extern PacketCache PC;
+extern AuthPacketCache PC;
extern StatBag S;
/**
}
}
+ // Group records by name and type, signpipe stumbles over interrupted rrsets
+ sort(zrrs.begin(), zrrs.end(), [](const DNSZoneRecord& a, const DNSZoneRecord& b) {
+ return tie(a.dr.d_name, a.dr.d_type) < tie(b.dr.d_name, b.dr.d_type);
+ });
+
if(rectify) {
// set auth
for(DNSZoneRecord &zrr : zrrs) {
L<<Logger::Error<<"Setsockopt failed"<<endl;
exit(1);
}
-
+
+ if (::arg().asNum("tcp-fast-open") > 0) {
+#ifdef TCP_FASTOPEN
+ int fastOpenQueueSize = ::arg().asNum("tcp-fast-open");
+ if (setsockopt(s, IPPROTO_TCP, TCP_FASTOPEN, &fastOpenQueueSize, sizeof fastOpenQueueSize) < 0) {
+ L<<Logger::Error<<"Failed to enable TCP Fast Open for listening socket: "<<strerror(errno)<<endl;
+ }
+#else
+ L<<Logger::Warning<<"TCP Fast Open configured but not supported for listening socket"<<endl;
+#endif
+ }
+
if( ::arg().mustDo("non-local-bind") )
Utility::setBindAny(AF_INET, s);
L<<Logger::Error<<"Setsockopt failed"<<endl;
exit(1);
}
+
+ if (::arg().asNum("tcp-fast-open") > 0) {
+#ifdef TCP_FASTOPEN
+ int fastOpenQueueSize = ::arg().asNum("tcp-fast-open");
+ if (setsockopt(s, IPPROTO_TCP, TCP_FASTOPEN, &fastOpenQueueSize, sizeof fastOpenQueueSize) < 0) {
+ L<<Logger::Error<<"Failed to enable TCP Fast Open for listening socket: "<<strerror(errno)<<endl;
+ }
+#else
+ L<<Logger::Warning<<"TCP Fast Open configured but not supported for listening socket"<<endl;
+#endif
+ }
+
if( ::arg().mustDo("non-local-bind") )
Utility::setBindAny(AF_INET6, s);
if(setsockopt(s, IPPROTO_IPV6, IPV6_V6ONLY, &tmp, sizeof(tmp)) < 0) {
BOOST_CHECK_THROW(name.getRawLabel(name.countLabels()), std::out_of_range);
}
+BOOST_AUTO_TEST_CASE(test_getlastlabel) {
+ DNSName name("www.powerdns.com");
+ DNSName ans = name.getLastLabel();
+
+ // Check the const-ness
+ BOOST_CHECK_EQUAL(name, DNSName("www.powerdns.com"));
+
+ // Check if the last label is indeed returned
+ BOOST_CHECK_EQUAL(ans, DNSName("com"));
+}
BOOST_AUTO_TEST_SUITE_END()
--- /dev/null
+#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 "lock.hh"
+#include <thread>
+
+using namespace boost;
+
+BOOST_AUTO_TEST_SUITE(test_lock_hh)
+
+static std::vector<std::unique_ptr<pthread_rwlock_t> > g_locks;
+
+static void lthread()
+{
+ std::vector<ReadLock> rlocks;
+ for(auto& pp : g_locks)
+ rlocks.emplace_back(&*pp);
+
+}
+
+BOOST_AUTO_TEST_CASE(test_pdns_lock)
+{
+ for(unsigned int n=0; n < 1000; ++n) {
+ auto p = new pthread_rwlock_t;
+ pthread_rwlock_init(p, 0);
+ g_locks.emplace_back(p);
+ }
+
+ std::vector<ReadLock> rlocks;
+ for(auto& pp : g_locks)
+ rlocks.emplace_back(&*pp);
+
+ std::thread thr(lthread);
+ thr.join();
+ rlocks.clear();
+
+ std::vector<WriteLock> wlocks;
+ for(auto& pp : g_locks)
+ wlocks.emplace_back(&*pp);
+
+ TryReadLock trl(&*g_locks[0]);
+ BOOST_CHECK(!trl.gotIt());
+
+ wlocks.clear();
+ TryReadLock trl2(&*g_locks[0]);
+ BOOST_CHECK(trl2.gotIt());
+
+
+}
+
+BOOST_AUTO_TEST_SUITE_END()
#include "iputils.hh"
#include "nameserver.hh"
#include "statbag.hh"
-#include "packetcache.hh"
+#include "auth-packetcache.hh"
+#include "auth-querycache.hh"
#include "arguments.hh"
#include <utility>
extern StatBag S;
BOOST_AUTO_TEST_SUITE(packetcache_cc)
-BOOST_AUTO_TEST_CASE(test_PacketCacheSimple) {
- PacketCache PC;
+BOOST_AUTO_TEST_CASE(test_AuthQueryCacheSimple) {
+ AuthQueryCache QC;
+ QC.setMaxEntries(1000000);
- ::arg().set("max-cache-entries", "Maximum number of cache entries")="1000000";
- ::arg().set("cache-ttl","Seconds to store packets in the PacketCache")="20";
- ::arg().set("negquery-cache-ttl","Seconds to store negative query results in the QueryCache")="60";
- ::arg().set("query-cache-ttl","Seconds to store query results in the QueryCache")="20";
+ vector<DNSZoneRecord> records;
- S.declare("deferred-cache-inserts","Amount of cache inserts that were deferred because of maintenance");
- S.declare("deferred-cache-lookup","Amount of cache lookups that were deferred because of maintenance");
+ BOOST_CHECK_EQUAL(QC.size(), 0);
+ QC.insert(DNSName("hello"), QType(QType::A), records, 3600, 1);
+ BOOST_CHECK_EQUAL(QC.size(), 1);
+ BOOST_CHECK_EQUAL(QC.purge(), 1);
+ BOOST_CHECK_EQUAL(QC.size(), 0);
-
- BOOST_CHECK_EQUAL(PC.size(), 0);
- PC.insert(DNSName("hello"), QType(QType::A), PacketCache::QUERYCACHE, "something", 3600, 1);
- BOOST_CHECK_EQUAL(PC.size(), 1);
- PC.purge();
- BOOST_CHECK_EQUAL(PC.size(), 0);
-
- int counter=0;
+ uint64_t counter=0;
try {
for(counter = 0; counter < 100000; ++counter) {
DNSName a=DNSName("hello ")+DNSName(std::to_string(counter));
BOOST_CHECK_EQUAL(DNSName(a.toString()), a);
- PC.insert(a, QType(QType::A), PacketCache::QUERYCACHE, "something", 3600, 1);
- if(!PC.purge(a.toString()))
- BOOST_FAIL("Could not remove entry we just added to packet cache!");
- PC.insert(a, QType(QType::A), PacketCache::QUERYCACHE, "something", 3600, 1);
+ QC.insert(a, QType(QType::A), records, 3600, 1);
+ if(!QC.purge(a.toString()))
+ BOOST_FAIL("Could not remove entry we just added to the query cache!");
+ QC.insert(a, QType(QType::A), records, 3600, 1);
}
- BOOST_CHECK_EQUAL(PC.size(), counter);
-
- int delcounter=0;
+ BOOST_CHECK_EQUAL(QC.size(), counter);
+
+ uint64_t delcounter=0;
for(delcounter=0; delcounter < counter/100; ++delcounter) {
DNSName a=DNSName("hello ")+DNSName(std::to_string(delcounter));
- PC.purge(a.toString());
+ BOOST_CHECK_EQUAL(QC.purge(a.toString()), 1);
}
-
- BOOST_CHECK_EQUAL(PC.size(), counter-delcounter);
-
- int matches=0;
+
+ BOOST_CHECK_EQUAL(QC.size(), counter-delcounter);
+
+ uint64_t matches=0;
vector<DNSZoneRecord> entry;
- int expected=counter-delcounter;
+ int64_t expected=counter-delcounter;
for(; delcounter < counter; ++delcounter) {
- if(PC.getEntry(DNSName("hello ")+DNSName(std::to_string(delcounter)), QType(QType::A), PacketCache::QUERYCACHE, entry, 1)) {
+ if(QC.getEntry(DNSName("hello ")+DNSName(std::to_string(delcounter)), QType(QType::A), entry, 1)) {
matches++;
}
}
BOOST_CHECK_EQUAL(matches, expected);
- // BOOST_CHECK_EQUAL(entry, "something");
+ BOOST_CHECK_EQUAL(entry.size(), records.size());
}
catch(PDNSException& e) {
cerr<<"Had error: "<<e.reason<<endl;
}
-static PacketCache* g_PC;
+static AuthQueryCache* g_QC;
+static AtomicCounter g_QCmissing;
-static void *threadMangler(void* a)
+static void *threadQCMangler(void* a)
try
{
+ vector<DNSZoneRecord> records;
unsigned int offset=(unsigned int)(unsigned long)a;
for(unsigned int counter=0; counter < 100000; ++counter)
- g_PC->insert(DNSName("hello ")+DNSName(std::to_string(counter+offset)), QType(QType::A), PacketCache::QUERYCACHE, "something", 3600, 1);
+ g_QC->insert(DNSName("hello ")+DNSName(std::to_string(counter+offset)), QType(QType::A), records, 3600, 1);
return 0;
}
catch(PDNSException& e) {
throw;
}
-AtomicCounter g_missing;
-
-static void *threadReader(void* a)
+static void *threadQCReader(void* a)
try
{
unsigned int offset=(unsigned int)(unsigned long)a;
vector<DNSZoneRecord> entry;
for(unsigned int counter=0; counter < 100000; ++counter)
- if(!g_PC->getEntry(DNSName("hello ")+DNSName(std::to_string(counter+offset)), QType(QType::A), PacketCache::QUERYCACHE, entry, 1)) {
- g_missing++;
+ if(!g_QC->getEntry(DNSName("hello ")+DNSName(std::to_string(counter+offset)), QType(QType::A), entry, 1)) {
+ g_QCmissing++;
}
return 0;
}
catch(PDNSException& e) {
- cerr<<"Had error in threadReader: "<<e.reason<<endl;
+ cerr<<"Had error in threadQCReader: "<<e.reason<<endl;
throw;
}
+BOOST_AUTO_TEST_CASE(test_QueryCacheThreaded) {
+ try {
+ AuthQueryCache QC;
+ QC.setMaxEntries(1000000);
+ g_QC=&QC;
+ pthread_t tid[4];
+ for(int i=0; i < 4; ++i)
+ pthread_create(&tid[i], 0, threadQCMangler, (void*)(i*1000000UL));
+ void* res;
+ for(int i=0; i < 4 ; ++i)
+ pthread_join(tid[i], &res);
+
+ BOOST_CHECK_EQUAL(QC.size() + S.read("deferred-cache-inserts"), 400000);
+ BOOST_CHECK_SMALL(1.0*S.read("deferred-cache-inserts"), 10000.0);
+
+ for(int i=0; i < 4; ++i)
+ pthread_create(&tid[i], 0, threadQCReader, (void*)(i*1000000UL));
+ for(int i=0; i < 4 ; ++i)
+ pthread_join(tid[i], &res);
+
+ BOOST_CHECK(S.read("deferred-cache-inserts") + S.read("deferred-cache-lookup") >= g_QCmissing);
+ // BOOST_CHECK_EQUAL(S.read("deferred-cache-lookup"), 0); // cache cleaning invalidates this
+ }
+ catch(PDNSException& e) {
+ cerr<<"Had error: "<<e.reason<<endl;
+ throw;
+ }
+
+}
+
+static AuthPacketCache* g_PC;
+static AtomicCounter g_PCmissing;
+
+static void *threadPCMangler(void* a)
+try
+{
+ unsigned int offset=(unsigned int)(unsigned long)a;
+ for(unsigned int counter=0; counter < 100000; ++counter) {
+ vector<uint8_t> pak;
+ DNSName qname = DNSName("hello ")+DNSName(std::to_string(counter+offset));
+
+ DNSPacketWriter pw(pak, qname, QType::A);
+ DNSPacket q(true);
+ q.parse((char*)&pak[0], pak.size());
+
+ pak.clear();
+ DNSPacketWriter pw2(pak, qname, QType::A);
+ pw2.startRecord(qname, QType::A, 16, 1, DNSResourceRecord::ANSWER);
+ pw2.xfrIP(htonl(0x7f000001));
+ pw2.commit();
+
+ DNSPacket r(false);
+ r.parse((char*)&pak[0], pak.size());
+
+ /* this step is necessary to get a valid hash */
+ DNSPacket cached(false);
+ g_PC->get(&q, &cached);
+
+ g_PC->insert(&q, &r, 10);
+ }
+
+ return 0;
+}
+ catch(PDNSException& e) {
+ cerr<<"Had error: "<<e.reason<<endl;
+ throw;
+ }
+
+static void *threadPCReader(void* a)
+try
+{
+ unsigned int offset=(unsigned int)(unsigned long)a;
+ vector<DNSZoneRecord> entry;
+ for(unsigned int counter=0; counter < 100000; ++counter) {
+ vector<uint8_t> pak;
+ DNSName qname = DNSName("hello ")+DNSName(std::to_string(counter+offset));
+
+ DNSPacketWriter pw(pak, qname, QType::A);
+ DNSPacket q(true);
+ q.parse((char*)&pak[0], pak.size());
+ DNSPacket r(false);
+
+ if(!g_PC->get(&q, &r)) {
+ g_PCmissing++;
+ }
+ }
+ return 0;
+}
+catch(PDNSException& e) {
+ cerr<<"Had error in threadPCReader: "<<e.reason<<endl;
+ throw;
+}
BOOST_AUTO_TEST_CASE(test_PacketCacheThreaded) {
try {
- PacketCache PC;
+ AuthPacketCache PC;
+ PC.setMaxEntries(1000000);
+ PC.setTTL(20);
+
g_PC=&PC;
pthread_t tid[4];
- for(int i=0; i < 4; ++i)
- pthread_create(&tid[i], 0, threadMangler, (void*)(i*1000000UL));
+ for(int i=0; i < 4; ++i)
+ pthread_create(&tid[i], 0, threadPCMangler, (void*)(i*1000000UL));
void* res;
for(int i=0; i < 4 ; ++i)
pthread_join(tid[i], &res);
-
- BOOST_CHECK_EQUAL(PC.size() + S.read("deferred-cache-inserts"), 400000);
- BOOST_CHECK_SMALL(1.0*S.read("deferred-cache-inserts"), 10000.0);
- for(int i=0; i < 4; ++i)
- pthread_create(&tid[i], 0, threadReader, (void*)(i*1000000UL));
+ BOOST_CHECK_EQUAL(PC.size() + S.read("deferred-packetcache-inserts"), 400000);
+ BOOST_CHECK_SMALL(1.0*S.read("deferred-packetcache-inserts"), 10000.0);
+
+ for(int i=0; i < 4; ++i)
+ pthread_create(&tid[i], 0, threadPCReader, (void*)(i*1000000UL));
for(int i=0; i < 4 ; ++i)
pthread_join(tid[i], &res);
- BOOST_CHECK(S.read("deferred-cache-inserts") + S.read("deferred-cache-lookup") >= g_missing);
- // BOOST_CHECK_EQUAL(S.read("deferred-cache-lookup"), 0); // cache cleaning invalidates this
+/*
+ cerr<<"Misses: "<<S.read("packetcache-miss")<<endl;
+ cerr<<"Hits: "<<S.read("packetcache-hit")<<endl;
+ cerr<<"Deferred inserts: "<<S.read("deferred-packetcache-inserts")<<endl;
+ cerr<<"Deferred lookups: "<<S.read("deferred-packetcache-lookup")<<endl;
+*/
+ BOOST_CHECK_EQUAL(g_PCmissing + S.read("packetcache-hit"), 400000);
+ BOOST_CHECK_GT(S.read("deferred-packetcache-inserts") + S.read("deferred-packetcache-lookup"), g_PCmissing);
}
catch(PDNSException& e) {
cerr<<"Had error: "<<e.reason<<endl;
throw;
}
-
+
}
bool g_stopCleaning;
try
{
while(!g_stopCleaning) {
- g_PC->cleanup();
+ g_QC->cleanup();
}
return 0;
}
catch(PDNSException& e) {
- cerr<<"Had error in threadReader: "<<e.reason<<endl;
+ cerr<<"Had error in cacheCleaner: "<<e.reason<<endl;
throw;
}
-BOOST_AUTO_TEST_CASE(test_PacketCacheClean) {
+BOOST_AUTO_TEST_CASE(test_QueryCacheClean) {
try {
- PacketCache PC;
+ AuthQueryCache QC;
+ QC.setMaxEntries(10000);
+ vector<DNSZoneRecord> records;
for(unsigned int counter = 0; counter < 1000000; ++counter) {
- PC.insert(DNSName("hello ")+DNSName(std::to_string(counter)), QType(QType::A), PacketCache::QUERYCACHE, "something", 1, 1);
+ QC.insert(DNSName("hello ")+DNSName(std::to_string(counter)), QType(QType::A), records, 1, 1);
}
sleep(1);
-
- g_PC=&PC;
- pthread_t tid[4];
- ::arg().set("max-cache-entries")="10000";
+ g_QC=&QC;
+ pthread_t tid[4];
- pthread_create(&tid[0], 0, threadReader, (void*)(0*1000000UL));
- pthread_create(&tid[1], 0, threadReader, (void*)(1*1000000UL));
- pthread_create(&tid[2], 0, threadReader, (void*)(2*1000000UL));
+ pthread_create(&tid[0], 0, threadQCReader, (void*)(0*1000000UL));
+ pthread_create(&tid[1], 0, threadQCReader, (void*)(1*1000000UL));
+ pthread_create(&tid[2], 0, threadQCReader, (void*)(2*1000000UL));
// pthread_create(&tid[2], 0, threadMangler, (void*)(0*1000000UL));
pthread_create(&tid[3], 0, cacheCleaner, 0);
pthread_join(tid[3], &res);
}
catch(PDNSException& e) {
- cerr<<"Had error in threadReader: "<<e.reason<<endl;
+ cerr<<"Had error in test_QueryCacheClean: "<<e.reason<<endl;
throw;
}
}
-BOOST_AUTO_TEST_CASE(test_PacketCachePacket) {
+BOOST_AUTO_TEST_CASE(test_AuthPacketCache) {
try {
::arg().setSwitch("no-shuffle","Set this to prevent random shuffling of answers - for regression testing")="off";
- PacketCache PC;
+ AuthPacketCache PC;
+ PC.setTTL(20);
+ PC.setMaxEntries(100000);
+
vector<uint8_t> pak;
- vector<pair<uint16_t,string > > opts;
+ DNSPacket q(true), differentIDQ(true), ednsQ(true), ednsVersion42(true), ednsDO(true), ecs1(true), ecs2(true), ecs3(true);
+ DNSPacket r(false), r2(false);
- DNSPacketWriter pw(pak, DNSName("www.powerdns.com"), QType::A);
- DNSPacket q(true), r(false), r2(false);
- q.parse((char*)&pak[0], pak.size());
+ {
+ DNSPacketWriter pw(pak, DNSName("www.powerdns.com"), QType::A);
+ q.parse((char*)&pak[0], pak.size());
- pak.clear();
- DNSPacketWriter pw2(pak, DNSName("www.powerdns.com"), QType::A);
- pw2.startRecord(DNSName("www.powerdns.com"), QType::A, 16, 1, DNSResourceRecord::ANSWER);
- pw2.xfrIP(htonl(0x7f000001));
- pw2.commit();
+ differentIDQ.parse((char*)&pak[0], pak.size());
+ differentIDQ.setID(4242);
- r.parse((char*)&pak[0], pak.size());
+ pw.addOpt(512, 0, 0);
+ pw.commit();
+ ednsQ.parse((char*)&pak[0], pak.size());
+
+ pak.clear();
+ }
+
+ DNSPacketWriter::optvect_t opts;
+ EDNSSubnetOpts ecsOpts;
+ {
+ DNSPacketWriter pw(pak, DNSName("www.powerdns.com"), QType::A);
+ pw.addOpt(512, 0, 0, DNSPacketWriter::optvect_t(), 42);
+ pw.commit();
+ ednsVersion42.parse((char*)&pak[0], pak.size());
+ pak.clear();
+ }
+
+ {
+ DNSPacketWriter pw(pak, DNSName("www.powerdns.com"), QType::A);
+ pw.addOpt(512, 0, EDNSOpts::DNSSECOK);
+ pw.commit();
+ ednsDO.parse((char*)&pak[0], pak.size());
+ pak.clear();
+ }
+
+ {
+ ecsOpts.source = Netmask(ComboAddress("192.0.2.1"), 32);
+ opts.push_back(make_pair(EDNSOptionCode::ECS, makeEDNSSubnetOptsString(ecsOpts)));
+ DNSPacketWriter pw(pak, DNSName("www.powerdns.com"), QType::A);
+ pw.addOpt(512, 0, 0, opts);
+ pw.commit();
+ ecs1.parse((char*)&pak[0], pak.size());
+ pak.clear();
+ opts.clear();
+ }
+
+ {
+ DNSPacketWriter pw(pak, DNSName("www.powerdns.com"), QType::A);
+ ecsOpts.source = Netmask(ComboAddress("192.0.2.2"), 32);
+ opts.push_back(make_pair(EDNSOptionCode::ECS, makeEDNSSubnetOptsString(ecsOpts)));
+ pw.addOpt(512, 0, 0, opts);
+ pw.commit();
+ ecs2.parse((char*)&pak[0], pak.size());
+ pak.clear();
+ opts.clear();
+ }
+
+ {
+ DNSPacketWriter pw(pak, DNSName("www.powerdns.com"), QType::A);
+ ecsOpts.source = Netmask(ComboAddress("192.0.2.3"), 16);
+ opts.push_back(make_pair(EDNSOptionCode::ECS, makeEDNSSubnetOptsString(ecsOpts)));
+ pw.addOpt(512, 0, 0, opts);
+ pw.commit();
+ ecs3.parse((char*)&pak[0], pak.size());
+ pak.clear();
+ opts.clear();
+ }
+
+ {
+ DNSPacketWriter pw(pak, DNSName("www.powerdns.com"), QType::A);
+ pw.startRecord(DNSName("www.powerdns.com"), QType::A, 16, 1, DNSResourceRecord::ANSWER);
+ pw.xfrIP(htonl(0x7f000001));
+ pw.commit();
+
+ r.parse((char*)&pak[0], pak.size());
+ }
+
+ /* this call is required so the correct hash is set into q->d_hash */
+ BOOST_CHECK_EQUAL(PC.get(&q, &r2), false);
PC.insert(&q, &r, 3600);
+ BOOST_CHECK_EQUAL(PC.size(), 1);
+
+ BOOST_CHECK_EQUAL(PC.get(&q, &r2), true);
+ BOOST_CHECK_EQUAL(r2.qdomain, r.qdomain);
+
+ /* different QID, still should match */
+ BOOST_CHECK_EQUAL(PC.get(&differentIDQ, &r2), true);
+ BOOST_CHECK_EQUAL(r2.qdomain, r.qdomain);
+
+ /* with EDNS, should not match */
+ BOOST_CHECK_EQUAL(PC.get(&ednsQ, &r2), false);
+ /* inserting the EDNS-enabled one too */
+ PC.insert(&ednsQ, &r, 3600);
+ BOOST_CHECK_EQUAL(PC.size(), 2);
+
+ /* different EDNS versions, should not match */
+ BOOST_CHECK_EQUAL(PC.get(&ednsVersion42, &r2), false);
+
+ /* EDNS DO set, should not match */
+ BOOST_CHECK_EQUAL(PC.get(&ednsDO, &r2), false);
- BOOST_CHECK_EQUAL(PC.get(&q, &r2), 1);
+ /* EDNS Client Subnet set, should not match
+ since not only we don't skip the actual option, but the
+ total EDNS opt RR is still different. */
+ BOOST_CHECK_EQUAL(PC.get(&ecs1, &r2), false);
+
+ /* inserting the version with ECS Client Subnet set,
+ it should NOT replace the existing EDNS one. */
+ PC.insert(&ecs1, &r, 3600);
+ BOOST_CHECK_EQUAL(PC.size(), 3);
+
+ /* different subnet of same size, should NOT match
+ since we don't skip the option */
+ BOOST_CHECK_EQUAL(PC.get(&ecs2, &r2), false);
BOOST_CHECK_EQUAL(r2.qdomain, r.qdomain);
- PC.purge("www.powerdns.com");
- BOOST_CHECK_EQUAL(PC.get(&q, &r2), 0);
+ /* different subnet of different size, should NOT match. */
+ BOOST_CHECK_EQUAL(PC.get(&ecs3, &r2), false);
+
+ BOOST_CHECK_EQUAL(PC.purge("www.powerdns.com"), 3);
+ BOOST_CHECK_EQUAL(PC.get(&q, &r2), false);
+ BOOST_CHECK_EQUAL(PC.size(), 0);
PC.insert(&q, &r, 3600);
- BOOST_CHECK_EQUAL(PC.get(&q, &r2), 1);
+ BOOST_CHECK_EQUAL(PC.size(), 1);
+ BOOST_CHECK_EQUAL(PC.get(&q, &r2), true);
BOOST_CHECK_EQUAL(r2.qdomain, r.qdomain);
- PC.purge("com$");
- BOOST_CHECK_EQUAL(PC.get(&q, &r2), 0);
+ BOOST_CHECK_EQUAL(PC.purge("com$"), 1);
+ BOOST_CHECK_EQUAL(PC.get(&q, &r2), false);
+ BOOST_CHECK_EQUAL(PC.size(), 0);
PC.insert(&q, &r, 3600);
- BOOST_CHECK_EQUAL(PC.get(&q, &r2), 1);
+ BOOST_CHECK_EQUAL(PC.size(), 1);
+ BOOST_CHECK_EQUAL(PC.get(&q, &r2), true);
BOOST_CHECK_EQUAL(r2.qdomain, r.qdomain);
- PC.purge("powerdns.com$");
- BOOST_CHECK_EQUAL(PC.get(&q, &r2), 0);
+ BOOST_CHECK_EQUAL(PC.purge("powerdns.com$"), 1);
+ BOOST_CHECK_EQUAL(PC.get(&q, &r2), false);
+ BOOST_CHECK_EQUAL(PC.size(), 0);
PC.insert(&q, &r, 3600);
- BOOST_CHECK_EQUAL(PC.get(&q, &r2), 1);
+ BOOST_CHECK_EQUAL(PC.size(), 1);
+ BOOST_CHECK_EQUAL(PC.get(&q, &r2), true);
BOOST_CHECK_EQUAL(r2.qdomain, r.qdomain);
- PC.purge("www.powerdns.com$");
- BOOST_CHECK_EQUAL(PC.get(&q, &r2), 0);
+ BOOST_CHECK_EQUAL(PC.purge("www.powerdns.com$"), 1);
+ BOOST_CHECK_EQUAL(PC.get(&q, &r2), false);
+ BOOST_CHECK_EQUAL(PC.size(), 0);
PC.insert(&q, &r, 3600);
- PC.purge("www.powerdns.net");
- BOOST_CHECK_EQUAL(PC.get(&q, &r2), 1);
+ BOOST_CHECK_EQUAL(PC.size(), 1);
+ BOOST_CHECK_EQUAL(PC.purge("www.powerdns.net"), 0);
+ BOOST_CHECK_EQUAL(PC.get(&q, &r2), true);
BOOST_CHECK_EQUAL(r2.qdomain, r.qdomain);
- PC.purge("net$");
- BOOST_CHECK_EQUAL(PC.get(&q, &r2), 1);
+ BOOST_CHECK_EQUAL(PC.size(), 1);
+
+ BOOST_CHECK_EQUAL(PC.purge("net$"), 0);
+ BOOST_CHECK_EQUAL(PC.get(&q, &r2), true);
BOOST_CHECK_EQUAL(r2.qdomain, r.qdomain);
- PC.purge("www.powerdns.com$");
+ BOOST_CHECK_EQUAL(PC.size(), 1);
+
+ BOOST_CHECK_EQUAL(PC.purge("www.powerdns.com$"), 1);
BOOST_CHECK_EQUAL(PC.size(), 0);
}
catch(PDNSException& e) {
- cerr<<"Had error in threadReader: "<<e.reason<<endl;
+ cerr<<"Had error in AuthPacketCache: "<<e.reason<<endl;
throw;
}
-}
+}
BOOST_AUTO_TEST_SUITE_END()
BOOST_AUTO_TEST_CASE(test_recPacketCacheSimple) {
RecursorPacketCache rpc;
string fpacket;
- int tag=0;
+ unsigned int tag=0;
uint32_t age=0;
uint32_t qhash=0;
uint32_t ttd=3600;
rpc.doWipePacketCache(DNSName("com"), 0xffff, true);
BOOST_CHECK_EQUAL(rpc.size(), 0);
-}
+}
+
+BOOST_AUTO_TEST_CASE(test_recPacketCache_Tags) {
+ /* Insert a response with tag1, the exact same query with a different tag
+ should lead to a miss. Inserting a different response with the second tag
+ should not override the first one, and we should get a hit for the
+ query with either tags, with the response matching the tag.
+ */
+ RecursorPacketCache rpc;
+ string fpacket;
+ const unsigned int tag1=0;
+ const unsigned int tag2=42;
+ uint32_t age=0;
+ uint32_t qhash=0;
+ uint32_t temphash=0;
+ uint32_t ttd=3600;
+ BOOST_CHECK_EQUAL(rpc.size(), 0);
+
+ DNSName qname("www.powerdns.com");
+ vector<uint8_t> packet;
+ DNSPacketWriter pw(packet, qname, QType::A);
+ pw.getHeader()->rd=true;
+ pw.getHeader()->qr=false;
+ pw.getHeader()->id=random();
+ string qpacket(reinterpret_cast<const char*>(&packet[0]), packet.size());
+ pw.startRecord(qname, QType::A, ttd);
+
+ /* Both interfaces (with and without the qname/qtype/qclass) should get the same hash */
+ BOOST_CHECK_EQUAL(rpc.getResponsePacket(tag1, qpacket, time(nullptr), &fpacket, &age, &qhash), false);
+ BOOST_CHECK_EQUAL(rpc.getResponsePacket(tag1, qpacket, qname, QType::A, QClass::IN, time(nullptr), &fpacket, &age, &temphash), false);
+ BOOST_CHECK_EQUAL(qhash, temphash);
+
+ /* Different tag, should still get get the same hash, for both interfaces */
+ BOOST_CHECK_EQUAL(rpc.getResponsePacket(tag2, qpacket, time(nullptr), &fpacket, &age, &temphash), false);
+ BOOST_CHECK_EQUAL(qhash, temphash);
+ BOOST_CHECK_EQUAL(rpc.getResponsePacket(tag2, qpacket, qname, QType::A, QClass::IN, time(nullptr), &fpacket, &age, &temphash), false);
+ BOOST_CHECK_EQUAL(qhash, temphash);
+
+ {
+ ARecordContent ar("127.0.0.1");
+ ar.toPacket(pw);
+ pw.commit();
+ }
+ string r1packet(reinterpret_cast<const char*>(&packet[0]), packet.size());
+
+ {
+ ARecordContent ar("127.0.0.2");
+ ar.toPacket(pw);
+ pw.commit();
+ }
+ string r2packet(reinterpret_cast<const char*>(&packet[0]), packet.size());
+
+ BOOST_CHECK(r1packet != r2packet);
+
+ /* inserting a response for tag1 */
+ rpc.insertResponsePacket(tag1, qhash, qname, QType::A, QClass::IN, r1packet, time(0), ttd);
+ BOOST_CHECK_EQUAL(rpc.size(), 1);
+
+ /* inserting a different response for tag2, should not override the first one */
+ rpc.insertResponsePacket(tag2, qhash, qname, QType::A, QClass::IN, r2packet, time(0), ttd);
+ BOOST_CHECK_EQUAL(rpc.size(), 2);
+
+ /* remove all responses from the cache */
+ rpc.doPruneTo(0);
+ BOOST_CHECK_EQUAL(rpc.size(), 0);
+
+ /* reinsert both */
+ rpc.insertResponsePacket(tag1, qhash, qname, QType::A, QClass::IN, r1packet, time(0), ttd);
+ BOOST_CHECK_EQUAL(rpc.size(), 1);
+
+ rpc.insertResponsePacket(tag2, qhash, qname, QType::A, QClass::IN, r2packet, time(0), ttd);
+ BOOST_CHECK_EQUAL(rpc.size(), 2);
+
+ /* remove the responses by qname, should remove both */
+ rpc.doWipePacketCache(qname);
+ BOOST_CHECK_EQUAL(rpc.size(), 0);
+
+ /* insert the response for tag1 */
+ rpc.insertResponsePacket(tag1, qhash, qname, QType::A, QClass::IN, r1packet, time(0), ttd);
+ BOOST_CHECK_EQUAL(rpc.size(), 1);
+
+ /* we can retrieve it */
+ BOOST_CHECK_EQUAL(rpc.getResponsePacket(tag1, qpacket, qname, QType::A, QClass::IN, time(nullptr), &fpacket, &age, &temphash), true);
+ BOOST_CHECK_EQUAL(qhash, temphash);
+ BOOST_CHECK_EQUAL(fpacket, r1packet);
+
+ /* with both interfaces */
+ BOOST_CHECK_EQUAL(rpc.getResponsePacket(tag1, qpacket, time(nullptr), &fpacket, &age, &temphash), true);
+ BOOST_CHECK_EQUAL(qhash, temphash);
+ BOOST_CHECK_EQUAL(fpacket, r1packet);
+
+ /* but not with the second tag */
+ BOOST_CHECK_EQUAL(rpc.getResponsePacket(tag2, qpacket, qname, QType::A, QClass::IN, time(nullptr), &fpacket, &age, &temphash), false);
+ /* we should still get the same hash */
+ BOOST_CHECK_EQUAL(temphash, qhash);
+
+ /* adding a response for the second tag */
+ rpc.insertResponsePacket(tag2, qhash, qname, QType::A, QClass::IN, r2packet, time(0), ttd);
+ BOOST_CHECK_EQUAL(rpc.size(), 2);
+
+ /* We still get the correct response for the first tag */
+ BOOST_CHECK_EQUAL(rpc.getResponsePacket(tag1, qpacket, time(nullptr), &fpacket, &age, &temphash), true);
+ BOOST_CHECK_EQUAL(qhash, temphash);
+ BOOST_CHECK_EQUAL(fpacket, r1packet);
+
+ BOOST_CHECK_EQUAL(rpc.getResponsePacket(tag1, qpacket, qname, QType::A, QClass::IN, time(nullptr), &fpacket, &age, &temphash), true);
+ BOOST_CHECK_EQUAL(qhash, temphash);
+ BOOST_CHECK_EQUAL(fpacket, r1packet);
+
+ /* and the correct response for the second tag */
+ BOOST_CHECK_EQUAL(rpc.getResponsePacket(tag2, qpacket, time(nullptr), &fpacket, &age, &temphash), true);
+ BOOST_CHECK_EQUAL(qhash, temphash);
+ BOOST_CHECK_EQUAL(fpacket, r2packet);
+
+ BOOST_CHECK_EQUAL(rpc.getResponsePacket(tag2, qpacket, qname, QType::A, QClass::IN, time(nullptr), &fpacket, &age, &temphash), true);
+ BOOST_CHECK_EQUAL(qhash, temphash);
+ BOOST_CHECK_EQUAL(fpacket, r2packet);
+}
BOOST_AUTO_TEST_SUITE_END()
#include <boost/assign/list_of.hpp>
#include <boost/tuple/tuple.hpp>
-#include <boost/iostreams/stream.hpp>
#include <boost/iostreams/device/file.hpp>
#include "dns.hh"
#include "zoneparser-tng.hh"
#include "config.h"
#endif
#include <boost/test/unit_test.hpp>
-#include "packetcache.hh"
+#include "auth-packetcache.hh"
+#include "auth-querycache.hh"
+#include "statbag.hh"
StatBag S;
-PacketCache PC;
-
+AuthPacketCache PC;
+AuthQueryCache QC;
#include <boost/archive/binary_iarchive.hpp>
#include <boost/archive/binary_oarchive.hpp>
-#include "packetcache.hh"
+#include "auth-querycache.hh"
#include "utility.hh"
for_each(backends.begin(),backends.end(),del);
}
-// silly Solaris fix
-#undef PC
-
// returns -1 for miss, 0 for negative match, 1 for hit
int UeberBackend::cacheHas(const Question &q, vector<DNSZoneRecord> &rrs)
{
- extern PacketCache PC;
- static AtomicCounter *qcachehit=S.getPointer("query-cache-hit");
- static AtomicCounter *qcachemiss=S.getPointer("query-cache-miss");
+ extern AuthQueryCache QC;
if(!d_cache_ttl && ! d_negcache_ttl) {
- (*qcachemiss)++;
return -1;
}
rrs.clear();
// L<<Logger::Warning<<"looking up: '"<<q.qname+"'|N|"+q.qtype.getName()+"|"+itoa(q.zoneId)<<endl;
- bool ret=PC.getEntry(q.qname, q.qtype, PacketCache::QUERYCACHE, rrs, q.zoneId); // think about lowercasing here
+ bool ret=QC.getEntry(q.qname, q.qtype, rrs, q.zoneId); // think about lowercasing here
if(!ret) {
- (*qcachemiss)++;
return -1;
}
- (*qcachehit)++;
if(rrs.empty()) // negatively cached
return 0;
void UeberBackend::addNegCache(const Question &q)
{
- extern PacketCache PC;
+ extern AuthQueryCache QC;
if(!d_negcache_ttl)
return;
// we should also not be storing negative answers if a pipebackend does scopeMask, but we can't pass a negative scopeMask in an empty set!
- PC.insert(q.qname, q.qtype, PacketCache::QUERYCACHE, vector<DNSZoneRecord>(), d_negcache_ttl, q.zoneId);
+ QC.insert(q.qname, q.qtype, vector<DNSZoneRecord>(), d_negcache_ttl, q.zoneId);
}
void UeberBackend::addCache(const Question &q, const vector<DNSZoneRecord> &rrs)
{
- extern PacketCache PC;
+ extern AuthQueryCache QC;
if(!d_cache_ttl)
return;
return;
}
- PC.insert(q.qname, q.qtype, PacketCache::QUERYCACHE, rrs, store_ttl, q.zoneId);
+ QC.insert(q.qname, q.qtype, rrs, store_ttl, q.zoneId);
}
void UeberBackend::alsoNotifies(const DNSName &domain, set<string> *ips)
DNSSECMode g_dnssecmode{DNSSECMode::ProcessNoValidate};
bool g_dnssecLogBogus;
+extern int getMTaskerTID();
+
#define LOG(x) if(g_dnssecLOG) { L <<Logger::Warning << x; }
class SRRecordOracle : public DNSRecordOracle
struct timeval tv;
gettimeofday(&tv, 0);
SyncRes sr(tv);
- sr.setId(MT->getTid());
+ sr.setId(getMTaskerTID());
#ifdef HAVE_PROTOBUF
- sr.d_initialRequestId = d_ctx.d_initialRequestId;
+ sr.setInitialRequestId(d_ctx.d_initialRequestId);
#endif
vector<DNSRecord> ret;
- sr.d_doDNSSEC=true;
+ sr.setDoDNSSEC(true);
if (qtype == QType::DS || qtype == QType::DNSKEY || qtype == QType::NS)
sr.setSkipCNAMECheck(true);
sr.beginResolve(qname, QType(qtype), 1, ret);
#include "json.hh"
#include "webserver.hh"
#include "logger.hh"
-#include "packetcache.hh"
#include "statbag.hh"
#include "misc.hh"
#include "arguments.hh"
#include <iomanip>
#include "zoneparser-tng.hh"
#include "common_startup.hh"
-
+#include "auth-caches.hh"
using json11::Json;
extern StatBag S;
-extern PacketCache PC;
static void patchZone(HttpRequest* req, HttpResponse* resp);
static void storeChangedPTRs(UeberBackend& B, vector<DNSResourceRecord>& new_ptrs);
}
sd.db->commitTransaction();
- PC.purgeExact(rr.qname);
+ purgeAuthCachesExact(rr.qname);
}
}
}
di.backend->commitTransaction();
- PC.purgeExact(zonename);
+ purgeAuthCachesExact(zonename);
// now the PTRs
storeChangedPTRs(B, new_ptrs);
DNSName canon = apiNameToDNSName(req->getvars["domain"]);
- int count = PC.purgeExact(canon);
+ uint64_t count = purgeAuthCachesExact(canon);
resp->setBody(Json::object {
- { "count", count },
- { "result", "Flushed cache." }
+ { "count", (int) count },
+ { "result", "Flushed cache." }
});
}
that the queries sent from dnsdist were as expected.
"""
_dnsDistPort = 5340
+ _dnsDistListeningAddr = "127.0.0.1"
_testServerPort = 5350
_toResponderQueue = Queue.Queue()
_fromResponderQueue = Queue.Queue()
conf.write(cls._config_template % params)
dnsdistcmd = [os.environ['DNSDISTBIN'], '-C', conffile,
- '-l', '127.0.0.1:%d' % cls._dnsDistPort]
+ '-l', '%s:%d' % (cls._dnsDistListeningAddr, cls._dnsDistPort) ]
for acl in cls._acl:
dnsdistcmd.extend(['--acl', acl])
print(' '.join(dnsdistcmd))
receivedQuery.id = query.id
self.assertEquals(receivedQuery, query)
self.assertEquals(receivedResponse, response)
+
+class TestAdvancedGetLocalPort(DNSDistTest):
+
+ _config_template = """
+ function answerBasedOnLocalPort(dq)
+ local port = dq.localaddr:getPort()
+ return DNSAction.Spoof, "port-was-"..port..".local-port.advanced.tests.powerdns.com."
+ end
+ addLuaAction("local-port.advanced.tests.powerdns.com.", answerBasedOnLocalPort)
+ newServer{address="127.0.0.1:%s"}
+ """
+
+ def testAdvancedGetLocalPort(self):
+ """
+ Advanced: Return CNAME containing the local port
+ """
+ name = 'local-port.advanced.tests.powerdns.com.'
+ query = dns.message.make_query(name, 'A', 'IN')
+ # dnsdist set RA = RD for spoofed responses
+ query.flags &= ~dns.flags.RD
+
+ response = dns.message.make_response(query)
+ rrset = dns.rrset.from_text(name,
+ 60,
+ dns.rdataclass.IN,
+ dns.rdatatype.CNAME,
+ 'port-was-{}.local-port.advanced.tests.powerdns.com.'.format(self._dnsDistPort))
+ response.answer.append(rrset)
+
+ (_, receivedResponse) = self.sendUDPQuery(query, response=None, useQueue=False)
+ self.assertEquals(receivedResponse, response)
+
+ (_, receivedResponse) = self.sendTCPQuery(query, response=None, useQueue=False)
+ self.assertEquals(receivedResponse, response)
+
+class TestAdvancedGetLocalPortOnAnyBind(DNSDistTest):
+
+ _config_template = """
+ function answerBasedOnLocalPort(dq)
+ local port = dq.localaddr:getPort()
+ return DNSAction.Spoof, "port-was-"..port..".local-port-any.advanced.tests.powerdns.com."
+ end
+ addLuaAction("local-port-any.advanced.tests.powerdns.com.", answerBasedOnLocalPort)
+ newServer{address="127.0.0.1:%s"}
+ """
+ _dnsDistListeningAddr = "0.0.0.0"
+
+ def testAdvancedGetLocalPortOnAnyBind(self):
+ """
+ Advanced: Return CNAME containing the local port for an ANY bind
+ """
+ name = 'local-port-any.advanced.tests.powerdns.com.'
+ query = dns.message.make_query(name, 'A', 'IN')
+ # dnsdist set RA = RD for spoofed responses
+ query.flags &= ~dns.flags.RD
+
+ response = dns.message.make_response(query)
+ rrset = dns.rrset.from_text(name,
+ 60,
+ dns.rdataclass.IN,
+ dns.rdatatype.CNAME,
+ 'port-was-{}.local-port-any.advanced.tests.powerdns.com.'.format(self._dnsDistPort))
+ response.answer.append(rrset)
+
+ (_, receivedResponse) = self.sendUDPQuery(query, response=None, useQueue=False)
+ self.assertEquals(receivedResponse, response)
+
+ (_, receivedResponse) = self.sendTCPQuery(query, response=None, useQueue=False)
+ self.assertEquals(receivedResponse, response)
total += self._responsesCounter[key]
self.assertEquals(total, misses)
+
+class TestCachingDontAge(DNSDistTest):
+
+ _config_template = """
+ pc = newPacketCache(100, 86400, 0, 60, 60, true)
+ getPool(""):setCache(pc)
+ newServer{address="127.0.0.1:%s"}
+ """
+ def testCacheDoesntDecreaseTTL(self):
+ """
+ Cache: Cache doesn't decrease TTL with 'don't age' set
+
+ dnsdist is configured to cache entries but without aging the TTL,
+ we are sending one request (cache miss) and verify that the cache
+ hits don't have a decreasing TTL.
+ """
+ ttl = 600
+ misses = 0
+ name = 'cachedoesntdecreasettl.cache-dont-age.tests.powerdns.com.'
+ query = dns.message.make_query(name, 'AAAA', 'IN')
+ response = dns.message.make_response(query)
+ rrset = dns.rrset.from_text(name,
+ ttl,
+ dns.rdataclass.IN,
+ dns.rdatatype.AAAA,
+ '::1')
+ response.answer.append(rrset)
+
+ # first query to fill the cache
+ (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
+ self.assertTrue(receivedQuery)
+ self.assertTrue(receivedResponse)
+ receivedQuery.id = query.id
+ self.assertEquals(query, receivedQuery)
+ self.assertEquals(receivedResponse, response)
+ misses += 1
+
+ # next queries should hit the cache
+ (_, receivedResponse) = self.sendUDPQuery(query, response=None, useQueue=False)
+ self.assertEquals(receivedResponse, response)
+ for an in receivedResponse.answer:
+ self.assertTrue(an.ttl == ttl)
+
+ # now we wait a bit for the TTL to decrease
+ time.sleep(1)
+
+ # next queries should hit the cache
+ (_, receivedResponse) = self.sendUDPQuery(query, response=None, useQueue=False)
+ self.assertEquals(receivedResponse, response)
+ for an in receivedResponse.answer:
+ self.assertTrue(an.ttl == ttl)
+
+ total = 0
+ for key in self._responsesCounter:
+ total += self._responsesCounter[key]
+
+ self.assertEquals(total, misses)
#!/usr/bin/env python
+import base64
import time
import dns
from dnsdisttests import DNSDistTest
_dynBlockBytesPerSecond = 200
_dynBlockPeriod = 2
_dynBlockDuration = 5
- _config_params = ['_dynBlockBytesPerSecond', '_dynBlockPeriod', '_dynBlockDuration', '_testServerPort']
+ _consoleKey = DNSDistTest.generateConsoleKey()
+ _consoleKeyB64 = base64.b64encode(_consoleKey)
+ _config_params = ['_consoleKeyB64', '_consolePort', '_dynBlockBytesPerSecond', '_dynBlockPeriod', '_dynBlockDuration', '_testServerPort']
_config_template = """
+ setKey("%s")
+ controlSocket("127.0.0.1:%s")
function maintenance()
addDynBlocks(exceedRespByterate(%d, %d), "Exceeded response byterate", %d)
end
allowed = 0
sent = 0
+
+ print(time.time())
+
for _ in xrange(self._dynBlockBytesPerSecond * 5 / len(response.to_wire())):
(receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
sent = sent + len(response.to_wire())
# the query has not reached the responder,
# let's clear the response queue
self.clearToResponderQueue()
+ # and stop right there, otherwise we might
+ # wait for so long that the dynblock is gone
+ # by the time we finished
+ break
# we might be already blocked, but we should have been able to send
# at least self._dynBlockBytesPerSecond bytes
+ print(allowed)
+ print(sent)
+ print(time.time())
self.assertGreaterEqual(allowed, self._dynBlockBytesPerSecond)
+ print(self.sendConsoleCommand("showDynBlocks()"))
+ print(self.sendConsoleCommand("grepq(\"\")"))
+ print(time.time())
+
if allowed == sent:
# wait for the maintenance function to run
+ print("Waiting for the maintenance function to run")
time.sleep(2)
+ print(self.sendConsoleCommand("showDynBlocks()"))
+ print(self.sendConsoleCommand("grepq(\"\")"))
+ print(time.time())
+
# we should now be dropped for up to self._dynBlockDuration + self._dynBlockPeriod
(_, receivedResponse) = self.sendUDPQuery(query, response=None, useQueue=False)
self.assertEquals(receivedResponse, None)
+ print(self.sendConsoleCommand("showDynBlocks()"))
+ print(self.sendConsoleCommand("grepq(\"\")"))
+ print(time.time())
+
# wait until we are not blocked anymore
time.sleep(self._dynBlockDuration + self._dynBlockPeriod)
+ print(self.sendConsoleCommand("showDynBlocks()"))
+ print(self.sendConsoleCommand("grepq(\"\")"))
+ print(time.time())
+
# this one should succeed
(receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
receivedQuery.id = query.id
# the query has not reached the responder,
# let's clear the response queue
self.clearToResponderQueue()
+ # and stop right there, otherwise we might
+ # wait for so long that the dynblock is gone
+ # by the time we finished
+ break
# we might be already blocked, but we should have been able to send
# at least self._dynBlockBytesPerSecond bytes
corrupt-packets=0
deferred-cache-inserts=0
deferred-cache-lookup=0
+deferred-packetcache-inserts=0
+deferred-packetcache-lookup=0
dnsupdate-answers=0
dnsupdate-changes=0
dnsupdate-queries=0
key-cache-size=0
meta-cache-size=1
overload-drops=0
-packetcache-size=8
+packetcache-size=4
qsize-q=0
+query-cache-size=4
rd-queries=0
recursing-answers=0
recursing-questions=0
17a1219ca79020c1c67ad173a291d3c8 ../regression-tests/zones/example.com
-b4feb02bd50f7b78cd5c40c7a560019c ../regression-tests/zones/test.com
+fe49d2784b1bcc3b91ddd5619f0b6cc1 ../regression-tests/zones/test.com
005b3381db2a7dc70b690484f6ab7770 ../regression-tests/zones/test.dyndns
0861783f9f83acd9044278cda2528ee2 ../regression-tests/zones/wtest.com
42b442de632686e94bde75acf66cf524 ../regression-tests/zones/nztest.com
b1f775045fa2cf0a3b91aa834af06e49 ../regression-tests/zones/stest.com
a98864b315f16bcf49ce577426063c42 ../regression-tests/zones/cdnskey-cds-test.com
9aeed2c26d0c3ba3baf22dfa9568c451 ../regression-tests/zones/2.0.192.in-addr.arpa
-da60133e145e3a8ff6c86003607a5d96 ../modules/tinydnsbackend/data.cdb
+e30afafb846e773a5bff70dcfdb52088 ../modules/tinydnsbackend/data.cdb
$RUNWRAPPER $PDNS --daemon=no --local-port=$port --config-dir=. \
--config-name=$backend --socket-dir=./ --no-shuffle \
--dnsupdate=yes --resolver=8.8.8.8 --outgoing-axfr-expand-alias=yes \
+ --expand-alias=yes \
--cache-ttl=$cachettl --dname-processing \
--disable-axfr-rectify=yes $lua_prequery &
RFC2136 describes that every update message should result in the SOA-serial to be changed.
This test checks if that happens.
-
A test for RFC2136 which checks section 3.4.2.2 - Replacing a record.
-
-Use CNAME to validate '@' expansion in RR contents.
\ No newline at end of file
+Use CNAME to validate '@' expansion in RR contents.
counter.test.com. 86400 IN NSEC d.test.com. A RRSIG NSEC
d.test.com. 86400 IN NSEC _double._tcp.dc.test.com. DNAME RRSIG NSEC
enum.test.com. 86400 IN NSEC hightxt.test.com. NAPTR RRSIG NSEC
-hightxt.test.com. 86400 IN NSEC ns1.test.com. TXT RRSIG NSEC SPF
+hightxt.test.com. 86400 IN NSEC interrupted-rrset.test.com. TXT RRSIG NSEC SPF
+interrupted-rrset.test.com. 86400 IN NSEC ns1.test.com. A TXT RRSIG NSEC
ns1.test.com. 86400 IN NSEC ns2.test.com. A RRSIG NSEC
ns2.test.com. 86400 IN NSEC server1.test.com. A RRSIG NSEC
server1.test.com. 86400 IN NSEC *.test.test.com. A RP RRSIG NSEC
b022o9dksaj737fh77e7kqqtj3om56ki.test.com. 86400 IN NSEC3 1 0 1 abcd DAFC69CV5N2TFCF6OVBVTV94DRGMQJO5
dafc69cv5n2tfcf6ovbvtv94drgmqjo5.test.com. 86400 IN NSEC3 1 0 1 abcd DE592K86U3HEVDJ57JPBT7J5KV7DOO78 TXT RRSIG
de592k86u3hevdj57jpbt7j5kv7doo78.test.com. 86400 IN NSEC3 1 0 1 abcd EBAN51BJGUGORB20UNP5PEEC7S5D2EKA NS
-eban51bjgugorb20unp5peec7s5d2eka.test.com. 86400 IN NSEC3 1 0 1 abcd H5855RVON2AASM8QV1NK49I1B2MKBEJP SRV RRSIG
+eban51bjgugorb20unp5peec7s5d2eka.test.com. 86400 IN NSEC3 1 0 1 abcd ENG6HBK77VJMQFVG6S04HAJOA2201LII SRV RRSIG
+eng6hbk77vjmqfvg6s04hajoa2201lii.test.com. 86400 IN NSEC3 1 0 1 abcd H5855RVON2AASM8QV1NK49I1B2MKBEJP A TXT RRSIG
h5855rvon2aasm8qv1nk49i1b2mkbejp.test.com. 86400 IN NSEC3 1 0 1 abcd IAI9HIN25MEH689R5V5GTIFK8OM5DI0E A RRSIG
iai9hin25meh689r5v5gtifk8om5di0e.test.com. 86400 IN NSEC3 1 0 1 abcd IGF4M7OTECACH14P0A6INGI7DBUAS5B2 A RRSIG
igf4m7otecach14p0a6ingi7dbuas5b2.test.com. 86400 IN NSEC3 1 0 1 abcd N5RSKFSBG0UK5SSPJ595R6546HKK5VK1 A RP RRSIG
aovp95mr44hqefrqus6nomsd944bm3vb.test.com. 86400 IN NSEC3 1 1 1 abcd B022O9DKSAJ737FH77E7KQQTJ3OM56KI A RRSIG
b022o9dksaj737fh77e7kqqtj3om56ki.test.com. 86400 IN NSEC3 1 1 1 abcd DAFC69CV5N2TFCF6OVBVTV94DRGMQJO5
dafc69cv5n2tfcf6ovbvtv94drgmqjo5.test.com. 86400 IN NSEC3 1 1 1 abcd EBAN51BJGUGORB20UNP5PEEC7S5D2EKA TXT RRSIG
-eban51bjgugorb20unp5peec7s5d2eka.test.com. 86400 IN NSEC3 1 1 1 abcd H5855RVON2AASM8QV1NK49I1B2MKBEJP SRV RRSIG
+eban51bjgugorb20unp5peec7s5d2eka.test.com. 86400 IN NSEC3 1 1 1 abcd ENG6HBK77VJMQFVG6S04HAJOA2201LII SRV RRSIG
+eng6hbk77vjmqfvg6s04hajoa2201lii.test.com. 86400 IN NSEC3 1 1 1 abcd H5855RVON2AASM8QV1NK49I1B2MKBEJP A TXT RRSIG
h5855rvon2aasm8qv1nk49i1b2mkbejp.test.com. 86400 IN NSEC3 1 1 1 abcd IAI9HIN25MEH689R5V5GTIFK8OM5DI0E A RRSIG
iai9hin25meh689r5v5gtifk8om5di0e.test.com. 86400 IN NSEC3 1 1 1 abcd IGF4M7OTECACH14P0A6INGI7DBUAS5B2 A RRSIG
igf4m7otecach14p0a6ingi7dbuas5b2.test.com. 86400 IN NSEC3 1 1 1 abcd N5RSKFSBG0UK5SSPJ595R6546HKK5VK1 A RP RRSIG
This test checks a secure delegations.
-
hightxt IN SPF "v=spf1 mx ip4:78.46.192.210 –all"
d IN DNAME d2.test2.com.
urc65226 IN TYPE65226 \# 3 414243
+interrupted-rrset IN A 1.1.1.1
+interrupted-rrset IN TXT "check AXFR signpipe"
+interrupted-rrset IN A 2.2.2.2