command: mkdir -p $HOME/.ssh && ssh-keyscan $(echo ${CIRCLE_REPOSITORY_URL} | sed 's,.*@\([^:]*\):.*,\1,') > $HOME/.ssh/known_hosts
- run:
name: Clone the repo
- command: git clone --depth 1 --branch $CIRCLE_BRANCH $CIRCLE_REPOSITORY_URL ~/project
+ command: |
+ if [ -n "$CIRCLE_PR_NUMBER" ]
+ then
+ echo === Checking out PR "$CIRCLE_PR_NUMBER" from "$CIRCLE_REPOSITORY_URL"
+ git clone --depth 1 $CIRCLE_REPOSITORY_URL ~/project
+ cd ~/project
+ git fetch --depth 1 origin +refs/pull/${CIRCLE_PR_NUMBER}/merge
+ git checkout -qf FETCH_HEAD
+ else
+ echo === Checking out branch "${CIRCLE_BRANCH}" from "$CIRCLE_REPOSITORY_URL"
+ git clone --depth 1 --branch $CIRCLE_BRANCH $CIRCLE_REPOSITORY_URL ~/project
+ fi
+ git show -s
+
+ install-coverity-tools:
+ description: Install the coverity tools to /usr/local
+ steps:
+ - run:
+ name: Install Coverity tools
+ command: curl -s https://scan.coverity.com/download/linux64 --data "token=${COVERITY_TOKEN}&project=${COVERITY_PROJECT}" | gunzip | tar xvf /dev/stdin --strip-components=2 --no-same-owner -C /usr/local
auth-regress-setup:
description: Prepare the environment for auth regression tests
autoconf \
automake \
bison \
+ bzip2 \
+ curl \
default-libmysqlclient-dev \
flex \
g++ \
CXXFLAGS="-O1 -Werror=vla" \
./configure \
--enable-unit-tests \
+ --enable-nod \
--prefix=/opt/pdns-recursor \
--with-libsodium \
--with-lua=luajit \
PDNSRECURSOR="/opt/pdns-recursor/sbin/pdns_recursor" \
./runtests recursor
+ coverity-auth:
+ docker:
+ - image: debian:stretch
+ steps:
+ - install-auth-dev-deps
+ - install-coverity-tools
+ - checkout-shallow
+ - run:
+ name: autoconf
+ command: BUILDER_VERSION=0.0.0-git1 autoreconf -vfi
+ - run:
+ name: configure
+ command: |
+ CFLAGS="-O1 -Werror=vla" \
+ CXXFLAGS="-O1 -Werror=vla" \
+ ./configure \
+ --disable-lua-records \
+ --with-modules='bind lmdb ldap gmysql gsqlite3 gpgsql godbc mydns random tinydns' \
+ --enable-tools \
+ --with-lmdb=/usr \
+ --with-libsodium \
+ --prefix=/opt/pdns-auth
+ - run:
+ name: build
+ command: /usr/local/bin/cov-build --dir cov-int make -j2 -k
+ - run:
+ name: Create Coverity tarball
+ command: tar caf auth.tar.bz2 cov-int
+ - run:
+ name: Upload tarball to coverity
+ command: |
+ curl --form token=${COVERITY_TOKEN} \
+ --form email="${COVERITY_EMAIL}" \
+ --form file=@auth.tar.bz2 \
+ --form version="$(./builder-support/gen-version)" \
+ --form description="master build" \
+ https://scan.coverity.com/builds?project=${COVERITY_PROJECT}
+
+ coverity-dnsdist:
+ docker:
+ - image: debian:stretch
+ steps:
+ - run:
+ name: Install dependencies
+ command: |
+ apt-get update && apt-get -qq --no-install-recommends install \
+ autoconf \
+ automake \
+ bison \
+ bzip2 \
+ ca-certificates \
+ curl \
+ flex \
+ g++ \
+ git \
+ libboost-all-dev \
+ libcap-dev \
+ libedit-dev \
+ libfstrm-dev \
+ libluajit-5.1-dev \
+ libprotobuf-dev \
+ libre2-dev \
+ libsnmp-dev \
+ libsodium-dev \
+ libssl-dev \
+ libsystemd-dev \
+ libtool \
+ make \
+ pkg-config \
+ protobuf-compiler \
+ ragel \
+ virtualenv
+ - install-coverity-tools
+ - checkout-shallow
+ - run:
+ name: autoconf
+ command: BUILDER_VERSION=0.0.0-git1 autoreconf -vfi
+ working_directory: ~/project/pdns/dnsdistdist
+ - run:
+ name: configure
+ command: |
+ CFLAGS="-O1 -Werror=vla" \
+ CXXFLAGS="-O1 -Werror=vla" \
+ ./configure \
+ --disable-systemd \
+ --disable-unit-tests \
+ --enable-dnstap \
+ --enable-dnscrypt \
+ --enable-dns-over-tls \
+ --prefix=/opt/dnsdist \
+ --with-libsodium \
+ --with-lua=luajit \
+ --with-libcap \
+ --with-protobuf=yes \
+ --with-re2
+ working_directory: ~/project/pdns/dnsdistdist
+ - run:
+ name: build
+ command: /usr/local/bin/cov-build --dir cov-int make -j2 -k
+ working_directory: ~/project/pdns/dnsdistdist
+ - run:
+ name: Create Coverity tarball
+ command: tar caf dnsdist.tar.bz2 cov-int
+ working_directory: ~/project/pdns/dnsdistdist
+ - run:
+ name: Upload tarball to coverity
+ command: |
+ curl --form token=${COVERITY_TOKEN} \
+ --form email="${COVERITY_EMAIL}" \
+ --form file=@pdns/dnsdistdist/dnsdist.tar.bz2 \
+ --form version="$(./builder-support/gen-version)" \
+ --form description="master build" \
+ https://scan.coverity.com/builds?project=${COVERITY_PROJECT}
+
+ coverity-recursor:
+ docker:
+ - image: debian:stretch
+ steps:
+ - run:
+ name: Install dependencies
+ command: |
+ apt-get update && apt-get -qq --no-install-recommends install \
+ autoconf \
+ automake \
+ ca-certificates \
+ curl \
+ bison \
+ bzip2 \
+ flex \
+ g++ \
+ git \
+ libboost-all-dev \
+ libcap-dev \
+ libluajit-5.1-dev \
+ libprotobuf-dev \
+ libsodium-dev \
+ libssl-dev \
+ libsystemd-dev \
+ libtool \
+ make \
+ pkg-config \
+ protobuf-compiler \
+ ragel \
+ virtualenv
+ - install-coverity-tools
+ - checkout-shallow
+ - run:
+ name: autoconf
+ command: BUILDER_VERSION=0.0.0-git1 autoreconf -vfi
+ working_directory: ~/project/pdns/recursordist
+ - run:
+ name: configure
+ command: |
+ CFLAGS="-O1 -Werror=vla" \
+ CXXFLAGS="-O1 -Werror=vla" \
+ ./configure \
+ --disable-systemd \
+ --disable-unit-tests \
+ --prefix=/opt/pdns-recursor \
+ --with-libsodium \
+ --with-lua=luajit \
+ --with-libcap \
+ --with-protobuf=yes \
+ --without-net-snmp
+ working_directory: ~/project/pdns/recursordist
+ - run:
+ name: build
+ command: /usr/local/bin/cov-build --dir cov-int make -j2 -k
+ working_directory: ~/project/pdns/recursordist
+ - run:
+ name: Create Coverity tarball
+ command: tar caf recursor.tar.bz2 cov-int
+ working_directory: ~/project/pdns/recursordist
+ - run:
+ name: Upload tarball to coverity
+ command: |
+ curl --form token=${COVERITY_TOKEN} \
+ --form email="${COVERITY_EMAIL}" \
+ --form file=@pdns/recursordist/recursor.tar.bz2 \
+ --form version="$(./builder-support/gen-version)" \
+ --form description="master build" \
+ https://scan.coverity.com/builds?project=${COVERITY_PROJECT}
+
workflows:
version: 2
+ coverity:
+ triggers:
+ - schedule:
+ cron: "0 0 * * *"
+ filters:
+ branches:
+ only: master
+ jobs:
+ - coverity-auth:
+ context: auth-coverity
+ - coverity-dnsdist:
+ context: dnsdist-coverity
+ - coverity-recursor:
+ context: recursor-coverity
+
build-and-test-all:
jobs:
- build-auth
*.gcno
*.gcov
built_pkgs
+*-wal
+*-shm
+__pycache__
\ No newline at end of file
-Subproject commit 4f5ab935098ebacd6b55e89b405bd69e80ab2baf
+Subproject commit 6176c5f68354ca82814ef20a4c87327785157ff3
FROM alpine:3.6 as sdist
ARG BUILDER_CACHE_BUSTER=
+@IF [ -z "$M_all$M_authoritative$M_recursor$M_dnsdist"]
+RUN echo "no valid modules specified!" ; exit 1
+@ENDIF
+
@IF [ ! -z "$M_authoritative$M_all" ]
COPY --from=pdns-authoritative /sdist/ /sdist/
@ENDIF
git status | grep -q clean || DIRTY='.dirty'
# Special environment variable to signal that we are building a release, as this
-# has condequenses for the version number.
+# has consequenses for the version number.
if [ "${IS_RELEASE}" = "YES" ]; then
TAG="$(git describe --tags --exact-match 2> /dev/null | cut -d- -f 2-)"
if [ -n "${TAG}" ]; then
# Generate the version number based on the branch
#
if [ ! -z "$(git rev-parse --abbrev-ref HEAD 2> /dev/null)" ]; then
- GIT_VERSION=""
- if $(git rev-parse --abbrev-ref HEAD | grep -q 'rel/'); then
- REL_TYPE="$(git rev-parse --abbrev-ref HEAD | cut -d/ -f 2 | cut -d- -f 1)"
- GIT_VERSION="$(git describe --match=${REL_TYPE}-* --tags | cut -d- -f2-)"
+ if [ -n "${BUILDER_MODULES}" ]; then
+ match=${BUILDER_MODULES}
+ [ $match = "authoritative" ] && match='auth'
+ [ $match = "recursor" ] && match='rec'
+ GIT_VERSION="$(git describe --match=${match}-* --tags | cut -d- -f2-)"
+ if [ $(echo ${GIT_VERSION} | awk -F"-" '{print NF-1}') = '3' ]; then
+ # A prerelease happened before
+ LAST_TAG="$(echo ${GIT_VERSION} | cut -d- -f1-2)"
+ COMMITS_SINCE_TAG="$(echo ${GIT_VERSION} | cut -d- -f3)"
+ GIT_HASH="$(echo ${GIT_VERSION} | cut -d- -f4)"
+ else
+ LAST_TAG="$(echo ${GIT_VERSION} | cut -d- -f1)"
+ COMMITS_SINCE_TAG="$(echo ${GIT_VERSION} | cut -d- -f2)"
+ GIT_HASH="$(echo ${GIT_VERSION} | cut -d- -f3)"
+ fi
fi
- LAST_TAG="$(echo ${GIT_VERSION} | cut -d- -f1)"
- COMMITS_SINCE_TAG="$(echo ${GIT_VERSION} | cut -d- -f2)"
- GIT_HASH="$(echo ${GIT_VERSION} | cut -d- -f3)"
-
if [ -z "${GIT_VERSION}" ]; then
+ # BUILDER_SUPPORT has more than one product listed, fall back to the 0.0.0 logic
+
# We used 0.0.XXXXgHASH for master in the previous incarnation of our build pipeline.
# This now becomes 0.0.XXXX.0.gHASH, as 0.0.0.XXXX.gHASH (which is more correct)
# would break upgrades for those running master
LAST_TAG=0.0
COMMITS_SINCE_TAG="$(git rev-list --count 12c868770afc20b6cc0da439d881105151d557dd..HEAD 2> /dev/null).0"
[ "${COMMITS_SINCE_TAG}" = ".0" ] && COMMITS_SINCE_TAG=0.0
- GIT_HASH="$(git rev-parse HEAD | cut -c1-10 2> /dev/null)"
+ GIT_HASH="g$(git rev-parse HEAD | cut -c1-10 2> /dev/null)"
fi
BRANCH=".$(git rev-parse --abbrev-ref HEAD | perl -p -e 's/[^[:alnum:]]//g;')"
- [ "${BRANCH}" = ".master" ] && BRANCH=''
TAG="$(git describe --tags --exact-match 2> /dev/null | cut -d- -f 2-)"
if [ -n "${TAG}" ]; then # We're exactly on a tag
fi
fi
- VERSION="${LAST_TAG}.${COMMITS_SINCE_TAG}${BRANCH}.g${GIT_HASH}${DIRTY}"
+ VERSION="${LAST_TAG}.${COMMITS_SINCE_TAG}${BRANCH}.${GIT_HASH}${DIRTY}"
fi
printf $VERSION
PDNS_ENABLE_REMOTEBACKEND_ZEROMQ
-AC_MSG_CHECKING([whether we will be building and installing the extra tools])
-AC_ARG_ENABLE([tools],
- [AS_HELP_STRING([--enable-tools], [if we should build and install the tools @<:@default=no@:>@])],
- [enable_tools=$enableval],
- [enable_tools=no]
-)
-
-AC_MSG_RESULT([$enable_tools])
-AM_CONDITIONAL([TOOLS], [test "x$enable_tools" != "xno"])
+PDNS_ENABLE_TOOLS
PDNS_ENABLE_IXFRDIST
PDNS_WITH_PROTOBUF
it will (silently) serve it without DNSSEC. This in turn results in
serving the domain as bogus.
+.. _setting-bind-dnssec-db-journal-mode:
+
+``bind-dnssec-db-journal-mode``
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+SQLite3 journal mode to set. The default is WAL. Set to empty to leave the journal mode alone.
+
.. _setting-bind-hybrid:
``bind-hybrid``
point this is going to harm your database, for example when an incoming
zone transfer fails.
+.. warning::
+ While it is possible to run the Generic MySQL backend on top of MySQL
+ views, we have received several reports of this causing performance
+ problems and memory leaks. Please know that when reporting problems when
+ running PowerDNS on top of a modified schema, our open source support
+ offering requires you to reproduce your problem on an unmodified schema without
+ views.
+
The default schema is included at the bottom of this page.
:ref:`migration-zone2sql` with the ``--gmysql`` flag also
assumes this layout is in place. For full migration notes, please see
.. _setting-gmysql-thread-cleanup:
``gmysql-thread-cleanup``
-^^^^^^^^^^^^^^^^^^^^^^^^
+^^^^^^^^^^^^^^^^^^^^^^^^^
.. versionadded:: 4.1.8
Older versions (such as those shipped on RHEL 7) of the MySQL/MariaDB client libraries leak memory unless applications explicitly report the end of each thread to the library. Enabling ``gmysql-thread-cleanup`` tells PowerDNS to call ``mysql_thread_end()`` whenever a thread ends.
Path to the SQLite3 database.
+.. _setting-gsqlite3-pragma-journal-mode:
+
+``gsqlite3-pragma-journal-mode``
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+SQLite3 journal mode to set. The default is WAL. Set to empty to leave the journal mode alone.
+
.. _setting-gsqlite3-pragma-synchronous:
``gsqlite3-pragma-synchronous``
``geoip-zones-file``
~~~~~~~~~~~~~~~~~~~~
-Specifies the full path of the zone configuration file to use. The file is re-opened with a ``pdns_control reload''.
+Specifies the full path of the zone configuration file to use. The file is re-opened with a ``pdns_control reload``.
.. _setting-geoip-dnssec-keydir:
+------------------------------------------------+--------+--------+-------+--------------+-------------+---------------------------------+--------------+
| :doc:`LDAP <ldap>` | Yes | No | No | No | No | No | ``ldap`` |
+------------------------------------------------+--------+--------+-------+--------------+-------------+---------------------------------+--------------+
+| :doc:`LMDB <lmdb>` | Yes | Yes | Yes | No | Yes | Yes | ``lmdb`` |
++------------------------------------------------+--------+--------+-------+--------------+-------------+---------------------------------+--------------+
| :doc:`Lua <lua>` | Yes | Yes | No | No | Yes | Yes | ``lua`` |
+------------------------------------------------+--------+--------+-------+--------------+-------------+---------------------------------+--------------+
| :doc:`Lua2 <lua2>` | Yes | Yes | No | No | Yes | Yes | ``lua2`` |
generic-sqlite3
geoip
ldap
+ lmdb
lua
lua2
mydns
--- /dev/null
+LMDB backend
+============
+
+* Native: Yes
+* Master: Yes
+* Slave: Yes
+* Superslave: No
+* Case: All lower
+* DNSSEC: Yes
+* Disabled data: No
+* Comments: No
+* Module name: lmdb
+* Launch name: ``lmdb``
+
+Enabling the backend
+--------------------
+
+When building PowerDNS yourself, append ``lmdb`` to ``--with-modules`` or ``--with-dynmodules``. It is expected that most pre-built packages contain this backend or be separately installable.
+
+
+Settings
+--------
+
+.. _setting-lmdb-filename:
+
+``lmdb-filename``
+^^^^^^^^^^^^^^^^^
+
+Path to the LMDB file (e.g. */var/spool/powerdns/pdns.lmdb*)
+
+.. warning::
+ On systemd systems,
+ When running PowerDNS via the provided systemd service file, `ProtectSystem <http://www.freedesktop.org/software/systemd/man/systemd.exec.html#ProtectSystem=>`_ is set to ``full``, this means PowerDNS is unable to write to e.g. ``/etc`` and ``/home``, possibly being unable to write to the LMDB database.
+
+.. _setting-lmdb-shards:
+
+``lmdb-shards``
+^^^^^^^^^^^^^^^^^
+
+Records database will be split into this number of shards e.g. lmdb-shards=64
+Default is 2 on 32 bits systems, and 64 on 64 bits systems.
+
+.. _setting-lmdb-sync-mode:
+
+``lmdb-sync-mode``
+^^^^^^^^^^^^^^^^^^
+
+Synchronisation mode: sync, nosync, nometasync, mapasync
+Default: mapasync
+
+* ``sync``: LMDB synchronous mode. Safest option, but also slightly slower. Can also be enabled with ``lmdb-sync-mode=``
+* ``nosync``: don't flush systems buffers to disk when committing a transation.
+ This means a system crash can corrupt the database or lose the last transactions if buffers are not yet flushed to disk.
+* ``nometasync``: flush system buffers to disk only once per transaction, omit the metadata flush. This maintains database integrity, but can potentially lose the last committed transaction if the operating system crashes.
+* ``mapasync``: (default). Use asynchronous flushes to disk. As with nosync, a system crash can then corrupt the database or lose the last transactions.
+
+
+LMDB Structure
+--------------
+
+PowerDNS will create the database structure, no need to manually create the database schema.
+Also, it is not possible to directly query the LMDB DB, so recommendation is to use either the API, or pdnsutil.
:property string content: The content of this record
:property bool disabled: Whether or not this record is disabled
:property bool set-ptr: If set to true, the server will find the matching reverse zone and create a PTR there. Existing PTR records are replaced. If no matching reverse :json:object:`Zone`, an error is thrown. Only valid in client bodies, only valid for A and AAAA types. Not returned by the server.
+ This feature (set-ptr) is deprecated and will be removed in 4.3.0.
Comment
- RSASHA1 (algorithm 5, algorithm 7)
- RSASHA256 (algorithm 8)
- RSASHA512 (algorithm 10)
-- ECC-GOST (algorithm 12)
- ECDSA (algorithm 13 and 14)
- ed25519 (algorithm 15)
- ed448 (algorithm 16)
- SHA-1 (algorithm 1)
- SHA-256 (algorithm 2)
-- GOST R 34.11-94 (algorithm 3)
- SHA-384 (algorithm 4)
This corresponds to:
- :rfc:`4509`: Use of SHA-256 in DNSSEC Delegation Signer (DS) Resource Records (RRs)
- :rfc:`5155`: DNS Security (DNSSEC) Hashed Authenticated Denial of Existence
- :rfc:`5702`: Use of SHA-2 Algorithms with RSA in DNSKEY and RRSIG Resource Records for DNSSEC
-- :rfc:`5933`: Use of GOST Signature Algorithms in DNSKEY and RRSIG Resource Records for DNSSEC
- :rfc:`6605`: Elliptic Curve Digital Signature Algorithm (DSA) for DNSSEC
- :rfc:`8080`: Edwards-Curve Digital Security Algorithm (EdDSA) for DNSSEC
description: 'Whether or not this record is disabled'
set-ptr:
type: boolean
- description: 'If set to true, the server will find the matching reverse zone and create a PTR there. Existing PTR records are replaced. If no matching reverse Zone, an error is thrown. Only valid in client bodies, only valid for A and AAAA types. Not returned by the server.'
+ description: 'If set to true, the server will find the matching reverse zone and create a PTR there. Existing PTR records are replaced. If no matching reverse Zone, an error is thrown. Only valid in client bodies, only valid for A and AAAA types. Not returned by the server. This feature is deprecated and will be removed in 4.3.0.'
Comment:
title: Comment
Synopsis
--------
-:program:`sdig` *IPADDRESS* *PORT* *QNAME* *QTYPE* [*OPTION*]
+:program:`sdig` *IP-ADDRESS-OR-DOH-URL * *PORT* *QNAME* *QTYPE* [*OPTION*]
Description
-----------
-:program:`sdig` sends a DNS query to *IPADDRESS* on port *PORT* and displays
+:program:`sdig` sends a DNS query to *IP-ADDRESS-OR-DOH-URL* on port *PORT* and displays
the answer in a formatted way.
+If the address starts with an ``h``, it is assumed to be a DoH endpoint, and *PORT* is ignored.
Options
-------
-These options can be added to the commandline in any order. dnssec : Set
-the DO bit to request DNSSEC information.
+These options can be added to the commandline in any order.
+dnssec
+ Set the DO bit to request DNSSEC information.
+ednssubnet *SUBNET*
+ Send *SUBNET* in the edns-client-subnet option. If this option is
+ not set, no edns-client-subnet option is set in the query.
hidesoadetails
Don't show the SOA serial in the response.
+hidettl
+ Replace TTLs with `[ttl]` in the response.
recurse
Set the RD bit in the question.
showflags
- Show the NSEC3 flags in the response.
+ Show the NSEC3 flags in the response (they are hidden by default).
tcp
Use TCP instead of UDP to send the query.
-ednssubnet *SUBNET*
- Send *SUBNET* in the edns-client-subnet option. If this option is
- not set, no edns-client-subnet option is set in the query.
+xpf *XPFCODE* *XPFVERSION* *XPFPROTO* *XPFSRC* *XPFSRC*
+ Send an *XPF* additional with these parameters.
\ No newline at end of file
.. versionchanged:: 4.2.0
Supermaster support needs to be explicitly enabled with the
- :ref:`setting-supermaster` setting.
+ :ref:`setting-superslave` setting.
PowerDNS can recognize so called 'supermasters'. A supermaster is a host
which is master for domains and for which we are to be a slave. When a
-@ 86400 IN SOA pdns-public-ns1.powerdns.com. pieter\.lexis.powerdns.com. 2019050701 10800 3600 604800 10800
+@ 86400 IN SOA pdns-public-ns1.powerdns.com. pieter\.lexis.powerdns.com. 2019060601 10800 3600 604800 10800
@ 3600 IN NS pdns-public-ns1.powerdns.com.
@ 3600 IN NS pdns-public-ns2.powerdns.com.
; Auth
recursor-4.1.10.security-status 60 IN TXT "1 OK"
recursor-4.1.11.security-status 60 IN TXT "1 OK"
recursor-4.1.12.security-status 60 IN TXT "1 OK"
+recursor-4.1.13.security-status 60 IN TXT "1 OK"
recursor-4.2.0-alpha1.security-status 60 IN TXT "1 OK"
recursor-4.2.0-beta1.security-status 60 IN TXT "1 OK"
+recursor-4.2.0-rc1.security-status 60 IN TXT "1 OK"
; Recursor Debian
recursor-3.6.2-2.debian.security-status 60 IN TXT "3 Upgrade now, see https://doc.powerdns.com/3/security/powerdns-advisory-2015-01/ and https://doc.powerdns.com/3/security/powerdns-advisory-2016-02/"
dnsdist-1.3.3.security-status 60 IN TXT "1 OK"
dnsdist-1.4.0-alpha1.security-status 60 IN TXT "1 OK"
dnsdist-1.4.0-alpha2.security-status 60 IN TXT "1 OK"
+dnsdist-1.4.0-beta1.security-status 60 IN TXT "1 OK"
- Boolean
- Default: no
+.. versionchanged:: 4.2.0
+ This setting has been removed
+
Do not listen to TCP queries. Breaks RFC compliance.
.. _setting-distributor-threads:
``udp-truncation-threshold``
----------------------------
+.. versionchanged:: 4.2.0
+ Before 4.2.0, the default was 1680
- Integer
- Default: 1232
--------------
- Superslave operation is no longer enabled by default, use :ref:`setting-superslave` to enable. This setting was called ``supermaster`` in some 4.2.0 prereleases.
+- The gsqlite3 backend, and the DNSSEC database for the BIND backend, have a new journal-mode setting. This setting defaults to `WAL <https://www.sqlite.org/wal.html>`_; older versions of PowerDNS did not set the journal mode, which means they used the SQLite default of DELETE.
4.1.0 to 4.1.1
--------------
# along with this program. If not, see <http://www.gnu.org/licenses/>.
m4_define([_BOOST_SERIAL], [m4_translit([
-# serial 27
+# serial 28
], [#
], [])])
# * This library was introduced in Boost 1.51.0
# * The signatures of make_fcontext() and jump_fcontext were changed in 1.56.0
# * A dependency on boost_thread appears in 1.57.0
+# * The implementation details were moved to boost::context::detail in 1.61.0
BOOST_DEFUN([Context],
[boost_context_save_LIBS=$LIBS
boost_context_save_LDFLAGS=$LDFLAGS
LIBS="$LIBS $BOOST_THREAD_LIBS"
LDFLAGS="$LDFLAGS $BOOST_THREAD_LDFLAGS"
fi
+
+if test $boost_major_version -ge 161; then
+BOOST_FIND_LIB([context], [$1],
+ [boost/context/continuation.hpp], [[
+namespace ctx=boost::context;
+int a;
+ctx::continuation source=ctx::callcc(
+ [&a](ctx::continuation && sink){
+ a=0;
+ int b=1;
+ for(;;){
+ sink=sink.resume();
+ int next=a+b;
+ a=b;
+ b=next;
+ }
+ return std::move(sink);
+ });
+for (int j=0;j<10;++j) {
+ source=source.resume();
+}
+return a == 34;
+]])
+
+else
+
BOOST_FIND_LIB([context], [$1],
[boost/context/fcontext.hpp],[[
}
#endif
])
+
+fi
+
LIBS=$boost_context_save_LIBS
LDFLAGS=$boost_context_save_LDFLAGS
])# BOOST_CONTEXT
# I'm not sure about my test for `il' (be careful: Intel's ICC pre-defines
# the same defines as GCC's).
for i in \
+ _BOOST_mingw_test(8, 3) \
+ _BOOST_gcc_test(8, 3) \
_BOOST_mingw_test(8, 2) \
_BOOST_gcc_test(8, 2) \
_BOOST_mingw_test(8, 1) \
--- /dev/null
+AC_DEFUN([PDNS_CHECK_LIBCURL], [
+ AS_IF([test -n "$HAVE_LIBCURL"], [ : ], [
+ LIBCURL_CHECK_CONFIG("yes", "7.21.3", [ HAVE_LIBCURL=y ], [ HAVE_LIBCURL=n ])
+ ])
+ AM_CONDITIONAL([HAVE_LIBCURL], [test "$HAVE_LIBCURL" = "y"])
+])
AM_CONDITIONAL([HAVE_LINUX], [test "x$have_linux" = "xyes"])
AM_CONDITIONAL([HAVE_SOLARIS], [test "x$have_solaris" = "xyes"])
- case "$host" in
- mips* | powerpc-* )
- AC_MSG_CHECKING([whether the linker accepts -latomic])
- LDFLAGS="-latomic $LDFLAGS"
- AC_LINK_IFELSE([m4_default([],[AC_LANG_PROGRAM()])],
- [AC_MSG_RESULT([yes])],
- [AC_MSG_ERROR([Unable to link against libatomic, cannot continue])]
- )
- ;;
- esac
+ AC_MSG_CHECKING([whether -latomic is needed for __atomic builtins])
+ AC_LINK_IFELSE(
+ [AC_LANG_PROGRAM([[#include <stdint.h>]],
+ [[uint64_t val = 0; __atomic_add_fetch(&val, 1, __ATOMIC_RELAXED);]]
+ )],
+ [AC_MSG_RESULT([no])],
+ [LIBS="$LIBS -latomic"
+ AC_LINK_IFELSE(
+ [AC_LANG_PROGRAM([[#include <stdint.h>]],
+ [[uint64_t val = 0; __atomic_add_fetch(&val, 1, __ATOMIC_RELAXED);]]
+ )],
+ [AC_MSG_RESULT([yes])],
+ [AC_MSG_FAILURE([libatomic needed, but linking with -latomic failed, cannot continue])]
+ )]
+ )
AC_SUBST(THREADFLAGS)
AC_SUBST([DYNLINKFLAGS], [-export-dynamic])
--- /dev/null
+AC_DEFUN([PDNS_ENABLE_TOOLS], [
+ AC_REQUIRE([PDNS_CHECK_LIBCURL]) dnl We only care about the #define HAVE_LIBCURL and can build tools without DOH support.
+ AC_MSG_CHECKING([whether we will be building and installing the extra tools])
+ AC_ARG_ENABLE([tools],
+ [AS_HELP_STRING([--enable-tools], [if we should build and install the tools @<:@default=no@:>@])],
+ [enable_tools=$enableval],
+ [enable_tools=no]
+ )
+ AC_MSG_RESULT([$enable_tools])
+
+ AM_CONDITIONAL([TOOLS], [test "x$enable_tools" != "xno"])
+])
AC_DEFUN([PDNS_WITH_LUA_RECORDS], [
+ AC_REQUIRE([PDNS_CHECK_LIBCURL])
AC_MSG_CHECKING([whether we will enable LUA records])
AC_ARG_ENABLE([lua-records],
AS_IF([test "x$LUAPC" = "x"],
AC_MSG_ERROR([LUA records need LUA. You can disable this feature with the --disable-lua-records switch or configure a proper LUA installation.])
)
- LIBCURL_CHECK_CONFIG("yes", "7.21.3", [ : ], [
+ AS_IF([test "$HAVE_LIBCURL" != "y"], [
AC_MSG_ERROR([libcurl minimum version requirement not met. This is required for LUA records. You can disable it with the --disable-lua-records switch or use --with-libcurl to select another curl installation.])
])
+
AC_DEFINE([HAVE_LUA_RECORDS], [1], [Define if enabling LUA records.])
])
AM_CONDITIONAL([HAVE_LUA_RECORDS], [test "x$enable_lua_records" != "xno"])
AS_IF([test "x$with_net_snmp" != "xno"], [
AS_IF([test "x$with_net_snmp" = "xyes" -o "x$with_net_snmp" = "xauto"], [
AC_CHECK_PROG([NET_SNMP_CFLAGS], [net-snmp-config], [`net-snmp-config --cflags`])
- AC_CHECK_PROG([NET_SNMP_LIBS], [net-snmp-config], [`net-snmp-config --agent-libs`])
- AC_CHECK_DECLS([snmp_select_info2], [ : ], [ : ],
+ AC_CHECK_PROG([NET_SNMP_LIBS], [net-snmp-config], [`net-snmp-config --netsnmp-agent-libs`])
+ AC_CHECK_DECLS([snmp_select_info2], [
+ AC_DEFINE([HAVE_SNMP_SELECT_INFO2], [1], [define to 1 if snmp_select_info2 is available.])
+ ],
+ [ : ],
[AC_INCLUDES_DEFAULT
#include <net-snmp/net-snmp-config.h>
#include <net-snmp/definitions.h>
d_transaction_id=id;
BB2DomainInfo bbd;
if(safeGetBBDomainInfo(id, &bbd)) {
- d_transaction_tmpname=bbd.d_filename+"."+itoa(random());
- d_of=std::unique_ptr<ofstream>(new ofstream(d_transaction_tmpname.c_str()));
+ d_transaction_tmpname = bbd.d_filename + "XXXXXX";
+ int fd = mkstemp(&d_transaction_tmpname.at(0));
+ if (fd == -1) {
+ throw DBException("Unable to create a unique temporary zonefile '"+d_transaction_tmpname+"': "+stringerror());
+ return false;
+ }
+
+ d_of = std::unique_ptr<ofstream>(new ofstream(d_transaction_tmpname.c_str()));
if(!*d_of) {
unlink(d_transaction_tmpname.c_str());
+ close(fd);
+ fd = -1;
d_of.reset();
throw DBException("Unable to open temporary zonefile '"+d_transaction_tmpname+"': "+stringerror());
}
-
+ close(fd);
+ fd = -1;
+
*d_of<<"; Written by PowerDNS, don't edit!"<<endl;
*d_of<<"; Zone '"<<bbd.d_name<<"' retrieved from master "<<endl<<"; at "<<nowTime()<<endl; // insert master info here again
-
+
return true;
}
return false;
bbd.d_loaded=true;
bbd.d_lastcheck=0;
bbd.d_status="parsing into memory";
+ bbd.setCtime();
safePutBBDomainInfo(bbd);
setArgPrefix("bind"+suffix);
d_logprefix="[bind"+suffix+"backend]";
d_hybrid=mustDo("hybrid");
+ d_transaction_id=0;
s_ignore_broken_records=mustDo("ignore-broken-records");
if (!loadZones && d_hybrid)
Lock l(&s_startup_lock);
- d_transaction_id=0;
setupDNSSEC();
if(!s_first) {
return;
}
BB2DomainInfo bbd;
+ bbd.d_kind = DomainInfo::Native;
bbd.d_id = newid;
bbd.d_records = shared_ptr<recordstorage_t >(new recordstorage_t);
bbd.d_name = domain;
bbd.setCheckInterval(getArgAsNum("check-interval"));
bbd.d_filename = filename;
+
return bbd;
}
BB2DomainInfo bbd = createDomainEntry(domain, filename);
bbd.d_kind = DomainInfo::Slave;
bbd.d_masters.push_back(ComboAddress(ip, 53));
+ bbd.setCtime();
safePutBBDomainInfo(bbd);
return true;
}
for(state_t::const_iterator i = s_state.begin(); i != s_state.end() ; ++i) {
BB2DomainInfo h;
- safeGetBBDomainInfo(i->d_id, &h);
+ if (!safeGetBBDomainInfo(i->d_id, &h)) {
+ continue;
+ }
+
shared_ptr<const recordstorage_t> rhandle = h.d_records.get();
for(recordstorage_t::const_iterator ri = rhandle->begin(); result.size() < static_cast<vector<DNSResourceRecord>::size_type>(maxResults) && ri != rhandle->end(); ri++) {
declare(suffix,"supermasters","List of IP-addresses of supermasters","");
declare(suffix,"supermaster-destdir","Destination directory for newly added slave zones",::arg()["config-dir"]);
declare(suffix,"dnssec-db","Filename to store & access our DNSSEC metadatabase, empty for none", "");
+ declare(suffix,"dnssec-db-journal-mode","SQLite3 journal mode", "WAL");
declare(suffix,"hybrid","Store DNSSEC metadata in other backend","no");
}
vector<ComboAddress> d_masters; //!< IP address of the master of this domain
set<string> d_also_notify; //!< IP list of hosts to also notify
LookButDontTouch<recordstorage_t> d_records; //!< the actual records belonging to this domain
- time_t d_ctime; //!< last known ctime of the file on disk
- time_t d_lastcheck; //!< last time domain was checked for freshness
- uint32_t d_lastnotified; //!< Last serial number we notified our slaves of
+ time_t d_ctime{0}; //!< last known ctime of the file on disk
+ time_t d_lastcheck{0}; //!< last time domain was checked for freshness
+ uint32_t d_lastnotified{0}; //!< Last serial number we notified our slaves of
unsigned int d_id; //!< internal id of the domain
mutable bool d_checknow; //!< if this domain has been flagged for a check
bool d_loaded; //!< if a domain is loaded
if(getArg("dnssec-db").empty() || d_hybrid)
return;
try {
- d_dnssecdb = shared_ptr<SSQLite3>(new SSQLite3(getArg("dnssec-db")));
+ d_dnssecdb = shared_ptr<SSQLite3>(new SSQLite3(getArg("dnssec-db"), getArg("dnssec-db-journal-mode")));
setupStatements();
}
catch(SSqlException& se) {
declare(suffix,"update-account-query","", "update domains set account=? where name=?");
declare(suffix,"update-serial-query","", "update domains set notified_serial=? where id=?");
declare(suffix,"update-lastcheck-query","", "update domains set last_check=? where id=?");
- declare(suffix,"info-all-master-query","", "select id,name,master,last_check,notified_serial,type from domains where type='MASTER'");
+ declare(suffix,"info-all-master-query","", "select d.id, d.name, d.notified_serial, r.content from records r join domains d on r.name=d.name where r.type='SOA' and r.disabled=0 and d.type='MASTER'");
declare(suffix,"delete-domain-query","", "delete from domains where name=?");
declare(suffix,"delete-zone-query","", "delete from records where domain_id=?");
declare(suffix,"delete-rrset-query","","delete from records where domain_id=? and name=? and type=?");
declare(suffix,"update-account-query","", "update domains set account=? where name=?");
declare(suffix,"update-serial-query","", "update domains set notified_serial=? where id=?");
declare(suffix,"update-lastcheck-query","", "update domains set last_check=? where id=?");
- declare(suffix,"info-all-master-query","", "select id,name,master,last_check,notified_serial,type from domains where type='MASTER'");
+ declare(suffix,"info-all-master-query","", "select domains.id, domains.name, domains.notified_serial, records.content from records join domains on records.name=domains.name where records.type='SOA' and records.disabled=0 and domains.type='MASTER'");
declare(suffix,"delete-domain-query","", "delete from domains where name=?");
declare(suffix,"delete-zone-query","", "delete from records where domain_id=?");
declare(suffix,"delete-rrset-query","","delete from records where domain_id=? and name=? and type=?");
declare(suffix,"update-account-query","", "update domains set account=$1 where name=$2");
declare(suffix,"update-serial-query","", "update domains set notified_serial=$1 where id=$2");
declare(suffix,"update-lastcheck-query","", "update domains set last_check=$1 where id=$2");
- declare(suffix,"info-all-master-query","", "select id,name,master,last_check,notified_serial,type from domains where type='MASTER'");
+ declare(suffix,"info-all-master-query","", "select domains.id, domains.name, domains.notified_serial, records.content from records join domains on records.name=domains.name where records.type='SOA' and records.disabled=false and domains.type='MASTER'");
declare(suffix,"delete-domain-query","", "delete from domains where name=$1");
declare(suffix,"delete-zone-query","", "delete from records where domain_id=$1");
declare(suffix,"delete-rrset-query","","delete from records where domain_id=$1 and name=$2 and type=$3");
{
try
{
- SSQLite3 *ptr = new SSQLite3( getArg( "database" ));
+ SSQLite3 *ptr = new SSQLite3( getArg( "database" ), getArg( "pragma-journal-mode") );
setDB(ptr);
if(!getArg("pragma-synchronous").empty()) {
ptr->execute("PRAGMA synchronous="+getArg("pragma-synchronous"));
declare(suffix, "database", "Filename of the SQLite3 database", "powerdns.sqlite");
declare(suffix, "pragma-synchronous", "Set this to 0 for blazing speed", "");
declare(suffix, "pragma-foreign-keys", "Enable foreign key constraints", "no" );
+ declare(suffix, "pragma-journal-mode", "SQLite3 journal mode", "WAL");
declare(suffix, "dnssec", "Enable DNSSEC processing","no");
declare(suffix, "update-account-query","", "update domains set account=:account where name=:domain");
declare(suffix, "update-serial-query", "", "update domains set notified_serial=:serial where id=:domain_id");
declare(suffix, "update-lastcheck-query", "", "update domains set last_check=:last_check where id=:domain_id");
- declare(suffix, "info-all-master-query", "", "select id,name,master,last_check,notified_serial,type from domains where type='MASTER'");
+ declare(suffix, "info-all-master-query", "", "select domains.id, domains.name, domains.notified_serial, records.content from records join domains on records.name=domains.name where records.type='SOA' and records.disabled=0 and domains.type='MASTER'");
declare(suffix, "delete-domain-query","", "delete from domains where name=:domain");
declare(suffix, "delete-zone-query", "", "delete from records where domain_id=:domain_id");
declare(suffix, "delete-rrset-query", "", "delete from records where domain_id=:domain_id and name=:qname and type=:qtype");
// search for SOARecord of domain
filter = "(&(associatedDomain=" + toLower( d_pldap->escape( domain.toStringRootDot() ) ) + ")(SOARecord=*))";
d_search = d_pldap->search( getArg( "basedn" ), LDAP_SCOPE_SUBTREE, filter, attronly );
- d_search->getNext( result );
+ if (!d_search->getNext( result )) {
+ return false;
+ }
}
catch( LDAPTimeout < )
{
return d_cursor;
}
- MDB_cursor* d_cursor;
+ MDB_cursor* d_cursor{nullptr};
Transaction* d_parent;
};
MDBROCursor(MDBROCursor&& rhs) : MDBGenCursor<MDBROTransaction>(rhs.d_parent)
{
d_cursor = rhs.d_cursor;
- rhs.d_cursor=0;
+ rhs.d_cursor = nullptr;
}
void close()
{
- mdb_cursor_close(d_cursor);
- d_cursor=0;
+ if (d_cursor) {
+ mdb_cursor_close(d_cursor);
+ d_cursor = nullptr;
+ }
}
~MDBROCursor()
MDBRWCursor(MDBRWCursor&& rhs) : MDBGenCursor<MDBRWTransaction>(rhs.d_parent)
{
d_cursor = rhs.d_cursor;
- rhs.d_cursor=0;
+ rhs.d_cursor = nullptr;
d_parent->reportCursorMove(&rhs, this);
}
{
if(d_cursor)
mdb_cursor_close(d_cursor);
- d_cursor=0;
+ d_cursor = nullptr;
}
~MDBRWCursor()
}
DomainInfo di;
- d_tdomains->getROTransaction().get(domain_id, di); // XX error checking
-
+ if (!d_tdomains->getROTransaction().get(domain_id, di)) {
+ return false;
+ }
+
compoundOrdername co;
auto cursor = txn->txn.getCursor(txn->db->dbi);
MDBOutVal key, val;
rr.qname = dzr.dr.d_name;
rr.ttl = dzr.dr.d_ttl;
rr.qtype =dzr.dr.d_type;
- rr.content = dzr.dr.d_content->getZoneRepresentation();
+ rr.content = dzr.dr.d_content->getZoneRepresentation(true);
rr.domain_id = dzr.domain_id;
+ rr.auth = dzr.auth;
// cout<<"old school called for "<<rr.qname<<", "<<rr.qtype.getName()<<endl;
return true;
}
sdig_LDADD = $(LIBCRYPTO_LIBS)
sdig_LDFLAGS = $(AM_LDFLAGS) $(LIBCRYPTO_LDFLAGS)
+if HAVE_LIBCURL
+sdig_SOURCES += minicurl.cc minicurl.hh
+sdig_LDADD += $(LIBCURL)
+endif
+
calidns_SOURCES = \
base32.cc \
base64.cc base64.hh \
void GSQLBackend::getUpdatedMasters(vector<DomainInfo> *updatedDomains)
{
/* list all domains that need notifications for which we are master, and insert into updatedDomains
- id,name,master IP,serial */
+ id, name, notified_serial, serial */
try {
reconnectIfNeeded();
throw PDNSException("GSQLBackend unable to retrieve list of master domains: "+e.txtReason());
}
- vector<DomainInfo> allMasters;
size_t numanswers=d_result.size();
- for(size_t n=0;n<numanswers;++n) { // id,name,master,last_check,notified_serial
- DomainInfo sd;
- ASSERT_ROW_COLUMNS("info-all-master-query", d_result[n], 6);
- sd.id=pdns_stou(d_result[n][0]);
+ vector<string>parts;
+ DomainInfo di;
+
+ di.backend = this;
+ di.kind = DomainInfo::Master;
+
+ for( size_t n = 0; n < numanswers; ++n ) { // id, name, notified_serial, content
+ ASSERT_ROW_COLUMNS( "info-all-master-query", d_result[n], 4 );
+
+ parts.clear();
+ stringtok( parts, d_result[n][3] );
+
try {
- sd.zone= DNSName(d_result[n][1]);
- } catch (...) {
+ uint32_t serial = parts.size() > 2 ? pdns_stou(parts[2]) : 0;
+ uint32_t notified_serial = pdns_stou( d_result[n][2] );
+
+ if( serial != notified_serial ) {
+ di.id = pdns_stou( d_result[n][0] );
+ di.zone = DNSName( d_result[n][1] );
+ di.serial = serial;
+ di.notified_serial = notified_serial;
+
+ updatedDomains->emplace_back(di);
+ }
+ } catch ( ... ) {
continue;
}
- sd.last_check=pdns_stou(d_result[n][3]);
- sd.notified_serial=pdns_stou(d_result[n][4]);
- sd.backend=this;
- sd.kind=DomainInfo::Master;
- allMasters.push_back(sd);
- }
-
- for(vector<DomainInfo>::iterator i=allMasters.begin();i!=allMasters.end();++i) {
- SOAData sdata;
- sdata.serial=0;
- sdata.refresh=0;
- getSOA(i->zone,sdata);
- if(i->notified_serial!=sdata.serial) {
- i->serial=sdata.serial;
- updatedDomains->push_back(*i);
- }
}
}
SOAData sd;
fillSOAData(row[2], sd);
di.serial = sd.serial;
- di.notified_serial = pdns_stou(row[5]);
- di.last_check = pdns_stou(row[6]);
+ try {
+ di.notified_serial = pdns_stou(row[5]);
+ di.last_check = pdns_stou(row[6]);
+ } catch(...) {
+ continue;
+ }
di.account = row[7];
di.backend = this;
CommunicatorClass Communicator;
shared_ptr<UDPNameserver> N;
int avg_latency;
-TCPNameserver *TN;
+unique_ptr<TCPNameserver> TN;
static vector<DNSDistributor*> g_distributors;
vector<std::shared_ptr<UDPNameserver> > g_udpReceivers;
::arg().set("queue-limit","Maximum number of milliseconds to queue a query")="1500";
::arg().set("resolver","Use this resolver for ALIAS and the internal stub resolver")="no";
::arg().set("udp-truncation-threshold", "Maximum UDP response size before we truncate")="1232";
- ::arg().set("disable-tcp","Do not listen to TCP queries")="no";
::arg().set("config-name","Name of this virtual configuration - will rename the binary image")="";
g_8bitDNS = ::arg().mustDo("8bit-dns");
#ifdef HAVE_LUA_RECORDS
g_doLuaRecord = ::arg().mustDo("enable-lua-records");
+ g_LuaRecordSharedState = (::arg()["enable-lua-records"] == "shared");
g_luaRecordExecLimit = ::arg().asNum("lua-records-exec-limit");
#endif
if(::arg().mustDo("slave") || ::arg().mustDo("master") || !::arg()["forward-notify"].empty())
Communicator.go();
- if(TN)
- TN->go(); // tcp nameserver launch
+ TN->go(); // tcp nameserver launch
// fork(); (this worked :-))
unsigned int max_rthreads= ::arg().asNum("receiver-threads", 1);
extern std::shared_ptr<UDPNameserver> N;
extern vector<std::shared_ptr<UDPNameserver> > g_udpReceivers;
extern int avg_latency;
-extern TCPNameserver *TN;
+extern std::unique_ptr<TCPNameserver> TN;
extern ArgvMap & arg( void );
extern void declareArguments();
extern void declareStats();
extern bool g_8bitDNS;
#ifdef HAVE_LUA_RECORDS
extern bool g_doLuaRecord;
+extern bool g_LuaRecordSharedState;
#endif // HAVE_LUA_RECORDS
#endif // COMMON_STARTUP_HH
if (extraSlaveRefresh)
slaveRefresh(&P);
}
- else {
+ else {
+ // eat up extra posts to avoid busy looping if many posts were done
+ while (d_any_sem.tryWait() == 0) {
+ }
break; // something happened
}
// this gets executed at least once every second
#endif
case RNG_URANDOM: {
uint32_t num = 0;
+ size_t attempts = 5;
do {
- if (read(urandom_fd, &num, sizeof(num)) < 0) {
+ ssize_t got = read(urandom_fd, &num, sizeof(num));
+ if (got < 0) {
+ if (errno == EINTR) {
+ continue;
+ }
+
(void)close(urandom_fd);
throw std::runtime_error("Cannot read random device");
}
+ else if (static_cast<size_t>(got) != sizeof(num)) {
+ /* short read, let's retry */
+ if (attempts == 0) {
+ throw std::runtime_error("Too many short reads on random device");
+ }
+ attempts--;
+ continue;
+ }
}
while(num < min);
("type,t", po::value<string>()->default_value("A"), "What type to query for")
("envoutput,e", "write report in shell environment format")
("version", "show the version number")
- ("www", po::value<bool>()->default_value("true"), "duplicate all queries with an additional 'www.' in front")
+ ("www", po::value<bool>()->default_value(true), "duplicate all queries with an additional 'www.' in front")
;
po::options_description alloptions;
split=splitField(line,',');
if (split.second.empty())
split=splitField(line,'\t');
- if(!split.second.find('.')) // skip 'Hidden profile' in quantcast list.
+ if(split.second.find('.') == 0) // skip 'Hidden profile' in quantcast list.
continue;
pos=split.second.find('/');
if(pos != string::npos) // alexa has whole urls in the list now.
/* keyword, function, parameters, description */
{ "addACL", true, "netmask", "add to the ACL set who can use this server" },
{ "addAction", true, "DNS rule, DNS action [, {uuid=\"UUID\"}]", "add a rule" },
+ { "addBPFFilterDynBlocks", true, "addresses, dynbpf[[, seconds=10], msg]", "This is the eBPF equivalent of addDynBlocks(), blocking a set of addresses for (optionally) a number of seconds, using an eBPF dynamic filter" },
{ "addConsoleACL", true, "netmask", "add a netmask to the console ACL" },
{ "addDNSCryptBind", true, "\"127.0.0.1:8443\", \"provider name\", \"/path/to/resolver.cert\", \"/path/to/resolver.key\", {reusePort=false, tcpFastOpenSize=0, interface=\"\", cpus={}}", "listen to incoming DNSCrypt queries on 127.0.0.1 port 8443, with a provider name of `provider name`, using a resolver certificate and associated key stored respectively in the `resolver.cert` and `resolver.key` files. The fifth optional parameter is a table of parameters" },
{ "addDOHLocal", true, "addr, certFile, keyFile [, urls [, vars]]", "listen to incoming DNS over HTTPS queries on the specified address using the specified certificate and key. The last two parameters are tables" },
{ "addDynBlocks", true, "addresses, message[, seconds[, action]]", "block the set of addresses with message `msg`, for `seconds` seconds (10 by default), applying `action` (default to the one set with `setDynBlocksAction()`)" },
+ { "addDynBlockSMT", true, "names, msessage[, seconds [, action]]", "block the set of names with message `msg`, for `seconds` seconds (10 by default), applying `action` (default to the one set with `setDynBlocksAction()`)" },
{ "addLocal", true, "addr [, {doTCP=true, reusePort=false, tcpFastOpenSize=0, interface=\"\", cpus={}}]", "add `addr` to the list of addresses we listen on" },
{ "addCacheHitResponseAction", true, "DNS rule, DNS response action [, {uuid=\"UUID\"}]", "add a cache hit response rule" },
{ "addResponseAction", true, "DNS rule, DNS response action [, {uuid=\"UUID\"}]", "add a response rule" },
{ "DelayAction", true, "milliseconds", "delay the response by the specified amount of milliseconds (UDP-only)" },
{ "DelayResponseAction", true, "milliseconds", "delay the response by the specified amount of milliseconds (UDP-only)" },
{ "delta", true, "", "shows all commands entered that changed the configuration" },
+ { "DisableECSAction", true, "", "Disable the sending of ECS to the backend. Subsequent rules are processed after this action." },
{ "DisableValidationAction", true, "", "set the CD bit in the question, let it go through" },
+ { "DNSSECRule", true, "", "matches queries with the DO bit set" },
{ "DnstapLogAction", true, "identity, FrameStreamLogger [, alterFunction]", "send the contents of this query to a FrameStreamLogger or RemoteLogger as dnstap. `alterFunction` is a callback, receiving a DNSQuestion and a DnstapMessage, that can be used to modify the dnstap message" },
{ "DnstapLogResponseAction", true, "identity, FrameStreamLogger [, alterFunction]", "send the contents of this response to a remote or FrameStreamLogger or RemoteLogger as dnstap. `alterFunction` is a callback, receiving a DNSResponse and a DnstapMessage, that can be used to modify the dnstap message" },
{ "DropAction", true, "", "drop these packets" },
{ "DSTPortRule", true, "port", "matches questions received to the destination port specified" },
{ "dumpStats", true, "", "print all statistics we gather" },
{ "dynBlockRulesGroup", true, "", "return a new DynBlockRulesGroup object" },
+ { "ECSOverrideAction", true, "override", "Whether an existing EDNS Client Subnet value should be overridden (true) or not (false). Subsequent rules are processed after this action" },
+ { "ECSPrefixLengthAction", true, "v4, v6", "Set the ECS prefix length. Subsequent rules are processed after this action" },
{ "EDNSVersionRule", true, "version", "matches queries with the specified EDNS version" },
{ "EDNSOptionRule", true, "optcode", "matches queries with the specified EDNS0 option present" },
{ "ERCodeAction", true, "ercode", "Reply immediately by turning the query into a response with the specified EDNS extended rcode" },
{ "fixupCase", true, "bool", "if set (default to no), rewrite the first qname of the question part of the answer to match the one from the query. It is only useful when you have a downstream server that messes up the case of the question qname in the answer" },
{ "generateDNSCryptCertificate", true, "\"/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" },
{ "generateDNSCryptProviderKeys", true, "\"/path/to/providerPublic.key\", \"/path/to/providerPrivate.key\"", "generate a new provider keypair" },
+ { "getAction", true, "n", "Returns the Action associated with rule n" },
{ "getBind", true, "n", "returns the listener at index n" },
{ "getDNSCryptBind", true, "n", "return the `DNSCryptContext` object corresponding to the bind `n`" },
{ "getDOHFrontend", true, "n", "returns the DOH frontend with index n" },
{ "getPoolServers", true, "pool", "return servers part of this pool" },
{ "getQueryCounters", true, "[max=10]", "show current buffer of query counters, limited by 'max' if provided" },
{ "getResponseRing", true, "", "return the current content of the response ring" },
+ { "getRespRing", true, "", "return the qname/rcode content of the response ring" },
{ "getServer", true, "n", "returns server with index n" },
{ "getServers", true, "", "returns a table with all defined servers" },
+ { "getStatisticsCounters", true, "", "returns a map of statistic counters" },
{ "getTLSContext", true, "n", "returns the TLS context with index n" },
{ "getTLSFrontend", true, "n", "returns the TLS frontend with index n" },
+ { "HTTPHeaderRule", true, "name, regex", "matches DoH queries with a HTTP header 'name' whose content matches the regular expression 'regex'"},
+ { "HTTPPathRule", true, "path", "matches DoH queries whose HTTP path is an exact match to 'path'"},
{ "inClientStartup", true, "", "returns true during console client parsing of configuration" },
+ { "includeDirectory", true, "path", "nclude configuration files from `path`" },
{ "grepq", true, "Netmask|DNS Name|100ms|{\"::1\", \"powerdns.com\", \"100ms\"} [, n]", "shows the last n queries and responses matching the specified client address or range (Netmask), or the specified DNS Name, or slower than 100ms" },
{ "leastOutstanding", false, "", "Send traffic to downstream server with least outstanding queries, with the lowest 'order', and within that the lowest recent latency"},
{ "LogAction", true, "[filename], [binary], [append], [buffered]", "Log a line for each query, to the specified file if any, to the console (require verbose) otherwise. When logging to a file, the `binary` optional parameter specifies whether we log in binary form (default) or in textual form, the `append` optional parameter specifies whether we open the file for appending or truncate each time (default), and the `buffered` optional parameter specifies whether writes to the file are buffered (default) or not." },
+ { "LuaAction", true, "function", "Invoke a Lua function that accepts a DNSQuestion" },
+ { "LuaResponseAction", true, "function", "Invoke a Lua function that accepts a DNSResponse" },
+ { "MacAddrAction", true, "option", "Add the source MAC address to the query as EDNS0 option option. This action is currently only supported on Linux. Subsequent rules are processed after this action" },
+ { "makeIPCipherKey", true, "password", "generates a 16-byte key that can be used to pseudonymize IP addresses with IP cipher" },
{ "makeKey", true, "", "generate a new server access key, emit configuration line ready for pasting" },
+ { "makeRule", true, "rule", "Make a NetmaskGroupRule() or a SuffixMatchNodeRule(), depending on how it is called" } ,
{ "MaxQPSIPRule", true, "qps, [v4Mask=32 [, v6Mask=64 [, burst=qps [, expiration=300 [, cleanupDelay=60]]]]]", "matches traffic exceeding the qps limit per subnet" },
{ "MaxQPSRule", true, "qps", "matches traffic **not** exceeding this qps limit" },
{ "mvCacheHitResponseRule", true, "from, to", "move cache hit response rule 'from' to a position where it is in front of 'to'. 'to' can be one larger than the largest rule" },
{ "mvResponseRule", true, "from, to", "move response rule 'from' to a position where it is in front of 'to'. 'to' can be one larger than the largest rule" },
{ "mvRule", true, "from, to", "move rule 'from' to a position where it is in front of 'to'. 'to' can be one larger than the largest rule, in which case the rule will be moved to the last position" },
{ "mvSelfAnsweredResponseRule", true, "from, to", "move self-answered response rule 'from' to a position where it is in front of 'to'. 'to' can be one larger than the largest rule" },
+ { "NetmaskGroupRule", true, "nmg[, src]", "Matches traffic from/to the network range specified in nmg. Set the src parameter to false to match nmg against destination address instead of source address. This can be used to differentiate between clients" },
+ { "newBPFFilter", true, "maxV4, maxV6, maxQNames", "Return a new eBPF socket filter with a maximum of maxV4 IPv4, maxV6 IPv6 and maxQNames qname entries in the block table" },
+ { "newCA", true, "address", "Returns a ComboAddress based on `address`" },
{ "newDNSName", true, "name", "make a DNSName based on this .-terminated name" },
+ { "newDNSNameSet", true, "", "returns a new DNSNameSet" },
+ { "newDynBPFFilter", true, "bpf", "Return a new dynamic eBPF filter associated to a given BPF Filter" },
{ "newFrameStreamTcpLogger", true, "addr", "create a FrameStream logger object writing to a TCP address (addr should be ip:port), to use with `DnstapLogAction()` and `DnstapLogResponseAction()`" },
{ "newFrameStreamUnixLogger", true, "socket", "create a FrameStream logger object writing to a local unix socket, to use with `DnstapLogAction()` and `DnstapLogResponseAction()`" },
+ { "newNMG", true, "", "Returns a NetmaskGroup" },
{ "newPacketCache", true, "maxEntries[, maxTTL=86400, minTTL=0, temporaryFailureTTL=60, staleTTL=60, dontAge=false, numberOfShards=1, deferrableInsertLock=true, options={}]", "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()`" },
{ "newServer", true, "{address=\"ip:port\", qps=1000, order=1, weight=10, pool=\"abuse\", retries=5, tcpConnectTimeout=5, tcpSendTimeout=30, tcpRecvTimeout=30, checkName=\"a.root-servers.net.\", checkType=\"A\", maxCheckFailures=1, mustResolve=false, useClientSubnet=true, source=\"address|interface name|address@interface\", sockets=1}", "instantiate a server" },
{ "newServerPolicy", true, "name, function", "create a policy object from a Lua function" },
{ "newSuffixMatchNode", true, "", "returns a new SuffixMatchNode" },
- { "newDNSNameSet", true, "", "returns a new DNSNameSet" },
+ { "NoneAction", true, "", "Does nothing. Subsequent rules are processed after this action" },
{ "NoRecurseAction", true, "", "strip RD bit from the question, let it go through" },
+ { "NotRule", true, "selector", "Matches the traffic if the selector rule does not match" },
+ { "OpcodeRule", true, "code", "Matches queries with opcode code. code can be directly specified as an integer, or one of the built-in DNSOpcodes" },
+ { "OrRule", true, "selectors", "Matches the traffic if one or more of the the selectors rules does match" },
{ "PoolAction", true, "poolname", "set the packet into the specified pool" },
+ { "PoolAvailableRule", true, "poolname", "Check whether a pool has any servers available to handle queries" },
{ "printDNSCryptProviderFingerprint", true, "\"/path/to/providerPublic.key\"", "display the fingerprint of the provided resolver public key" },
+ { "ProbaRule", true, "probability", "Matches queries with a given probability. 1.0 means always" },
+ { "QClassRule", true, "qclass", "Matches queries with the specified qclass. class can be specified as an integer or as one of the built-in DNSClass" },
{ "QNameLabelsCountRule", true, "min, max", "matches if the qname has less than `min` or more than `max` labels" },
{ "QNameRule", true, "qname", "matches queries with the specified qname" },
+ { "QNameSetRule", true, "set", "Matches if the set contains exact qname" },
{ "QNameWireLengthRule", true, "min, max", "matches if the qname's length on the wire is less than `min` or more than `max` bytes" },
+ { "QPSAction", true, "maxqps", "Drop a packet if it does exceed the maxqps queries per second limits. Letting the subsequent rules apply otherwise" },
+ { "QPSPoolAction", true, "maxqps, poolname", "Send the packet into the specified pool only if it does not exceed the maxqps queries per second limits. Letting the subsequent rules apply otherwise" },
{ "QTypeRule", true, "qtype", "matches queries with the specified qtype" },
{ "RCodeAction", true, "rcode", "Reply immediately by turning the query into a response with the specified rcode" },
{ "RCodeRule", true, "rcode", "matches responses with the specified rcode" },
+ { "RDRule", true, "", "Matches queries with the RD flag set" },
+ { "RecordsCountRule", true, "section, minCount, maxCount", "Matches if there is at least minCount and at most maxCount records in the section section. section can be specified as an integer or as a DNS Packet Sections" },
+ { "RecordsTypeCountRule", true, "section, qtype, minCount, maxCount", "Matches if there is at least minCount and at most maxCount records of type type in the section section" },
{ "RegexRule", true, "regex", "matches the query name against the supplied regex" },
{ "registerDynBPFFilter", true, "DynBPFFilter", "register this dynamic BPF filter into the web interface so that its counters are displayed" },
{ "reloadAllCertificates", true, "", "reload all DNSCrypt and TLS certificates, along with their associated keys" },
{ "setAddEDNSToSelfGeneratedResponses", true, "add", "set whether to add EDNS to self-generated responses, provided that the initial query had EDNS" },
{ "setAllowEmptyResponse", true, "allow", "Set to true (defaults to false) to allow empty responses (qdcount=0) with a NoError or NXDomain rcode (default) from backends" },
{ "setAPIWritable", true, "bool, dir", "allow modifications via the API. if `dir` is set, it must be a valid directory where the configuration files will be written by the API" },
+ { "setCacheCleaningDelay", true, "num", "Set the interval in seconds between two runs of the cache cleaning algorithm, removing expired entries" },
+ { "setCacheCleaningPercentage", true, "num", "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 remove" },
{ "setConsoleACL", true, "{netmask, netmask}", "replace the console ACL set with these netmasks" },
{ "setConsoleConnectionsLogging", true, "enabled", "whether to log the opening and closing of console connections" },
{ "setConsoleOutputMaxMsgSize", true, "messageSize", "set console message maximum size in bytes, default is 10 MB" },
- { "setDNSSECPool", true, "pool name", "move queries requesting DNSSEC processing to this pool" },
+ { "setDefaultBPFFilter", true, "filter", "When used at configuration time, the corresponding BPFFilter will be attached to every bind" },
{ "setDynBlocksAction", true, "action", "set which action is performed when a query is blocked. Only DNSAction.Drop (the default) and DNSAction.Refused are supported" },
+ { "SetECSAction", true, "v4[, v6]", "Set the ECS prefix and prefix length sent to backends to an arbitrary value" },
{ "setECSOverride", true, "bool", "whether to override an existing EDNS Client Subnet value in the query" },
{ "setECSSourcePrefixV4", true, "prefix-length", "the EDNS Client Subnet prefix-length used for IPv4 queries" },
{ "setECSSourcePrefixV6", true, "prefix-length", "the EDNS Client Subnet prefix-length used for IPv6 queries" },
{ "setMaxUDPOutstanding", true, "n", "set the maximum number of outstanding UDP queries to a given backend server. This can only be set at configuration time and defaults to 10240" },
{ "setPayloadSizeOnSelfGeneratedAnswers", true, "payloadSize", "set the UDP payload size advertised via EDNS on self-generated responses" },
{ "setPoolServerPolicy", true, "policy, pool", "set the server selection policy for this pool to that policy" },
- { "setPoolServerPolicy", true, "name, func, pool", "set the server selection policy for this pool to one named 'name' and provided by 'function'" },
+ { "setPoolServerPolicyLua", true, "name, func, pool", "set the server selection policy for this pool to one named 'name' and provided by 'function'" },
{ "setPreserveTrailingData", true, "bool", "set whether trailing data should be preserved while adding ECS or XPF records to incoming queries" },
{ "setQueryCount", true, "bool", "set whether queries should be counted" },
{ "setQueryCountFilter", true, "func", "filter queries that would be counted, where `func` is a function with parameter `dq` which decides whether a query should and how it should be counted" },
{ "setRingBuffersLockRetries", true, "n", "set the number of attempts to get a non-blocking lock to a ringbuffer shard before blocking" },
{ "setRingBuffersSize", true, "n [, numberOfShards]", "set the capacity of the ringbuffers used for live traffic inspection to `n`, and optionally the number of shards to use to `numberOfShards`" },
+ { "setRoundRobinFailOnNoServer", true, "value", "By default the roundrobin load-balancing policy will still try to select a backend even if all backends are currently down. Setting this to true will make the policy fail and return that no server is available instead" },
{ "setRules", true, "list of rules", "replace the current rules with the supplied list of pairs of DNS Rules and DNS Actions (see `newRuleAction()`)" },
{ "setSecurityPollInterval", true, "n", "set the security polling interval to `n` seconds" },
{ "setSecurityPollSuffix", true, "suffix", "set the security polling suffix to the specified value" },
{ "setUDPTimeout", true, "n", "set the maximum time dnsdist will wait for a response from a backend over UDP, in seconds" },
{ "setVerboseHealthChecks", true, "bool", "set whether health check errors will be logged" },
{ "setWebserverConfig", true, "[{password=string, apiKey=string, customHeaders}]", "Updates webserver configuration" },
+ { "setWHashedPertubation", true, "value", "Set the hash perturbation value to be used in the whashed policy instead of a random one, allowing to have consistent whashed results on different instance" },
{ "show", true, "string", "outputs `string`" },
{ "showACL", true, "", "show our ACL set" },
{ "showBinds", true, "", "show listening addresses (frontends)" },
{ "showTLSContexts", true, "", "list all the available TLS contexts" },
{ "showVersion", true, "", "show the current version" },
{ "shutdown", true, "", "shut down `dnsdist`" },
+ { "SkipCacheAction", true, "", "Don’t lookup the cache for this query, don’t store the answer" },
+ { "SNIRule", true, "name", "Create a rule which matches on the incoming TLS SNI value, if any (DoT or DoH)" },
{ "snmpAgent", true, "enableTraps [, masterSocket]", "enable `SNMP` support. `enableTraps` is a boolean indicating whether traps should be sent and `masterSocket` an optional string specifying how to connect to the master agent"},
{ "SNMPTrapAction", true, "[reason]", "send an SNMP trap, adding the optional `reason` string as the query description"},
{ "SNMPTrapResponseAction", true, "[reason]", "send an SNMP trap, adding the optional `reason` string as the response description"},
{ "SpoofAction", true, "{ip, ...} ", "forge a response with the specified IPv4 (for an A query) or IPv6 (for an AAAA). If you specify multiple addresses, all that match the query type (A, AAAA or ANY) will get spoofed in" },
+ { "SpoofCNAMEAction", true, "cname", "Forge a response with the specified CNAME value" },
+ { "SuffixMatchNodeRule", true, "smn[, quiet]", "Matches based on a group of domain suffixes for rapid testing of membership. Pass true as second parameter to prevent listing of all domains matched" },
{ "TagAction", true, "name, value", "set the tag named 'name' to the given value" },
{ "TagResponseAction", true, "name, value", "set the tag named 'name' to the given value" },
{ "TagRule", true, "name [, value]", "matches if the tag named 'name' is present, with the given 'value' matching if any" },
{ "TCAction", true, "", "create answer to query with TC and RD bits set, to move to TCP" },
+ { "TCPRule", true, "[tcp]", "Matches question received over TCP if tcp is true, over UDP otherwise" },
{ "TeeAction", true, "remote [, addECS]", "send copy of query to remote, optionally adding ECS info" },
{ "TempFailureCacheTTLAction", true, "ttl", "set packetcache TTL for temporary failure replies" },
{ "testCrypto", true, "", "test of the crypto all works" },
{ "topClients", true, "n", "show top-`n` clients sending the most queries over length of ringbuffer" },
{ "topQueries", true, "n[, labels]", "show top 'n' queries, as grouped when optionally cut down to 'labels' labels" },
{ "topResponses", true, "n, kind[, labels]", "show top 'n' responses with RCODE=kind (0=NO Error, 2=ServFail, 3=ServFail), as grouped when optionally cut down to 'labels' labels" },
- { "topCacheHitResponseRule", true, "", "move the last cache hit response rule to the first position" },
{ "topResponseRule", true, "", "move the last response rule to the first position" },
{ "topRule", true, "", "move the last rule to the first position" },
{ "topSelfAnsweredResponseRule", true, "", "move the last self-answered response rule to the first position" },
{ "topSlow", true, "[top][, limit][, labels]", "show `top` queries slower than `limit` milliseconds, grouped by last `labels` labels" },
+ { "TrailingDataRule", true, "", "Matches if the query has trailing data" },
{ "truncateTC", true, "bool", "if set (defaults to no starting with dnsdist 1.2.0) truncate TC=1 answers so they are actually empty. Fixes an issue for PowerDNS Authoritative Server 2.9.22. Note: turning this on breaks compatibility with RFC 6891." },
{ "unregisterDynBPFFilter", true, "DynBPFFilter", "unregister this dynamic BPF filter" },
{ "webserver", true, "address:port, password [, apiKey [, customHeaders ]])", "launch a webserver with stats on that address with that password" },
class DropAction : public DNSAction
{
public:
- DNSAction::Action operator()(DNSQuestion* dq, string* ruleresult) const override
+ DNSAction::Action operator()(DNSQuestion* dq, std::string* ruleresult) const override
{
return Action::Drop;
}
- string toString() const override
+ std::string toString() const override
{
return "drop";
}
class AllowAction : public DNSAction
{
public:
- DNSAction::Action operator()(DNSQuestion* dq, string* ruleresult) const override
+ DNSAction::Action operator()(DNSQuestion* dq, std::string* ruleresult) const override
{
return Action::Allow;
}
- string toString() const override
+ std::string toString() const override
{
return "allow";
}
class NoneAction : public DNSAction
{
public:
- DNSAction::Action operator()(DNSQuestion* dq, string* ruleresult) const override
+ DNSAction::Action operator()(DNSQuestion* dq, std::string* ruleresult) const override
{
return Action::None;
}
- string toString() const override
+ std::string toString() const override
{
return "no op";
}
public:
QPSAction(int limit) : d_qps(limit, limit)
{}
- DNSAction::Action operator()(DNSQuestion* dq, string* ruleresult) const override
+ DNSAction::Action operator()(DNSQuestion* dq, std::string* ruleresult) const override
{
if(d_qps.check())
return Action::None;
else
return Action::Drop;
}
- string toString() const override
+ std::string toString() const override
{
return "qps limit to "+std::to_string(d_qps.getRate());
}
public:
DelayAction(int msec) : d_msec(msec)
{}
- DNSAction::Action operator()(DNSQuestion* dq, string* ruleresult) const override
+ DNSAction::Action operator()(DNSQuestion* dq, std::string* ruleresult) const override
{
*ruleresult=std::to_string(d_msec);
return Action::Delay;
}
- string toString() const override
+ std::string toString() const override
{
return "delay by "+std::to_string(d_msec)+ " msec";
}
public:
TeeAction(const ComboAddress& ca, bool addECS=false);
~TeeAction() override;
- DNSAction::Action operator()(DNSQuestion* dq, string* ruleresult) const override;
- string toString() const override;
- std::map<string, double> getStats() const override;
+ DNSAction::Action operator()(DNSQuestion* dq, std::string* ruleresult) const override;
+ std::string toString() const override;
+ std::map<std::string, double> getStats() const override;
private:
ComboAddress d_remote;
d_worker.join();
}
-DNSAction::Action TeeAction::operator()(DNSQuestion* dq, string* ruleresult) const
+DNSAction::Action TeeAction::operator()(DNSQuestion* dq, std::string* ruleresult) const
{
if(dq->tcp) {
d_tcpdrops++;
query.reserve(dq->size);
query.assign((char*) dq->dh, len);
- string newECSOption;
+ std::string newECSOption;
generateECSOption(dq->ecsSet ? dq->ecs.getNetwork() : *dq->remote, newECSOption, dq->ecsSet ? dq->ecs.getBits() : dq->ecsPrefixLength);
if (!handleEDNSClientSubnet(const_cast<char*>(query.c_str()), query.capacity(), dq->qname->wirelength(), &len, &ednsAdded, &ecsAdded, dq->ecsOverride, newECSOption, g_preserveTrailingData)) {
return DNSAction::Action::None;
}
-string TeeAction::toString() const
+std::string TeeAction::toString() const
{
return "tee to "+d_remote.toStringWithPort();
}
-std::map<string,double> TeeAction::getStats() const
+std::map<std::string,double> TeeAction::getStats() const
{
return {{"queries", d_queries},
{"responses", d_responses},
{
public:
PoolAction(const std::string& pool) : d_pool(pool) {}
- DNSAction::Action operator()(DNSQuestion* dq, string* ruleresult) const override
+ DNSAction::Action operator()(DNSQuestion* dq, std::string* ruleresult) const override
{
*ruleresult=d_pool;
return Action::Pool;
}
- string toString() const override
+ std::string toString() const override
{
return "to pool "+d_pool;
}
private:
- string d_pool;
+ std::string d_pool;
};
{
public:
QPSPoolAction(unsigned int limit, const std::string& pool) : d_qps(limit, limit), d_pool(pool) {}
- DNSAction::Action operator()(DNSQuestion* dq, string* ruleresult) const override
+ DNSAction::Action operator()(DNSQuestion* dq, std::string* ruleresult) const override
{
if(d_qps.check()) {
*ruleresult=d_pool;
else
return Action::None;
}
- string toString() const override
+ std::string toString() const override
{
return "max " +std::to_string(d_qps.getRate())+" to pool "+d_pool;
}
private:
QPSLimiter d_qps;
- string d_pool;
+ std::string d_pool;
};
class RCodeAction : public DNSAction
{
public:
RCodeAction(uint8_t rcode) : d_rcode(rcode) {}
- DNSAction::Action operator()(DNSQuestion* dq, string* ruleresult) const override
+ DNSAction::Action operator()(DNSQuestion* dq, std::string* ruleresult) const override
{
dq->dh->rcode = d_rcode;
dq->dh->qr = true; // for good measure
return Action::HeaderModify;
}
- string toString() const override
+ std::string toString() const override
{
return "set rcode "+std::to_string(d_rcode);
}
{
public:
ERCodeAction(uint8_t rcode) : d_rcode(rcode) {}
- DNSAction::Action operator()(DNSQuestion* dq, string* ruleresult) const override
+ DNSAction::Action operator()(DNSQuestion* dq, std::string* ruleresult) const override
{
dq->dh->rcode = (d_rcode & 0xF);
dq->ednsRCode = ((d_rcode & 0xFFF0) >> 4);
dq->dh->qr = true; // for good measure
return Action::HeaderModify;
}
- string toString() const override
+ std::string toString() const override
{
return "set ercode "+ERCode::to_s(d_rcode);
}
class TCAction : public DNSAction
{
public:
- DNSAction::Action operator()(DNSQuestion* dq, string* ruleresult) const override
+ DNSAction::Action operator()(DNSQuestion* dq, std::string* ruleresult) const override
{
return Action::Truncate;
}
- string toString() const override
+ std::string toString() const override
{
return "tc=1 answer";
}
};
-DNSAction::Action LuaAction::operator()(DNSQuestion* dq, string* ruleresult) const
+DNSAction::Action LuaAction::operator()(DNSQuestion* dq, std::string* ruleresult) const
{
std::lock_guard<std::mutex> lock(g_luamutex);
try {
auto ret = d_func(dq);
if (ruleresult) {
- if (boost::optional<string> rule = std::get<1>(ret)) {
+ if (boost::optional<std::string> rule = std::get<1>(ret)) {
*ruleresult = *rule;
}
else {
return DNSAction::Action::ServFail;
}
-DNSResponseAction::Action LuaResponseAction::operator()(DNSResponse* dr, string* ruleresult) const
+DNSResponseAction::Action LuaResponseAction::operator()(DNSResponse* dr, std::string* ruleresult) const
{
std::lock_guard<std::mutex> lock(g_luamutex);
try {
auto ret = d_func(dr);
if(ruleresult) {
- if (boost::optional<string> rule = std::get<1>(ret)) {
+ if (boost::optional<std::string> rule = std::get<1>(ret)) {
*ruleresult = *rule;
}
else {
return DNSResponseAction::Action::ServFail;
}
-DNSAction::Action SpoofAction::operator()(DNSQuestion* dq, string* ruleresult) const
+DNSAction::Action SpoofAction::operator()(DNSQuestion* dq, std::string* ruleresult) const
{
uint16_t qtype = dq->qtype;
// do we even have a response?
dq->dh->arcount = 0; // for now, forget about your EDNS, we're marching over it
if(qtype == QType::CNAME) {
- string wireData = d_cname.toDNSString(); // Note! This doesn't do compression!
+ std::string wireData = d_cname.toDNSString(); // Note! This doesn't do compression!
const unsigned char recordstart[]={0xc0, 0x0c, // compressed name
0, (unsigned char) qtype,
0, QClass::IN, // IN
public:
MacAddrAction(uint16_t code) : d_code(code)
{}
- DNSAction::Action operator()(DNSQuestion* dq, string* ruleresult) const override
+ DNSAction::Action operator()(DNSQuestion* dq, std::string* ruleresult) const override
{
if(dq->dh->arcount)
return Action::None;
- string mac = getMACAddress(*dq->remote);
+ std::string mac = getMACAddress(*dq->remote);
if(mac.empty())
return Action::None;
- string optRData;
+ std::string optRData;
generateEDNSOption(d_code, mac, optRData);
- string res;
+ std::string res;
generateOptRR(optRData, res, g_EdnsUDPPayloadSize, 0, false);
if ((dq->size - dq->len) < res.length())
return Action::None;
}
- string toString() const override
+ std::string toString() const override
{
return "add EDNS MAC (code="+std::to_string(d_code)+")";
}
class NoRecurseAction : public DNSAction
{
public:
- DNSAction::Action operator()(DNSQuestion* dq, string* ruleresult) const override
+ DNSAction::Action operator()(DNSQuestion* dq, std::string* ruleresult) const override
{
dq->dh->rd = false;
return Action::None;
}
- string toString() const override
+ std::string toString() const override
{
return "set rd=0";
}
else
d_fp = fopen(str.c_str(), "w");
if(!d_fp)
- throw std::runtime_error("Unable to open file '"+str+"' for logging: "+string(strerror(errno)));
+ throw std::runtime_error("Unable to open file '"+str+"' for logging: "+std::string(strerror(errno)));
if(!buffered)
setbuf(d_fp, 0);
}
if(d_fp)
fclose(d_fp);
}
- DNSAction::Action operator()(DNSQuestion* dq, string* ruleresult) const override
+ DNSAction::Action operator()(DNSQuestion* dq, std::string* ruleresult) const override
{
if(!d_fp) {
vinfolog("Packet from %s for %s %s with id %d", dq->remote->toStringWithPort(), dq->qname->toString(), QType(dq->qtype).getName(), dq->dh->id);
}
else {
if(d_binary) {
- string out = dq->qname->toDNSString();
+ std::string out = dq->qname->toDNSString();
fwrite(out.c_str(), 1, out.size(), d_fp);
fwrite((void*)&dq->qtype, 1, 2, d_fp);
}
}
return Action::None;
}
- string toString() const override
+ std::string toString() const override
{
if (!d_fname.empty()) {
return "log to " + d_fname;
return "log";
}
private:
- string d_fname;
+ std::string d_fname;
FILE* d_fp{0};
bool d_binary{true};
};
class DisableValidationAction : public DNSAction
{
public:
- DNSAction::Action operator()(DNSQuestion* dq, string* ruleresult) const override
+ DNSAction::Action operator()(DNSQuestion* dq, std::string* ruleresult) const override
{
dq->dh->cd = true;
return Action::None;
}
- string toString() const override
+ std::string toString() const override
{
return "set cd=1";
}
class SkipCacheAction : public DNSAction
{
public:
- DNSAction::Action operator()(DNSQuestion* dq, string* ruleresult) const override
+ DNSAction::Action operator()(DNSQuestion* dq, std::string* ruleresult) const override
{
dq->skipCache = true;
return Action::None;
}
- string toString() const override
+ std::string toString() const override
{
return "skip cache";
}
public:
TempFailureCacheTTLAction(uint32_t ttl) : d_ttl(ttl)
{}
- TempFailureCacheTTLAction::Action operator()(DNSQuestion* dq, string* ruleresult) const override
+ TempFailureCacheTTLAction::Action operator()(DNSQuestion* dq, std::string* ruleresult) const override
{
dq->tempFailureTTL = d_ttl;
return Action::None;
}
- string toString() const override
+ std::string toString() const override
{
return "set tempfailure cache ttl to "+std::to_string(d_ttl);
}
ECSPrefixLengthAction(uint16_t v4Length, uint16_t v6Length) : d_v4PrefixLength(v4Length), d_v6PrefixLength(v6Length)
{
}
- DNSAction::Action operator()(DNSQuestion* dq, string* ruleresult) const override
+ DNSAction::Action operator()(DNSQuestion* dq, std::string* ruleresult) const override
{
dq->ecsPrefixLength = dq->remote->sin4.sin_family == AF_INET ? d_v4PrefixLength : d_v6PrefixLength;
return Action::None;
}
- string toString() const override
+ std::string toString() const override
{
return "set ECS prefix length to " + std::to_string(d_v4PrefixLength) + "/" + std::to_string(d_v6PrefixLength);
}
ECSOverrideAction(bool ecsOverride) : d_ecsOverride(ecsOverride)
{
}
- DNSAction::Action operator()(DNSQuestion* dq, string* ruleresult) const override
+ DNSAction::Action operator()(DNSQuestion* dq, std::string* ruleresult) const override
{
dq->ecsOverride = d_ecsOverride;
return Action::None;
}
- string toString() const override
+ std::string toString() const override
{
return "set ECS override to " + std::to_string(d_ecsOverride);
}
class DisableECSAction : public DNSAction
{
public:
- DNSAction::Action operator()(DNSQuestion* dq, string* ruleresult) const override
+ DNSAction::Action operator()(DNSQuestion* dq, std::string* ruleresult) const override
{
dq->useECS = false;
return Action::None;
}
- string toString() const override
+ std::string toString() const override
{
return "disable ECS";
}
{
}
- DNSAction::Action operator()(DNSQuestion* dq, string* ruleresult) const override
+ DNSAction::Action operator()(DNSQuestion* dq, std::string* ruleresult) const override
{
dq->ecsSet = true;
return Action::None;
}
- string toString() const override
+ std::string toString() const override
{
- string result = "set ECS to " + d_v4.toString();
+ std::string result = "set ECS to " + d_v4.toString();
if (d_hasV6) {
result += " / " + d_v6.toString();
}
DnstapLogAction(const std::string& identity, std::shared_ptr<RemoteLoggerInterface>& logger, boost::optional<std::function<void(DNSQuestion*, DnstapMessage*)> > alterFunc): d_identity(identity), d_logger(logger), d_alterFunc(alterFunc)
{
}
- DNSAction::Action operator()(DNSQuestion* dq, string* ruleresult) const override
+ DNSAction::Action operator()(DNSQuestion* dq, std::string* ruleresult) const override
{
#ifdef HAVE_PROTOBUF
DnstapMessage message(d_identity, dq->remote, dq->local, dq->tcp, reinterpret_cast<const char*>(dq->dh), dq->len, dq->queryTime, nullptr);
#endif /* HAVE_PROTOBUF */
return Action::None;
}
- string toString() const override
+ std::string toString() const override
{
return "remote log as dnstap to " + (d_logger ? d_logger->toString() : "");
}
RemoteLogAction(std::shared_ptr<RemoteLoggerInterface>& logger, boost::optional<std::function<void(DNSQuestion*, DNSDistProtoBufMessage*)> > alterFunc, const std::string& serverID, const std::string& ipEncryptKey): d_logger(logger), d_alterFunc(alterFunc), d_serverID(serverID), d_ipEncryptKey(ipEncryptKey)
{
}
- DNSAction::Action operator()(DNSQuestion* dq, string* ruleresult) const override
+ DNSAction::Action operator()(DNSQuestion* dq, std::string* ruleresult) const override
{
#ifdef HAVE_PROTOBUF
if (!dq->uniqueId) {
#endif /* HAVE_PROTOBUF */
return Action::None;
}
- string toString() const override
+ std::string toString() const override
{
return "remote log to " + (d_logger ? d_logger->toString() : "");
}
SNMPTrapAction(const std::string& reason): d_reason(reason)
{
}
- DNSAction::Action operator()(DNSQuestion* dq, string* ruleresult) const override
+ DNSAction::Action operator()(DNSQuestion* dq, std::string* ruleresult) const override
{
if (g_snmpAgent && g_snmpTrapsEnabled) {
g_snmpAgent->sendDNSTrap(*dq, d_reason);
return Action::None;
}
- string toString() const override
+ std::string toString() const override
{
return "send SNMP trap";
}
TagAction(const std::string& tag, const std::string& value): d_tag(tag), d_value(value)
{
}
- DNSAction::Action operator()(DNSQuestion* dq, string* ruleresult) const override
+ DNSAction::Action operator()(DNSQuestion* dq, std::string* ruleresult) const override
{
if (!dq->qTag) {
dq->qTag = std::make_shared<QTag>();
return Action::None;
}
- string toString() const override
+ std::string toString() const override
{
return "set tag '" + d_tag + "' to value '" + d_value + "'";
}
DnstapLogResponseAction(const std::string& identity, std::shared_ptr<RemoteLoggerInterface>& logger, boost::optional<std::function<void(DNSResponse*, DnstapMessage*)> > alterFunc): d_identity(identity), d_logger(logger), d_alterFunc(alterFunc)
{
}
- DNSResponseAction::Action operator()(DNSResponse* dr, string* ruleresult) const override
+ DNSResponseAction::Action operator()(DNSResponse* dr, std::string* ruleresult) const override
{
#ifdef HAVE_PROTOBUF
struct timespec now;
#endif /* HAVE_PROTOBUF */
return Action::None;
}
- string toString() const override
+ std::string toString() const override
{
return "log response as dnstap to " + (d_logger ? d_logger->toString() : "");
}
RemoteLogResponseAction(std::shared_ptr<RemoteLoggerInterface>& logger, boost::optional<std::function<void(DNSResponse*, DNSDistProtoBufMessage*)> > alterFunc, const std::string& serverID, const std::string& ipEncryptKey, bool includeCNAME): d_logger(logger), d_alterFunc(alterFunc), d_serverID(serverID), d_ipEncryptKey(ipEncryptKey), d_includeCNAME(includeCNAME)
{
}
- DNSResponseAction::Action operator()(DNSResponse* dr, string* ruleresult) const override
+ DNSResponseAction::Action operator()(DNSResponse* dr, std::string* ruleresult) const override
{
#ifdef HAVE_PROTOBUF
if (!dr->uniqueId) {
#endif /* HAVE_PROTOBUF */
return Action::None;
}
- string toString() const override
+ std::string toString() const override
{
return "remote log response to " + (d_logger ? d_logger->toString() : "");
}
class DropResponseAction : public DNSResponseAction
{
public:
- DNSResponseAction::Action operator()(DNSResponse* dr, string* ruleresult) const override
+ DNSResponseAction::Action operator()(DNSResponse* dr, std::string* ruleresult) const override
{
return Action::Drop;
}
- string toString() const override
+ std::string toString() const override
{
return "drop";
}
class AllowResponseAction : public DNSResponseAction
{
public:
- DNSResponseAction::Action operator()(DNSResponse* dr, string* ruleresult) const override
+ DNSResponseAction::Action operator()(DNSResponse* dr, std::string* ruleresult) const override
{
return Action::Allow;
}
- string toString() const override
+ std::string toString() const override
{
return "allow";
}
public:
DelayResponseAction(int msec) : d_msec(msec)
{}
- DNSResponseAction::Action operator()(DNSResponse* dr, string* ruleresult) const override
+ DNSResponseAction::Action operator()(DNSResponse* dr, std::string* ruleresult) const override
{
*ruleresult=std::to_string(d_msec);
return Action::Delay;
}
- string toString() const override
+ std::string toString() const override
{
return "delay by "+std::to_string(d_msec)+ " msec";
}
SNMPTrapResponseAction(const std::string& reason): d_reason(reason)
{
}
- DNSResponseAction::Action operator()(DNSResponse* dr, string* ruleresult) const override
+ DNSResponseAction::Action operator()(DNSResponse* dr, std::string* ruleresult) const override
{
if (g_snmpAgent && g_snmpTrapsEnabled) {
g_snmpAgent->sendDNSTrap(*dr, d_reason);
return Action::None;
}
- string toString() const override
+ std::string toString() const override
{
return "send SNMP trap";
}
TagResponseAction(const std::string& tag, const std::string& value): d_tag(tag), d_value(value)
{
}
- DNSResponseAction::Action operator()(DNSResponse* dr, string* ruleresult) const override
+ DNSResponseAction::Action operator()(DNSResponse* dr, std::string* ruleresult) const override
{
if (!dr->qTag) {
dr->qTag = std::make_shared<QTag>();
return Action::None;
}
- string toString() const override
+ std::string toString() const override
{
return "set tag '" + d_tag + "' to value '" + d_value + "'";
}
return std::shared_ptr<DNSAction>(new MacAddrAction(code));
});
- g_lua.writeFunction("PoolAction", [](const string& a) {
+ g_lua.writeFunction("PoolAction", [](const std::string& a) {
return std::shared_ptr<DNSAction>(new PoolAction(a));
});
return std::shared_ptr<DNSAction>(new QPSAction(limit));
});
- g_lua.writeFunction("QPSPoolAction", [](int limit, const string& a) {
+ g_lua.writeFunction("QPSPoolAction", [](int limit, const std::string& a) {
return std::shared_ptr<DNSAction>(new QPSPoolAction(limit, a));
});
- g_lua.writeFunction("SpoofAction", [](boost::variant<string,vector<pair<int, string>>> inp, boost::optional<string> b ) {
+ g_lua.writeFunction("SpoofAction", [](boost::variant<std::string,vector<pair<int, std::string>>> inp, boost::optional<std::string> b ) {
vector<ComboAddress> addrs;
- if(auto s = boost::get<string>(&inp))
+ if(auto s = boost::get<std::string>(&inp))
addrs.push_back(ComboAddress(*s));
else {
- const auto& v = boost::get<vector<pair<int,string>>>(inp);
+ const auto& v = boost::get<vector<pair<int,std::string>>>(inp);
for(const auto& a: v)
addrs.push_back(ComboAddress(a.second));
}
return std::shared_ptr<DNSAction>(new SpoofAction(addrs));
});
- g_lua.writeFunction("SpoofCNAMEAction", [](const string& a) {
+ g_lua.writeFunction("SpoofCNAMEAction", [](const std::string& a) {
return std::shared_ptr<DNSAction>(new SpoofAction(a));
});
return true;
});
+ g_lua.registerFunction<std::string(DNSQuestion::*)()>("getServerNameIndication", [](const DNSQuestion& dq) {
+ return dq.sni;
+ });
+
g_lua.registerFunction<void(DNSQuestion::*)(std::string)>("sendTrap", [](const DNSQuestion& dq, boost::optional<std::string> reason) {
#ifdef HAVE_NET_SNMP
if (g_snmpAgent && g_snmpTrapsEnabled) {
});
#endif
+ g_lua.writeFunction("SNIRule", [](const std::string& name) {
+ return std::shared_ptr<DNSRule>(new SNIRule(name));
+ });
+
g_lua.writeFunction("SuffixMatchNodeRule", [](const SuffixMatchNode& smn, boost::optional<bool> quiet) {
return std::shared_ptr<DNSRule>(new SuffixMatchNodeRule(smn, quiet ? *quiet : false));
});
{ "VERSION2", DNSCryptExchangeVersion::VERSION2 },
});
#endif
-
- g_lua.writeVariable("EDNSOptionCode", std::unordered_map<string, uint8_t>{
- { "NSID", EDNSOptionCode::NSID },
- { "DAU", EDNSOptionCode::DAU },
- { "DHU", EDNSOptionCode::DHU },
- { "N3U", EDNSOptionCode::N3U },
- { "ECS", EDNSOptionCode::ECS },
- { "EXPIRE", EDNSOptionCode::EXPIRE },
- { "COOKIE", EDNSOptionCode::COOKIE },
- { "TCPKEEPALIVE", EDNSOptionCode::TCPKEEPALIVE },
- { "PADDING", EDNSOptionCode::PADDING },
- { "CHAIN", EDNSOptionCode::CHAIN }
- });
}
catch(const FDMultiplexerException& e) {
vinfolog("Got an exception when trying to remove a pending IO operation on the socket to the %s backend: %s", d_ds->getName(), e.what());
}
+ catch(const std::runtime_error& e) {
+ /* might be thrown by getHandle() */
+ vinfolog("Got an exception when trying to remove a pending IO operation on the socket to the %s backend: %s", d_ds->getName(), e.what());
+ }
}
}
DNSName qname(query, state->d_querySize, sizeof(dnsheader), false, &qtype, &qclass, &consumed);
DNSQuestion dq(&qname, qtype, qclass, consumed, &state->d_ids.origDest, &state->d_ci.remote, reinterpret_cast<dnsheader*>(query), state->d_buffer.size(), state->d_querySize, true, &queryRealTime);
dq.dnsCryptQuery = std::move(dnsCryptQuery);
+ dq.sni = state->d_handler.getServerNameIndication();
state->d_isXFR = (dq.qtype == QType::AXFR || dq.qtype == QType::IXFR);
if (state->d_isXFR) {
}
#endif /* MSG_FASTOPEN */
- size_t sent = sendMsgWithTimeout(fd, reinterpret_cast<const char *>(&state->d_buffer.at(state->d_currentPos)), state->d_buffer.size() - state->d_currentPos, 0, &state->d_ds->remote, &state->d_ds->sourceAddr, state->d_ds->sourceItf, 0, socketFlags);
+ size_t sent = sendMsgWithOptions(fd, reinterpret_cast<const char *>(&state->d_buffer.at(state->d_currentPos)), state->d_buffer.size() - state->d_currentPos, &state->d_ds->remote, &state->d_ds->sourceAddr, state->d_ds->sourceItf, socketFlags);
if (sent == state->d_buffer.size()) {
/* request sent ! */
state->d_downstreamConnection->incQueries();
output << "\n";
}
+ // Latency histogram buckets
+ output << "# HELP dnsdist_latency Histogram of responses by latency\n";
+ output << "# TYPE dnsdist_latency histogram\n";
+ uint64_t latency_amounts = g_stats.latency0_1;
+ output << "dnsdist_latency_bucket{le=\"1\"} " << latency_amounts << "\n";
+ latency_amounts += g_stats.latency1_10;
+ output << "dnsdist_latency_bucket{le=\"10\"} " << latency_amounts << "\n";
+ latency_amounts += g_stats.latency10_50;
+ output << "dnsdist_latency_bucket{le=\"50\"} " << latency_amounts << "\n";
+ latency_amounts += g_stats.latency50_100;
+ output << "dnsdist_latency_bucket{le=\"100\"} " << latency_amounts << "\n";
+ latency_amounts += g_stats.latency100_1000;
+ output << "dnsdist_latency_bucket{le=\"1000\"} " << latency_amounts << "\n";
+ latency_amounts += g_stats.latencySlow; // Should be the same as latency_count
+ output << "dnsdist_latency_bucket{le=\"+Inf\"} " << latency_amounts << "\n";
+
auto states = g_dstates.getLocal();
const string statesbase = "dnsdist_server_";
else if(udiff < 100000) ++g_stats.latency50_100;
else if(udiff < 1000000) ++g_stats.latency100_1000;
else ++g_stats.latencySlow;
+ g_stats.latencySum += udiff / 1000;
auto doAvg = [](double& var, double n, double weight) {
var = (weight -1) * var/weight + n/weight;
unsigned int got = msgVec[msgIdx].msg_len;
const ComboAddress& remote = recvData[msgIdx].remote;
- if (got < 0 || static_cast<size_t>(got) < sizeof(struct dnsheader)) {
+ if (static_cast<size_t>(got) < sizeof(struct dnsheader)) {
++g_stats.nonCompliantQueries;
continue;
}
errlog("Fatal pdns error: %s", ae.reason);
_exit(EXIT_FAILURE);
}
+
+uint64_t getLatencyCount(const std::string&)
+{
+ return g_stats.responses + g_stats.selfAnswered + g_stats.cacheHits;
+}
#endif
Netmask ecs;
boost::optional<Netmask> subnet;
+ std::string sni; /* Server Name Indication, if any (DoT or DoH) */
const DNSName* qname{nullptr};
const ComboAddress* local{nullptr};
const ComboAddress* remote{nullptr};
extern vector<pair<struct timeval, std::string> > g_confDelta;
+extern uint64_t getLatencyCount(const std::string&);
+
struct DNSDistStats
{
using stat_t=std::atomic<uint64_t>; // aww yiss ;-)
stat_t noPolicy{0};
stat_t cacheHits{0};
stat_t cacheMisses{0};
- stat_t latency0_1{0}, latency1_10{0}, latency10_50{0}, latency50_100{0}, latency100_1000{0}, latencySlow{0};
+ stat_t latency0_1{0}, latency1_10{0}, latency10_50{0}, latency50_100{0}, latency100_1000{0}, latencySlow{0}, latencySum{0};
stat_t securityStatus{0};
double latencyAvg100{0}, latencyAvg1000{0}, latencyAvg10000{0}, latencyAvg1000000{0};
{"fd-usage", getOpenFileDescriptors},
{"dyn-blocked", &dynBlocked},
{"dyn-block-nmg-size", [](const std::string&) { return g_dynblockNMG.getLocal()->size(); }},
- {"security-status", &securityStatus}
+ {"security-status", &securityStatus},
+ // Latency histogram
+ {"latency-sum", &latencySum},
+ {"latency-count", getLatencyCount},
};
};
{ "dyn-blocked", MetricDefinition(PrometheusMetricType::counter, "Number of queries dropped because of a dynamic block")},
{ "dyn-block-nmg-size", MetricDefinition(PrometheusMetricType::gauge, "Number of dynamic blocks entries") },
{ "security-status", MetricDefinition(PrometheusMetricType::gauge, "Security status of this software. 0=unknown, 1=OK, 2=upgrade recommended, 3=upgrade mandatory") },
+ // Latency histogram
+ { "latency-sum", MetricDefinition(PrometheusMetricType::counter, "Total response time in milliseconds")},
+ { "latency-count", MetricDefinition(PrometheusMetricType::counter, "Number of queries contributing to response time histogram")},
};
};
};
#endif
+class SNIRule : public DNSRule
+{
+public:
+ SNIRule(const std::string& name) : d_sni(name)
+ {
+ }
+ bool matches(const DNSQuestion* dq) const override
+ {
+ return dq->sni == d_sni;
+ }
+ string toString() const override
+ {
+ return "SNI == " + d_sni;
+ }
+private:
+ std::string d_sni;
+};
+
class SuffixMatchNodeRule : public DNSRule
{
public:
Changelog
=========
+.. changelog::
+ :version: 1.4.0-beta1
+ :released: 6th of June 2019
+
+ .. change::
+ :tags: Bug Fixes, DoH
+ :pullreq: 7814
+ :tickets: 7810
+
+ DoH: Don't let 'self' dangling while parsing the request's qname, this could lead to a crash
+
+ .. change::
+ :tags: Bug Fixes
+ :pullreq: 7823
+
+ Fix minor issues reported by Coverity
+
+ .. change::
+ :tags: New Features, DoT, DoH
+ :pullreq: 7825
+ :tickets: 7210
+
+ Implement SNIRule for DoT and DoH
+
+ .. change::
+ :tags: Bug Fixes
+ :pullreq: 7833
+
+ Remove second, incomplete copy of lua EDNSOptionCode table
+
+ .. change::
+ :tags: Improvements, Prometheus
+ :pullreq: 7853
+ :tickets: 6088
+
+ Support Prometheus latency histograms (Marlin Cremers)
+
.. changelog::
:version: 1.4.0-alpha2
:released: 26th of April 2019
setCD=BOOL, -- Set the CD (Checking Disabled) flag in the health-check query, default: false
maxCheckFailures=NUM, -- Allow NUM check failures before declaring the backend down, default: 1
checkInterval=NUM -- The time in seconds between health checks
- mustResolve=BOOL, -- Set to true when the health check MUST return a NOERROR RCODE and an answer
+ mustResolve=BOOL, -- Set to true when the health check MUST return a RCODE different from NXDomain, ServFail and Refused. Default is false, meaning that every RCODE except ServFail is considered valid
useClientSubnet=BOOL, -- Add the client's IP address in the EDNS Client Subnet option when forwarding the query to this backend
source=STRING, -- The source address or interface to use for queries to this backend, by default this is left to the kernel's address selection
-- The following formats are supported:
PacketCache
~~~~~~~~~~~
-A Pool can have a packet cache to answer queries directly in stead of going to the backend.
+A Pool can have a packet cache to answer queries directly instead of going to the backend.
See :doc:`../guides/cache` for a how to.
.. function:: newPacketCache(maxEntries[, maxTTL=86400[, minTTL=0[, temporaryFailureTTL=60[, staleTTL=60[, dontAge=false[, numberOfShards=1[, deferrableInsertLock=true[, maxNegativeTTL=3600[, parseECS=false]]]]]]]) -> PacketCache
Return a string describing the rules and range exclusions of this DynBlockRulesGroup.
+SuffixMatchNode
+~~~~~~~~~~~~~~~
+
+A SuffixMatchNode can be used to quickly check whether a given name belongs to a set or not. This is achieved
+using an efficient tree structure based on DNS labels, making lookups cheap.
+Be careful that Suffix Node matching will match for any sub-domain, regardless of the depth, under the name added to the set. For example,
+if 'example.com.' is added to the set, 'www.example.com.' and 'sub.www.example.com.' will match as well.
+If you are looking for exact name matching, your might want to consider using a :class:`DNSNameSet` instead.
+
+.. function:: newSuffixMatchNode()
+
+ Creates a new :class:`SuffixMatchNode`.
+
+.. class:: SuffixMatchNode
+
+ Represent a set of DNS suffixes for quick matching.
+
+ .. method:: SuffixMatchNode:add(name)
+
+ Add a suffix to the current set.
+
+ :param DNSName name: The suffix to add to the set.
+
+ .. method:: SuffixMatchNode:check(name) -> bool
+
+ Return true if the given name is a sub-domain of one of those in the set, and false otherwise.
+
+ :param DNSName name: The name to test against the set.
+
Other functions
---------------
:returns: A table of EDNSOptionView objects, indexed on the ECS Option code
+ .. method:: DNSQuestion:getServerNameIndication() -> string
+
+ .. versionadded:: 1.4.0
+
+ Return the TLS Server Name Indication (SNI) value sent by the client over DoT or DoH, if any. See :func:`SNIRule`
+ for more information, especially about the availability of SNI over DoH.
+
+ :returns: A string containing the TLS SNI value, if any
+
.. method:: DNSQuestion:getTag(key) -> string
.. versionadded:: 1.2.0
Move the last self answered response rule to the first position.
-Function for pool related rules
-
-.. function:: PoolAvailableRule(poolname)
-
- .. versionadded:: 1.3.3
-
- Check whether a pool has any servers available to handle queries
-
- :param string poolname: Pool to check
-
.. _RulesIntro:
Matching Packets (Selectors)
:param int rcode: The RCODE to match on
+.. function:: HTTPHeaderRule(name, regex)
+ .. versionadded:: 1.4.0
+
+ Matches DNS over HTTPS queries with a HTTP header ``name`` whose content matches the regular expression ``regex``.
+
+ :param str name: The case-insensitive name of the HTTP header to match on
+ :param str regex: A regular expression to match the content of the specified header
+
+.. function:: HTTPPathRule(path)
+ .. versionadded:: 1.4.0
+
+ Matches DNS over HTTPS queries with a HTTP path of ``path``. For example, if the query has been sent to the https://192.0.2.1:443/PowerDNS?dns=... URL, the path would be '/PowerDNS'.
+
+ :param str path: The exact HTTP path to match on
+
.. function:: MaxQPSIPRule(qps[, v4Mask[, v6Mask[, burst[, expiration[, cleanupDelay[, scanFraction]]]]]])
.. versionchanged:: 1.3.1
Added the optional parameters ``expiration``, ``cleanupDelay`` and ``scanFraction``.
.. function:: QClassRule(qclass)
Matches queries with the specified ``qclass``.
- ``class`` can be specified as an integer or as one of the built-in :ref:`DNSQClass`.
+ ``class`` can be specified as an integer or as one of the built-in :ref:`DNSClass`.
:param int qclass: The Query Class to match on
:param string qname: Qname to match
.. function:: QNameSetRule(set)
- Matches if the set contains exact qname.
+
+ .. versionadded:: 1.4.0
+
+ Matches if the set contains exact qname.
To match subdomain names, see :func:`SuffixMatchNodeRule`.
:param str regex: The regular expression to match the QNAME.
+.. function:: SNIRule(name)
+ .. versionadded:: 1.4.0
+
+ Matches against the TLS Server Name Indication value sent by the client, if any. Only makes
+ sense for DoT or DoH, and for that last one matching on the HTTP Host header using :func:`HTTPHeaderRule`
+ might provide more consistent results.
+ As of the version 2.3.0-beta of h2o, it is unfortunately not possible to extract the SNI value from DoH
+ connections, and it is therefore necessary to use the HTTP Host header until version 2.3.0 is released.
+
+ :param str name: The exact SNI name to match.
+
.. function:: SuffixMatchNodeRule(smn[, quiet])
Matches based on a group of domain suffixes for rapid testing of membership.
Matches if the query has trailing data.
+.. function:: PoolAvailableRule(poolname)
+
+ .. versionadded:: 1.3.3
+
+ Check whether a pool has any servers available to handle queries
+
+ .. code-block:: Lua
+
+ --- Send queries to default pool when servers are available
+ addAction(PoolAvailableRule(""), PoolAction(""))
+ --- Send queries to fallback pool if not
+ addAction(AllRule(), PoolAction("fallback"))
+
+ :param string poolname: Pool to check
+
Combining Rules
~~~~~~~~~~~~~~~
.. function:: DelayAction(milliseconds)
Delay the response by the specified amount of milliseconds (UDP-only).
- Subsequent rules are processed after this rule.
+ Subsequent rules are processed after this action.
:param int milliseconds: The amount of milliseconds to delay the response
.. function:: DelayResponseAction(milliseconds)
Delay the response by the specified amount of milliseconds (UDP-only).
- Subsequent rules are processed after this rule.
+ Subsequent rules are processed after this action.
:param int milliseconds: The amount of milliseconds to delay the response
.. function:: DisableECSAction()
Disable the sending of ECS to the backend.
- Subsequent rules are processed after this rule.
+ Subsequent rules are processed after this action.
.. function:: DisableValidationAction()
.. function:: ECSOverrideAction(override)
Whether an existing EDNS Client Subnet value should be overridden (true) or not (false).
- Subsequent rules are processed after this rule.
+ Subsequent rules are processed after this action.
:param bool override: Whether or not to override ECS value
.. function:: ECSPrefixLengthAction(v4, v6)
Set the ECS prefix length.
- Subsequent rules are processed after this rule.
+ Subsequent rules are processed after this action.
:param int v4: The IPv4 netmask length
:param int v6: The IPv6 netmask length
When logging to a file, the ``binary`` optional parameter specifies whether we log in binary form (default) or in textual form.
The ``append`` optional parameter specifies whether we open the file for appending or truncate each time (default).
The ``buffered`` optional parameter specifies whether writes to the file are buffered (default) or not.
- Subsequent rules are processed after this rule.
+ Subsequent rules are processed after this action.
:param string filename: File to log to. Set to an empty string to log to the normal stdout log, this only works when ``-v`` is set on the command line.
:param bool binary: Do binary logging. Default true
Add the source MAC address to the query as EDNS0 option ``option``.
This action is currently only supported on Linux.
- Subsequent rules are processed after this rule.
+ Subsequent rules are processed after this action.
:param int option: The EDNS0 option number
.. function:: NoneAction()
Does nothing.
- Subsequent rules are processed after this rule.
+ Subsequent rules are processed after this action.
.. function:: NoRecurseAction()
Strip RD bit from the question, let it go through.
- Subsequent rules are processed after this rule.
+ Subsequent rules are processed after this action.
.. function:: PoolAction(poolname)
If both IPv4 and IPv6 masks are supplied the IPv4 one will be used for IPv4 clients
and the IPv6 one for IPv6 clients. Otherwise the first mask is used for both, and
can actually be an IPv6 mask.
- Subsequent rules are processed after this rule.
+ Subsequent rules are processed after this action.
:param string v4: The IPv4 netmask, for example "192.0.2.1/32"
:param string v6: The IPv6 netmask, if any
.. function:: SNMPTrapAction([message])
Send an SNMP trap, adding the optional ``message`` string as the query description.
- Subsequent rules are processed after this rule.
+ Subsequent rules are processed after this action.
:param string message: The message to include
.. function:: SNMPTrapResponseAction([message])
Send an SNMP trap, adding the optional ``message`` string as the query description.
- Subsequent rules are processed after this rule.
+ Subsequent rules are processed after this action.
:param string message: The message to include
------------
Number of queries answered in more than 1 second.
+latency-sum
+-----------
+Total response time of all queries combined in milliseconds since the start of dnsdist. Can be used to calculate the
+average response time over all queries.
+
+latency-count
+-------------
+Number of queries contributing to response time histogram
+
+latency-bucket
+--------------
+Number of queries contributing to response time histogram per latency bucket
+
latency0-1
----------
Number of queries answered in less than 1 ms.
dq.ednsAdded = du->ednsAdded;
dq.du = du;
queryId = ntohs(dh->id);
+#ifdef HAVE_H2O_SOCKET_GET_SSL_SERVER_NAME
+ h2o_socket_t* sock = du->req->conn->callbacks->get_socket(du->req->conn);
+ const char * sni = h2o_socket_get_ssl_server_name(sock);
+ if (sni != nullptr) {
+ dq.sni = sni;
+ }
+#endif /* HAVE_H2O_SOCKET_BET_SSL_SERVER_NAME */
std::shared_ptr<DownstreamState> ss{nullptr};
auto result = processQuery(dq, cs, holders, ss);
static void doh_dispatch_query(DOHServerConfig* dsc, h2o_handler_t* self, h2o_req_t* req, std::string&& query, const ComboAddress& local, const ComboAddress& remote)
{
try {
- auto du = std::unique_ptr<DOHUnit>(new DOHUnit);
- du->self = reinterpret_cast<DOHUnit**>(h2o_mem_alloc_shared(&req->pool, sizeof(*self), on_generator_dispose));
uint16_t qtype;
DNSName qname(query.c_str(), query.size(), sizeof(dnsheader), false, &qtype);
+
+ auto du = std::unique_ptr<DOHUnit>(new DOHUnit);
du->req = req;
- du->query = std::move(query);
du->dest = local;
du->remote = remote;
du->rsock = dsc->dohresponsepair[0];
+ du->query = std::move(query);
du->qtype = qtype;
+ du->self = reinterpret_cast<DOHUnit**>(h2o_mem_alloc_shared(&req->pool, sizeof(*self), on_generator_dispose));
auto ptr = du.release();
*(ptr->self) = ptr;
try {
OPENSSL_free(openssllocks);
}
-#else
-static void openssl_thread_setup()
-{
-}
-
-static void openssl_thread_cleanup()
-{
-}
-#endif /* (OPENSSL_VERSION_NUMBER < 0x1010000fL || defined LIBRESSL_VERSION_NUMBER) */
-
static std::atomic<uint64_t> s_users;
+#endif /* (OPENSSL_VERSION_NUMBER < 0x1010000fL || defined LIBRESSL_VERSION_NUMBER) */
void registerOpenSSLUser()
{
+#if (OPENSSL_VERSION_NUMBER < 0x1010000fL || defined LIBRESSL_VERSION_NUMBER)
if (s_users.fetch_add(1) == 0) {
SSL_load_error_strings();
OpenSSL_add_ssl_algorithms();
openssl_thread_setup();
}
+#endif
}
void unregisterOpenSSLUser()
{
+#if (OPENSSL_VERSION_NUMBER < 0x1010000fL || defined LIBRESSL_VERSION_NUMBER)
if (s_users.fetch_sub(1) == 1) {
ERR_free_strings();
CRYPTO_cleanup_all_ex_data();
openssl_thread_cleanup();
}
+#endif
}
#endif /* HAVE_LIBSSL */
PKG_CHECK_MODULES([LIBH2OEVLOOP], [libh2o-evloop], [
[HAVE_LIBH2OEVLOOP=1]
AC_DEFINE([HAVE_LIBH2OEVLOOP], [1], [Define to 1 if you have libh2o-evloop])
+ save_CFLAGS=$CFLAGS
+ save_LIBS=$LIBS
+ CFLAGS="$LIBH2OEVLOOP_CFLAGS $CFLAGS"
+ LIBS="$LIBH2OEVLOOP_LIBS $LIBS"
+ AC_CHECK_DECLS([h2o_socket_get_ssl_server_name], [
+ AC_DEFINE([HAVE_H2O_SOCKET_GET_SSL_SERVER_NAME], [1], [define to 1 if h2o_socket_get_ssl_server_name is available.])
+ ],
+ [ : ],
+ [AC_INCLUDES_DEFAULT
+ #include <h2o/socket.h>
+ ])
+ CFLAGS=$save_CFLAGS
+ LIBS=$save_LIBS
], [ : ])
AM_CONDITIONAL([HAVE_LIBH2OEVLOOP], [test "x$LIBH2OEVLOOP_LIBS" != "x"])
])
return got;
}
+
void close() override
{
if (d_conn) {
}
}
+ std::string getServerNameIndication() override
+ {
+ if (d_conn) {
+ const char* value = SSL_get_servername(d_conn.get(), TLSEXT_NAMETYPE_host_name);
+ if (value) {
+ return std::string(value);
+ }
+ }
+ return std::string();
+ }
+
private:
std::unique_ptr<SSL, void(*)(SSL*)> d_conn;
unsigned int d_timeout;
return got;
}
+ std::string getServerNameIndication() override
+ {
+ if (d_conn) {
+ unsigned int type;
+ size_t name_len = 256;
+ std::string sni;
+ sni.resize(name_len);
+
+ int res = gnutls_server_name_get(d_conn.get(), const_cast<char*>(sni.c_str()), &name_len, &type, 0);
+ if (res == GNUTLS_E_SUCCESS) {
+ sni.resize(name_len);
+ return sni;
+ }
+ }
+ return std::string();
+ }
+
void close() override
{
if (d_conn) {
pos->dr.d_content->toPacket(pw);
if(pw.size() + optsize > (d_tcp ? 65535 : getMaxReplyLen())) {
pw.rollback();
- if(pos->dr.d_place == DNSResourceRecord::ANSWER || pos->dr.d_place == DNSResourceRecord::AUTHORITY) {
- pw.truncate();
- pw.getHeader()->tc=1;
- }
+ pw.truncate();
+ pw.getHeader()->tc=1;
goto noCommit;
}
}
ComboAddress d_remote; //28
TSIGHashEnum d_tsig_algo{TSIG_MD5}; //4
- int d_ednsRawPacketSizeLimit; // only used for Lua record
+ int d_ednsRawPacketSizeLimit{-1}; // only used for Lua record
uint16_t qclass{QClass::IN}; //!< class of the question - should always be INternet 2
QType qtype; //!< type of the question 2
else
stop_at = d_recordlen;
+ /* think unsigned overflow */
+ if (stop_at < d_pos) {
+ throw std::out_of_range("getUnquotedText out of record range");
+ }
+
if(stop_at == d_pos)
return "";
contentCode=ntohs(d_lcc->lcc_protocol);
}
- if(contentCode==0x0800 && d_ip->ip_p==17) { // udp
+ if(contentCode==0x0800 && (d_pheader.caplen >= (d_skipMediaHeader + sizeof(*d_ip))) && d_ip->ip_p==17) { // udp
if (d_pheader.caplen < (d_skipMediaHeader + (4 * d_ip->ip_hl) + sizeof(*d_udp))) {
d_runts++;
continue;
d_correctpackets++;
return true;
}
- else if(contentCode==0x86dd && d_ip6->ip6_ctlun.ip6_un1.ip6_un1_nxt==17) { // udpv6, we ignore anything with extension hdr
+ else if(contentCode==0x86dd && (d_pheader.caplen >= (d_skipMediaHeader + sizeof(*d_ip6))) && d_ip6->ip6_ctlun.ip6_un1.ip6_un1_nxt==17) { // udpv6, we ignore anything with extension hdr
if (d_pheader.caplen < (d_skipMediaHeader + sizeof(struct ip6_hdr) + sizeof(struct udphdr))) {
d_runts++;
continue;
boilerplate_conv(RKEY, 57,
conv.xfr16BitInt(d_flags);
conv.xfr8BitInt(d_protocol);
+ conv.xfr8BitInt(d_algorithm);
conv.xfrBlob(d_key);
)
RKEYRecordContent::RKEYRecordContent() {}
// for SOA-EDIT-DNSUPDATE/API
bool increaseSOARecord(DNSResourceRecord& dr, const string& increaseKind, const string& editKind);
bool makeIncreasedSOARecord(SOAData& sd, const string& increaseKind, const string& editKind, DNSResourceRecord& rrout);
+DNSZoneRecord makeEditedDNSZRFromSOAData(DNSSECKeeper& dk, const SOAData& sd, DNSResourceRecord::Place place=DNSResourceRecord::ANSWER);
dnstap::Message* message = proto_message.mutable_message();
message->set_type(!dh->qr ? dnstap::Message_Type_CLIENT_QUERY : dnstap::Message_Type_CLIENT_RESPONSE);
-
- message->set_socket_family(requestor->sin4.sin_family == AF_INET ? dnstap::INET : dnstap::INET6);
message->set_socket_protocol(isTCP ? dnstap::TCP : dnstap::UDP);
- if (requestor->sin4.sin_family == AF_INET) {
- message->set_query_address(&requestor->sin4.sin_addr.s_addr, sizeof(requestor->sin4.sin_addr.s_addr));
- }
- else if (requestor->sin4.sin_family == AF_INET6) {
- message->set_query_address(&requestor->sin6.sin6_addr.s6_addr, sizeof(requestor->sin6.sin6_addr.s6_addr));
+ if (requestor != nullptr) {
+ message->set_socket_family(requestor->sin4.sin_family == AF_INET ? dnstap::INET : dnstap::INET6);
+ if (requestor->sin4.sin_family == AF_INET) {
+ message->set_query_address(&requestor->sin4.sin_addr.s_addr, sizeof(requestor->sin4.sin_addr.s_addr));
+ } else if (requestor->sin4.sin_family == AF_INET6) {
+ message->set_query_address(&requestor->sin6.sin6_addr.s6_addr, sizeof(requestor->sin6.sin6_addr.s6_addr));
+ }
+ message->set_query_port(ntohs(requestor->sin4.sin_port));
}
- message->set_query_port(ntohs(requestor->sin4.sin_port));
-
- if (requestor->sin4.sin_family == AF_INET) {
- message->set_response_address(&responder->sin4.sin_addr.s_addr, sizeof(responder->sin4.sin_addr.s_addr));
+ if (responder != nullptr) {
+ message->set_socket_family(responder->sin4.sin_family == AF_INET ? dnstap::INET : dnstap::INET6);
+ if (responder->sin4.sin_family == AF_INET) {
+ message->set_response_address(&responder->sin4.sin_addr.s_addr, sizeof(responder->sin4.sin_addr.s_addr));
+ } else if (responder->sin4.sin_family == AF_INET6) {
+ message->set_response_address(&responder->sin6.sin6_addr.s6_addr, sizeof(responder->sin6.sin6_addr.s6_addr));
+ }
+ message->set_response_port(ntohs(responder->sin4.sin_port));
}
- else if (requestor->sin4.sin_family == AF_INET6) {
- message->set_response_address(&responder->sin6.sin6_addr.s6_addr, sizeof(responder->sin6.sin6_addr.s6_addr));
- }
- message->set_response_port(ntohs(responder->sin4.sin_port));
-
if (queryTime != nullptr) {
message->set_query_time_sec(queryTime->tv_sec);
message->set_query_time_nsec(queryTime->tv_nsec);
struct BenchQuery
{
BenchQuery(const std::string& qname_, uint16_t qtype_) : qname(qname_), qtype(qtype_), udpUsec(0), tcpUsec(0), answerSecond(0) {}
- BenchQuery(){}
+ BenchQuery(): qtype(0), udpUsec(0), tcpUsec(0), answerSecond(0) {}
DNSName qname;
uint16_t qtype;
uint32_t udpUsec, tcpUsec;
if(!g_onlyTCP) {
Socket udpsock(g_dest.sin4.sin_family, SOCK_DGRAM);
- udpsock.sendTo(string((char*)&*packet.begin(), (char*)&*packet.end()), g_dest);
+ udpsock.sendTo(string(packet.begin(), packet.end()), g_dest);
ComboAddress origin;
res = waitForData(udpsock.getHandle(), 0, 1000 * g_timeoutMsec);
if(res < 0)
sock.connect(g_dest);
uint16_t len = htons(packet.size());
string tcppacket((char*)& len, 2);
- tcppacket.append((char*)&*packet.begin(), (char*)&*packet.end());
+ tcppacket.append(packet.begin(), packet.end());
sock.writen(tcppacket);
auto dh = reinterpret_cast<struct dnsheader*>(buffer);
for(;;) {
- uint16_t len = recvfrom(s.getHandle(), buffer, sizeof(buffer), 0, reinterpret_cast<struct sockaddr*>(&rem), &socklen);
+ ssize_t len = recvfrom(s.getHandle(), buffer, sizeof(buffer), 0, reinterpret_cast<struct sockaddr*>(&rem), &socklen);
if(len < 0)
unixDie("recvfrom");
- if (len < sizeof(dnsheader))
+ if (static_cast<size_t>(len) < sizeof(dnsheader))
unixDie("too small " + std::to_string(len));
if(dh->qr)
vector<char> mesg;
mesg.resize(1024000);
- int len;
+ ssize_t len;
ComboAddress remote;
socklen_t remlen=remote.getSocklen();
else if(len==0)
throw PDNSException("Guardian exited - going down as well");
- if(len == (int)mesg.size())
+ if(static_cast<size_t>(len) == mesg.size())
throw PDNSException("Line on control console was too long");
mesg[len]=0;
}
-
+
return &mesg[0];
}
#include "config.h"
#include "fstrm_logger.hh"
+
+#ifdef RECURSOR
+#include "logger.hh"
+#else
#include "dolog.hh"
+#endif
#define DNSTAP_CONTENT_TYPE "protobuf:dnstap.Dnstap"
#ifdef HAVE_FSTRM
-FrameStreamLogger::FrameStreamLogger(const int family, const std::string& address, bool connect): d_family(family), d_address(address)
+FrameStreamLogger::FrameStreamLogger(const int family, const std::string& address, bool connect,
+ const std::unordered_map<string,unsigned>& options): d_family(family), d_address(address)
{
fstrm_res res;
throw std::runtime_error("FrameStreamLogger: fstrm_iothr_options_set_queue_model failed: " + std::to_string(res));
}
+ if (options.find("bufferHint") != options.end() && options.at("bufferHint")) {
+ res = fstrm_iothr_options_set_buffer_hint(d_iothropt, options.at("bufferHint"));
+ if (res != fstrm_res_success) {
+ throw std::runtime_error("FrameStreamLogger: fstrm_iothr_options_set_buffer_hint failed: " + std::to_string(res));
+ }
+ }
+ if (options.find("flushTimeout") != options.end() && options.at("flushTimeout")) {
+ res = fstrm_iothr_options_set_flush_timeout(d_iothropt, options.at("flushTimeout"));
+ if (res != fstrm_res_success) {
+ throw std::runtime_error("FrameStreamLogger: fstrm_iothr_options_set_flush_timeout failed: " + std::to_string(res));
+ }
+ }
+ if (options.find("inputQueueSize") != options.end() && options.at("inputQueueSize")) {
+ res = fstrm_iothr_options_set_input_queue_size(d_iothropt, options.at("inputQueueSize"));
+ if (res != fstrm_res_success) {
+ throw std::runtime_error("FrameStreamLogger: fstrm_iothr_options_set_input_queue_size failed: " + std::to_string(res));
+ }
+ }
+ if (options.find("outputQueueSize") != options.end() && options.at("outputQueueSize")) {
+ res = fstrm_iothr_options_set_output_queue_size(d_iothropt, options.at("outputQueueSize"));
+ if (res != fstrm_res_success) {
+ throw std::runtime_error("FrameStreamLogger: fstrm_iothr_options_set_output_queue_size failed: " + std::to_string(res));
+ }
+ }
+ if (options.find("queueNotifyThreshold") != options.end() && options.at("queueNotifyThreshold")) {
+ res = fstrm_iothr_options_set_queue_notify_threshold(d_iothropt, options.at("queueNotifyThreshold"));
+ if (res != fstrm_res_success) {
+ throw std::runtime_error("FrameStreamLogger: fstrm_iothr_options_set_queue_notify_threshold failed: " + std::to_string(res));
+ }
+ }
+ if (options.find("setReopenInterval") != options.end() && options.at("setReopenInterval")) {
+ res = fstrm_iothr_options_set_reopen_interval(d_iothropt, options.at("setReopenInterval"));
+ if (res != fstrm_res_success) {
+ throw std::runtime_error("FrameStreamLogger: fstrm_iothr_options_set_reopen_interval failed: " + std::to_string(res));
+ }
+ }
+
+
if (connect) {
d_iothr = fstrm_iothr_init(d_iothropt, &d_writer);
if (!d_iothr) {
}
uint8_t *frame = (uint8_t*)malloc(data.length());
if (!frame) {
+#ifdef RECURSOR
+ g_log<<Logger::Warning<<"FrameStreamLogger: cannot allocate memory for stream."<<std::endl;
+#else
warnlog("FrameStreamLogger: cannot allocate memory for stream.");
+#endif
return;
}
memcpy(frame, data.c_str(), data.length());
// Frame successfully queued.
} else if (res == fstrm_res_again) {
free(frame);
+#ifdef RECURSOR
+ g_log<<Logger::Warning<<"FrameStreamLogger: queue full, dropping."<<std::endl;
+#else
warnlog("FrameStreamLogger: queue full, dropping.");
- } else {
+#endif
+ } else {
// Permanent failure.
free(frame);
+#ifdef RECURSOR
+ g_log<<Logger::Warning<<"FrameStreamLogger: submitting to queue failed."<<std::endl;
+#else
warnlog("FrameStreamLogger: submitting to queue failed.");
+#endif
}
}
#ifdef HAVE_FSTRM
+#include <unordered_map>
#include <fstrm.h>
#include <fstrm/iothr.h>
#include <fstrm/unix_writer.h>
class FrameStreamLogger : public RemoteLoggerInterface, boost::noncopyable
{
public:
- FrameStreamLogger(int family, const std::string& address, bool connect);
+ FrameStreamLogger(int family, const std::string& address, bool connect, const std::unordered_map<string,unsigned>& options = std::unordered_map<string,unsigned>());
virtual ~FrameStreamLogger();
virtual void queueData(const std::string& data) override;
virtual std::string toString() const override
{
return "FrameStreamLogger to " + d_address;
}
+ bool logQueries(void) const { return d_logQueries; }
+ bool logResponses(void) const { return d_logResponses; }
+ void setLogQueries(bool flag) { d_logQueries = flag; }
+ void setLogResponses(bool flag) { d_logResponses = flag; }
+
private:
+
const int d_family;
const std::string d_address;
struct fstrm_iothr_queue *d_ioqueue{nullptr};
struct fstrm_iothr *d_iothr{nullptr};
void cleanup();
+
+ bool d_logQueries{true};
+ bool d_logResponses{true};
};
+#else
+class FrameStreamLogger : public RemoteLoggerInterface, boost::noncopyable {};
#endif /* HAVE_FSTRM */
*place &= (~((1<<bitsleft)-1));
}
-size_t sendMsgWithTimeout(int fd, const char* buffer, size_t len, int idleTimeout, const ComboAddress* dest, const ComboAddress* local, unsigned int localItf, int totalTimeout, int flags)
+size_t sendMsgWithOptions(int fd, const char* buffer, size_t len, const ComboAddress* dest, const ComboAddress* local, unsigned int localItf, int flags)
{
- int remainingTime = totalTimeout;
- time_t start = 0;
- if (totalTimeout) {
- start = time(nullptr);
- }
-
struct msghdr msgh;
struct iovec iov;
char cbuf[256];
}
/* partial write */
+ firstTry = false;
iov.iov_len -= written;
iov.iov_base = reinterpret_cast<void*>(reinterpret_cast<char*>(iov.iov_base) + written);
written = 0;
}
+ else if (res == 0) {
+ return res;
+ }
else if (res == -1) {
if (errno == EINTR) {
continue;
else if (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINPROGRESS || errno == ENOTCONN) {
/* EINPROGRESS might happen with non blocking socket,
especially with TCP Fast Open */
- if (totalTimeout <= 0 && idleTimeout <= 0) {
- return sent;
- }
-
- if (firstTry) {
- int res = waitForRWData(fd, false, (totalTimeout == 0 || idleTimeout <= remainingTime) ? idleTimeout : remainingTime, 0);
- if (res > 0) {
- /* there is room available */
- firstTry = false;
- }
- else if (res == 0) {
- throw runtime_error("Timeout while waiting to write data");
- } else {
- throw runtime_error("Error while waiting for room to write data");
- }
- }
- else {
- throw runtime_error("Timeout while waiting to write data");
- }
+ return sent;
}
else {
unixDie("failed in sendMsgWithTimeout");
}
}
- if (totalTimeout) {
- time_t now = time(nullptr);
- int elapsed = now - start;
- if (elapsed >= remainingTime) {
- throw runtime_error("Timeout while sending data");
- }
- start = now;
- remainingTime -= elapsed;
- }
}
- while (firstTry);
+ while (true);
return 0;
}
template class NetmaskTree<bool>;
-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)
-{
- uint16_t size = htons(bufferLen);
- char cbuf[256];
- struct msghdr msgh;
- struct iovec iov[2];
- int remainingTime = totalTimeout;
- time_t start = 0;
- if (totalTimeout) {
- start = time(NULL);
- }
-
- /* Set up iov and msgh structures. */
- memset(&msgh, 0, sizeof(struct msghdr));
- msgh.msg_control = nullptr;
- msgh.msg_controllen = 0;
- if (dest) {
- msgh.msg_name = reinterpret_cast<void*>(const_cast<ComboAddress*>(dest));
- msgh.msg_namelen = dest->getSocklen();
- }
- else {
- msgh.msg_name = nullptr;
- msgh.msg_namelen = 0;
- }
-
- msgh.msg_flags = 0;
-
- if (localItf != 0 && local) {
- addCMsgSrcAddr(&msgh, cbuf, local, localItf);
- }
-
- iov[0].iov_base = &size;
- iov[0].iov_len = sizeof(size);
- iov[1].iov_base = reinterpret_cast<void*>(const_cast<char*>(buffer));
- iov[1].iov_len = bufferLen;
-
- size_t pos = 0;
- size_t sent = 0;
- size_t nbElements = sizeof(iov)/sizeof(*iov);
- while (true) {
- msgh.msg_iov = &iov[pos];
- msgh.msg_iovlen = nbElements - pos;
-
- ssize_t res = sendmsg(sock, &msgh, flags);
- if (res > 0) {
- size_t written = static_cast<size_t>(res);
- sent += written;
-
- if (sent == (sizeof(size) + bufferLen)) {
- return true;
- }
- /* partial write, we need to keep only the (parts of) elements
- that have not been written.
- */
- do {
- if (written < iov[pos].iov_len) {
- iov[pos].iov_len -= written;
- iov[pos].iov_base = reinterpret_cast<void*>(reinterpret_cast<char*>(iov[pos].iov_base) + written);
- written = 0;
- }
- else {
- written -= iov[pos].iov_len;
- iov[pos].iov_len = 0;
- pos++;
- }
- }
- while (written > 0 && pos < nbElements);
- }
- else if (res == -1) {
- if (errno == EINTR) {
- continue;
- }
- else if (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINPROGRESS) {
- /* EINPROGRESS might happen with non blocking socket,
- especially with TCP Fast Open */
- int ret = waitForRWData(sock, false, (totalTimeout == 0 || idleTimeout <= remainingTime) ? idleTimeout : remainingTime, 0);
- if (ret > 0) {
- /* there is room available */
- }
- else if (ret == 0) {
- throw runtime_error("Timeout while waiting to send data");
- } else {
- throw runtime_error("Error while waiting for room to send data");
- }
- }
- else {
- unixDie("failed in sendSizeAndMsgWithTimeout");
- }
- }
- if (totalTimeout) {
- time_t now = time(NULL);
- int elapsed = now - start;
- if (elapsed >= remainingTime) {
- throw runtime_error("Timeout while sending data");
- }
- start = now;
- remainingTime -= elapsed;
- }
- }
-
- return false;
-}
-
/* requires a non-blocking socket.
On Linux, we could use MSG_DONTWAIT on a blocking socket
but this is not portable.
bool HarvestTimestamp(struct msghdr* msgh, struct timeval* tv);
void fillMSGHdr(struct msghdr* msgh, struct iovec* iov, char* cbuf, size_t cbufsize, char* data, size_t datalen, ComboAddress* addr);
ssize_t sendfromto(int sock, const char* data, size_t len, int flags, const ComboAddress& from, const ComboAddress& to);
-size_t sendMsgWithTimeout(int fd, const char* buffer, size_t len, int idleTimeout, const ComboAddress* dest, const ComboAddress* local, unsigned int localItf, int totalTimeout, int flags);
-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);
+size_t sendMsgWithOptions(int fd, const char* buffer, size_t len, const ComboAddress* dest, const ComboAddress* local, unsigned int localItf, int flags);
+
/* requires a non-blocking, connected TCP socket */
bool isTCPSocketUsable(int sock);
for(auto iter = found.first; iter != found.second; ++iter) {
auto soa = std::dynamic_pointer_cast<SOARecordContent>(iter->d_content);
- soaret = *iter;
- return soa->d_st.serial;
+ if (soa) {
+ soaret = *iter;
+ return soa->d_st.serial;
+ }
}
return 0;
}
writeRecords(fp, soarecord);
fclose(fp);
- rename( (fname+".partial").c_str(), fname.c_str());
+ if (rename( (fname+".partial").c_str(), fname.c_str()) != 0) {
+ throw std::runtime_error("Unable to move the zone file for " + zone.toLogString() + " from " + fname + ".partial to " + fname + ": " + string(strerror(errno)));
+ }
}
void loadZoneFromDisk(records_t& records, const string& fname, const DNSName& zone)
return ret;
}
+static thread_local unique_ptr<AuthLua4> s_LUA;
+bool g_LuaRecordSharedState;
+
std::vector<shared_ptr<DNSRecordContent>> luaSynth(const std::string& code, const DNSName& query, const DNSName& zone, int zoneid, const DNSPacket& dnsp, uint16_t qtype)
{
- AuthLua4 alua;
+ if(!s_LUA || // we don't have a Lua state yet
+ !g_LuaRecordSharedState) { // or we want a new one even if we had one
+ s_LUA = make_unique<AuthLua4>();
+ }
std::vector<shared_ptr<DNSRecordContent>> ret;
- LuaContext& lua = *alua.getLua();
+ LuaContext& lua = *s_LUA->getLua();
lua.writeVariable("qname", query);
lua.writeVariable("who", dnsp.getRemote());
lua.writeVariable("dh", (dnsheader*)&dnsp.d);
#include "uuid-utils.hh"
+#ifdef HAVE_FSTRM
+#include "rec-dnstap.hh"
+#include "fstrm_logger.hh"
+bool g_syslog;
+
+static bool isEnabledForQueries(const std::shared_ptr<std::vector<std::unique_ptr<FrameStreamLogger>>>& fstreamLoggers)
+{
+ if (fstreamLoggers == nullptr) {
+ return false;
+ }
+ for (auto& logger : *fstreamLoggers) {
+ if (logger->logQueries()) {
+ return true;
+ }
+ }
+ return false;
+}
+
+static void logFstreamQuery(const std::shared_ptr<std::vector<std::unique_ptr<FrameStreamLogger>>>& fstreamLoggers, const struct timeval &queryTime, const ComboAddress& ip, bool doTCP, const vector<uint8_t>& packet)
+{
+ if (fstreamLoggers == nullptr)
+ return;
+
+ struct timespec ts;
+ TIMEVAL_TO_TIMESPEC(&queryTime, &ts);
+ RecDnstapMessage message(SyncRes::s_serverID, nullptr, &ip, doTCP, reinterpret_cast<const char*>(&*packet.begin()), packet.size(), &ts, nullptr);
+ std::string str;
+ message.serialize(str);
+
+ for (auto& logger : *fstreamLoggers) {
+ logger->queueData(str);
+ }
+}
+
+static bool isEnabledForResponses(const std::shared_ptr<std::vector<std::unique_ptr<FrameStreamLogger>>>& fstreamLoggers)
+{
+ if (fstreamLoggers == nullptr) {
+ return false;
+ }
+ for (auto& logger : *fstreamLoggers) {
+ if (logger->logResponses()) {
+ return true;
+ }
+ }
+ return false;
+}
+
+static void logFstreamResponse(const std::shared_ptr<std::vector<std::unique_ptr<FrameStreamLogger>>>& fstreamLoggers, const ComboAddress& ip, bool doTCP, const std::string& packet, const struct timeval& queryTime, const struct timeval& replyTime)
+{
+ if (fstreamLoggers == nullptr)
+ return;
+
+ struct timespec ts1, ts2;
+ TIMEVAL_TO_TIMESPEC(&queryTime, &ts1);
+ TIMEVAL_TO_TIMESPEC(&replyTime, &ts2);
+ RecDnstapMessage message(SyncRes::s_serverID, nullptr, &ip, doTCP, static_cast<const char*>(&*packet.begin()), packet.size(), &ts1, &ts2);
+ std::string str;
+ message.serialize(str);
+
+ for (auto& logger : *fstreamLoggers) {
+ logger->queueData(str);
+ }
+}
+
+#endif // HAVE_FSTRM
+
static void logOutgoingQuery(const std::shared_ptr<std::vector<std::unique_ptr<RemoteLogger>>>& outgoingLoggers, boost::optional<const boost::uuids::uuid&> initialRequestId, const boost::uuids::uuid& uuid, const ComboAddress& ip, const DNSName& domain, int type, uint16_t qid, bool doTCP, size_t bytes, boost::optional<Netmask>& srcmask)
{
if(!outgoingLoggers)
/** lwr is only filled out in case 1 was returned, and even when returning 1 for 'success', lwr might contain DNS errors
Never throws!
*/
-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, const std::shared_ptr<std::vector<std::unique_ptr<RemoteLogger>>>& outgoingLoggers, const std::set<uint16_t>& exportTypes, LWResult *lwr, bool* chained)
+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, const std::shared_ptr<std::vector<std::unique_ptr<RemoteLogger>>>& outgoingLoggers, const std::shared_ptr<std::vector<std::unique_ptr<FrameStreamLogger>>>& fstrmLoggers, const std::set<uint16_t>& exportTypes, LWResult *lwr, bool* chained)
{
size_t len;
size_t bufsize=g_outgoingEDNSBufsize;
uuid = getUniqueID();
logOutgoingQuery(outgoingLoggers, context ? context->d_initialRequestId : boost::none, uuid, ip, domain, type, qid, doTCP, vpacket.size(), srcmask);
}
-#endif
+#endif /* HAVE_PROTOBUF */
+#ifdef HAVE_FSTRM
+ if (isEnabledForQueries(fstrmLoggers)) {
+ logFstreamQuery(fstrmLoggers, queryTime, ip, doTCP, vpacket);
+ }
+#endif /* HAVE_FSTRM */
srcmask = boost::none; // this is also our return value, even if EDNS0Level == 0
- errno=0;
if(!doTCP) {
int queryfd;
if(ip.sin4.sin_family==AF_INET6)
return ret;
buf.resize(len);
+
+#ifdef HAVE_FSTRM
+ if (isEnabledForResponses(fstrmLoggers)) {
+ logFstreamResponse(fstrmLoggers, ip, doTCP, buf, queryTime, *now);
+ }
+#endif /* HAVE_FSTRM */
+
lwr->d_records.clear();
try {
lwr->d_tcbit=0;
#include "dns.hh"
#include "namespaces.hh"
#include "remote_logger.hh"
+#include "fstrm_logger.hh"
#include "resolve-context.hh"
+
int asendto(const char *data, size_t len, int flags, const ComboAddress& ip, uint16_t id,
const DNSName& domain, uint16_t qtype, int* fd);
int arecvfrom(std::string& packet, int flags, const ComboAddress& ip, size_t *d_len, uint16_t id,
bool d_haveEDNS{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, const std::shared_ptr<std::vector<std::unique_ptr<RemoteLogger>>>& outgoingLoggers, const std::set<uint16_t>& exportTypes, LWResult* res, bool* chained);
+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, const std::shared_ptr<std::vector<std::unique_ptr<RemoteLogger>>>& outgoingLoggers, const std::shared_ptr<std::vector<std::unique_ptr<FrameStreamLogger>>>& fstrmLoggers, const std::set<uint16_t>& exportTypes, LWResult* res, bool* chained);
#endif // PDNS_LWRES_HH
#include <curl/curl.h>
#include <stdexcept>
+void MiniCurl::init()
+{
+ static std::atomic_flag s_init = ATOMIC_FLAG_INIT;
+
+ if (s_init.test_and_set())
+ return;
+
+ CURLcode code = curl_global_init(CURL_GLOBAL_ALL);
+ if (code != 0) {
+ throw std::runtime_error("Error initializing libcurl");
+ }
+}
+
MiniCurl::MiniCurl(const string& useragent)
{
d_curl = curl_easy_init();
- if (d_curl != nullptr) {
- curl_easy_setopt(d_curl, CURLOPT_USERAGENT, useragent.c_str());
+ if (d_curl == nullptr) {
+ throw std::runtime_error("Error creating a MiniCurl session");
}
+ curl_easy_setopt(d_curl, CURLOPT_USERAGENT, useragent.c_str());
}
MiniCurl::~MiniCurl()
curl_easy_setopt(d_curl, CURLOPT_INTERFACE, src->toString().c_str());
}
curl_easy_setopt(d_curl, CURLOPT_FOLLOWLOCATION, true);
- /* only allow HTTP, TFTP and SFTP */
+ /* only allow HTTP and HTTPS */
curl_easy_setopt(d_curl, CURLOPT_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS);
curl_easy_setopt(d_curl, CURLOPT_SSL_VERIFYPEER, false);
curl_easy_setopt(d_curl, CURLOPT_SSL_VERIFYHOST, false);
curl_easy_setopt(d_curl, CURLOPT_WRITEDATA, this);
curl_easy_setopt(d_curl, CURLOPT_TIMEOUT, 2L);
+ clearHeaders();
d_data.clear();
}
+
std::string MiniCurl::getURL(const std::string& str, const ComboAddress* rem, const ComboAddress* src)
{
setupURL(str, rem, src);
return ret;
}
-std::string MiniCurl::postURL(const std::string& str, const std::string& postdata)
+std::string MiniCurl::postURL(const std::string& str, const std::string& postdata, MiniCurlHeaders& headers)
{
setupURL(str);
+ setHeaders(headers);
+ curl_easy_setopt(d_curl, CURLOPT_POSTFIELDSIZE, postdata.size());
curl_easy_setopt(d_curl, CURLOPT_POSTFIELDS, postdata.c_str());
auto res = curl_easy_perform(d_curl);
+
+ long http_code = 0;
+ curl_easy_getinfo(d_curl, CURLINFO_RESPONSE_CODE, &http_code);
+
if(res != CURLE_OK)
- throw std::runtime_error("Unable to post URL");
+ throw std::runtime_error("Unable to post URL ("+std::to_string(http_code)+"): "+string(curl_easy_strerror(res)));
std::string ret=d_data;
d_data.clear();
return ret;
}
+
+void MiniCurl::clearHeaders()
+{
+ if (d_curl) {
+ curl_easy_setopt(d_curl, CURLOPT_HTTPHEADER, NULL);
+ if (d_header_list != nullptr) {
+ curl_slist_free_all(d_header_list);
+ d_header_list = nullptr;
+ }
+ }
+}
+
+void MiniCurl::setHeaders(const MiniCurlHeaders& headers)
+{
+ if (d_curl) {
+ for (auto& header : headers) {
+ std::stringstream header_ss;
+ header_ss << header.first << ": " << header.second;
+ d_header_list = curl_slist_append(d_header_list, header_ss.str().c_str());
+ }
+ curl_easy_setopt(d_curl, CURLOPT_HTTPHEADER, d_header_list);
+ }
+}
class MiniCurl
{
public:
+ using MiniCurlHeaders = std::map<std::string, std::string>;
+
+ static void init();
+
MiniCurl(const string& useragent="MiniCurl/0.0");
~MiniCurl();
MiniCurl& operator=(const MiniCurl&) = delete;
std::string getURL(const std::string& str, const ComboAddress* rem=0, const ComboAddress* src=0);
- std::string postURL(const std::string& str, const std::string& postdata);
+ std::string postURL(const std::string& str, const std::string& postdata, MiniCurlHeaders& headers);
private:
CURL *d_curl;
static size_t write_callback(char *ptr, size_t size, size_t nmemb, void *userdata);
std::string d_data;
+ struct curl_slist* d_header_list = nullptr;
void setupURL(const std::string& str, const ComboAddress* rem=0, const ComboAddress* src=0);
+ void setHeaders(const MiniCurlHeaders& headers);
+ void clearHeaders();
};
if(sock.write((char *) &len, 2) != 2)
throw PDNSException("tcp write failed");
- sock.writen(string((char*)&*packet.begin(), (char*)&*packet.end()));
+ sock.writen(string(packet.begin(), packet.end()));
if(sock.read((char *) &len, 2) != 2)
throw PDNSException("tcp read failed");
public:
NSECBitmapGenerator(DNSPacketWriter& pw_): pw(pw_)
{
+ memset(res, 0, sizeof(res));
}
+
void set(uint16_t type)
{
uint16_t bit = type % 256;
tmp.assign(res, res+len+2);
pw.xfrBlob(tmp);
}
- memset(res, 0, 34);
+ memset(res, 0, sizeof(res));
oldWindow = window;
}
res[2+bit/8] |= 1 << (7-(bit%8));
}
-void PacketHandler::makeNXDomain(DNSPacket* p, DNSPacket* r, const DNSName& target, const DNSName& wildcard, SOAData& sd)
+void PacketHandler::makeNXDomain(DNSPacket* p, DNSPacket* r, const DNSName& target, const DNSName& wildcard, const SOAData& sd)
{
DNSZoneRecord rr;
- rr.dr.d_name=sd.qname;
- rr.dr.d_type=QType::SOA;
- rr.dr.d_content=makeSOAContent(sd);
+ rr=makeEditedDNSZRFromSOAData(d_dk, sd, DNSResourceRecord::AUTHORITY);
rr.dr.d_ttl=min(sd.ttl, sd.default_ttl);
- rr.signttl=sd.ttl;
- rr.domain_id=sd.domain_id;
- rr.dr.d_place=DNSResourceRecord::AUTHORITY;
- rr.auth = 1;
r->addRecord(rr);
if(d_dnssec) {
r->setRcode(RCode::NXDomain);
}
-void PacketHandler::makeNOError(DNSPacket* p, DNSPacket* r, const DNSName& target, const DNSName& wildcard, SOAData& sd, int mode)
+void PacketHandler::makeNOError(DNSPacket* p, DNSPacket* r, const DNSName& target, const DNSName& wildcard, const SOAData& sd, int mode)
{
DNSZoneRecord rr;
- rr.dr.d_name=sd.qname;
- rr.dr.d_type=QType::SOA;
- rr.dr.d_content=makeSOAContent(sd);
+ rr=makeEditedDNSZRFromSOAData(d_dk, sd, DNSResourceRecord::AUTHORITY);
rr.dr.d_ttl=min(sd.ttl, sd.default_ttl);
- rr.signttl=sd.ttl;
- rr.domain_id=sd.domain_id;
- rr.dr.d_place=DNSResourceRecord::AUTHORITY;
- rr.auth = 1;
r->addRecord(rr);
if(d_dnssec) {
}
if(p->qtype.getCode() == QType::SOA && sd.qname==p->qdomain) {
- rr.dr.d_name=sd.qname;
- rr.dr.d_type=QType::SOA;
- sd.serial = calculateEditSOA(sd.serial, d_dk, sd.qname);
- rr.dr.d_content=makeSOAContent(sd);
- rr.dr.d_ttl=sd.ttl;
- rr.domain_id=sd.domain_id;
- rr.dr.d_place=DNSResourceRecord::ANSWER;
- rr.auth = true;
+ rr=makeEditedDNSZRFromSOAData(d_dk, sd);
r->addRecord(rr);
goto sendit;
}
/* Add in SOA if required */
if(target==sd.qname) {
- rr.dr.d_name = sd.qname;
- rr.dr.d_type = QType::SOA;
- sd.serial = calculateEditSOA(sd.serial, d_dk, sd.qname);
- rr.dr.d_content = makeSOAContent(sd);
- rr.dr.d_ttl = sd.ttl;
- rr.domain_id = sd.domain_id;
- rr.auth = true;
+ rr=makeEditedDNSZRFromSOAData(d_dk, sd);
rrset.push_back(rr);
}
int checkUpdatePrerequisites(const DNSRecord *rr, DomainInfo *di);
void increaseSerial(const string &msgPrefix, const DomainInfo *di, bool haveNSEC3, bool narrow, const NSEC3PARAMRecordContent *ns3pr);
- void makeNXDomain(DNSPacket* p, DNSPacket* r, const DNSName& target, const DNSName& wildcard, SOAData& sd);
- void makeNOError(DNSPacket* p, DNSPacket* r, const DNSName& target, const DNSName& wildcard, SOAData& sd, int mode);
+ void makeNXDomain(DNSPacket* p, DNSPacket* r, const DNSName& target, const DNSName& wildcard, const SOAData& sd);
+ void makeNOError(DNSPacket* p, DNSPacket* r, const DNSName& target, const DNSName& wildcard, const SOAData& sd, int mode);
vector<DNSZoneRecord> getBestReferralNS(DNSPacket *p, SOAData& sd, const DNSName &target);
vector<DNSZoneRecord> getBestDNAMESynth(DNSPacket *p, SOAData& sd, DNSName &target);
bool tryDNAME(DNSPacket *p, DNSPacket*r, SOAData& sd, DNSName &target);
#ifdef HAVE_PROTOBUF
#include "uuid-utils.hh"
-#endif
+#endif /* HAVE_PROTOBUF */
#include "xpf.hh"
static thread_local uint64_t t_outgoingProtobufServersGeneration;
#endif /* HAVE_PROTOBUF */
+#ifdef HAVE_FSTRM
+static thread_local std::shared_ptr<std::vector<std::unique_ptr<FrameStreamLogger>>> t_frameStreamServers{nullptr};
+static thread_local uint64_t t_frameStreamServersGeneration;
+#endif /* HAVE_FSTRM */
+
thread_local std::unique_ptr<MT_t> MT; // the big MTasker
thread_local std::unique_ptr<MemRecursorCache> t_RC;
thread_local std::unique_ptr<RecursorPacketCache> t_packetCache;
{
}
- typedef set<int> socks_t;
- socks_t d_socks;
-
// returning -2 means: temporary OS error (ie, out of files), -1 means error related to remote
int getSocket(const ComboAddress& toaddr, int* fd)
{
if(connect(*fd, (struct sockaddr*)(&toaddr), toaddr.getSocklen()) < 0) {
int err = errno;
- // returnSocket(*fd);
try {
closesocket(*fd);
}
return -1;
}
- d_socks.insert(*fd);
d_numsocks++;
return 0;
}
- void returnSocket(int fd)
- {
- socks_t::iterator i=d_socks.find(fd);
- if(i==d_socks.end()) {
- throw PDNSException("Trying to return a socket (fd="+std::to_string(fd)+") not in the pool");
- }
- returnSocketLocked(i);
- }
-
// return a socket to the pool, or simply erase it
- void returnSocketLocked(socks_t::iterator& i)
+ void returnSocket(int fd)
{
- if(i==d_socks.end()) {
- throw PDNSException("Trying to return a socket not in the pool");
- }
try {
- t_fdm->removeReadFD(*i);
+ t_fdm->removeReadFD(fd);
}
- catch(FDMultiplexerException& e) {
+ catch(const FDMultiplexerException& e) {
// we sometimes return a socket that has not yet been assigned to t_fdm
}
+
try {
- closesocket(*i);
+ closesocket(fd);
}
catch(const PDNSException& e) {
g_log<<Logger::Error<<"Error closing returned UDP socket: "<<e.reason<<endl;
}
- d_socks.erase(i++);
--d_numsocks;
}
+private:
+
// returns -1 for errors which might go away, throws for ones that won't
static int makeClientSocket(int family)
{
if (::bind(ret, (struct sockaddr *)&sin, sin.getSocklen()) >= 0)
break;
}
- if(!tries)
+
+ if(!tries) {
+ closesocket(ret);
throw PDNSException("Resolver binding to local query client socket on "+sin.toString()+": "+stringerror());
+ }
+
+ try {
+ setReceiveSocketErrors(ret, family);
+ setNonBlocking(ret);
+ }
+ catch(...) {
+ closesocket(ret);
+ throw;
+ }
- setReceiveSocketErrors(ret, family);
- setNonBlocking(ret);
return ret;
}
};
int ret=MT->waitEvent(pident, &packet, g_networkTimeoutMsec, now);
+ /* -1 means error, 0 means timeout, 1 means a result from handleUDPServerResponse() which might still be an error */
if(ret > 0) {
+ /* handleUDPServerResponse() will close the socket for us no matter what */
if(packet.empty()) // means "error"
return -1;
}
}
else {
+ /* getting there means error or timeout, it's up to us to close the socket */
if(fd >= 0)
t_udpclientsocks->returnSocket(fd);
}
return true;
}
+
+#ifdef HAVE_FSTRM
+
+static std::shared_ptr<std::vector<std::unique_ptr<FrameStreamLogger>>> startFrameStreamServers(const FrameStreamExportConfig& config)
+{
+ auto result = std::make_shared<std::vector<std::unique_ptr<FrameStreamLogger>>>();
+
+ for (const auto& server : config.servers) {
+ try {
+ std::unordered_map<string,unsigned> options;
+ options["bufferHint"] = config.bufferHint;
+ options["flushTimeout"] = config.flushTimeout;
+ options["inputQueueSize"] = config.inputQueueSize;
+ options["outputQueueSize"] = config.outputQueueSize;
+ options["queueNotifyThreshold"] = config.queueNotifyThreshold;
+ options["reopenInterval"] = config.reopenInterval;
+ FrameStreamLogger *fsl = nullptr;
+ try {
+ ComboAddress address(server);
+ fsl = new FrameStreamLogger(address.sin4.sin_family, address.toStringWithPort(), true, options);
+ }
+ catch (const PDNSException& e) {
+ fsl = new FrameStreamLogger(AF_UNIX, server, true, options);
+ }
+ fsl->setLogQueries(config.logQueries);
+ fsl->setLogResponses(config.logResponses);
+ result->emplace_back(fsl);
+ }
+ catch(const std::exception& e) {
+ g_log<<Logger::Error<<"Error while starting dnstap framestream logger to '"<<server<<": "<<e.what()<<endl;
+ }
+ catch(const PDNSException& e) {
+ g_log<<Logger::Error<<"Error while starting dnstap framestream logger to '"<<server<<": "<<e.reason<<endl;
+ }
+ }
+
+ return result;
+}
+
+static bool checkFrameStreamExport(LocalStateHolder<LuaConfigItems>& luaconfsLocal)
+{
+ if (!luaconfsLocal->frameStreamExportConfig.enabled) {
+ if (t_frameStreamServers) {
+ // dt's take care of cleanup
+ t_frameStreamServers.reset();
+ }
+
+ return false;
+ }
+
+ /* if the server was not running, or if it was running according to a
+ previous configuration */
+ if (!t_frameStreamServers ||
+ t_frameStreamServersGeneration < luaconfsLocal->generation) {
+
+ if (t_frameStreamServers) {
+ // dt's take care of cleanup
+ t_frameStreamServers.reset();
+ }
+
+ t_frameStreamServers = startFrameStreamServers(luaconfsLocal->frameStreamExportConfig);
+ t_frameStreamServersGeneration = luaconfsLocal->generation;
+ }
+
+ return true;
+}
+#endif /* HAVE_FSTRM */
#endif /* HAVE_PROTOBUF */
#ifdef NOD_ENABLED
}
#endif /* HAVE_PROTOBUF */
+#ifdef HAVE_FSTRM
+ checkFrameStreamExport(luaconfsLocal);
+#endif
+
DNSPacketWriter pw(packet, dc->d_mdp.d_qname, dc->d_mdp.d_qtype, dc->d_mdp.d_qclass);
pw.getHeader()->aa=0;
sr.setInitialRequestId(dc->d_uuid);
sr.setOutgoingProtobufServers(t_outgoingProtobufServers);
#endif
-
+#ifdef HAVE_FSTRM
+ sr.setFrameStreamServers(t_frameStreamServers);
+#endif
sr.setQuerySource(dc->d_remote, g_useIncomingECS && !dc->d_ednssubnet.source.empty() ? boost::optional<const EDNSSubnetOpts&>(dc->d_ednssubnet) : boost::none);
bool tracedQuery=false; // we could consider letting Lua know about this too
needECS = true;
}
logQuery = t_protobufServers && luaconfsLocal->protobufExportConfig.logQueries;
+#endif /* HAVE_PROTOBUF */
+
+#ifdef HAVE_FSTRM
+ checkFrameStreamExport(luaconfsLocal);
#endif
if(needECS || needXPF || (t_pdl && (t_pdl->d_gettag_ffi || t_pdl->d_gettag))) {
}
logQuery = t_protobufServers && luaconfsLocal->protobufExportConfig.logQueries;
bool logResponse = t_protobufServers && luaconfsLocal->protobufExportConfig.logResponses;
+#endif
+#ifdef HAVE_FSTRM
+ checkFrameStreamExport(luaconfsLocal);
#endif
EDNSSubnetOpts ednssubnet;
bool ecsFound = false;
retryWithName:
if(!MT->sendEvent(pident, &packet)) {
+ /* we did not find a match for this response, something is wrong */
+
// we do a full scan for outstanding queries on unexpected answers. not too bad since we only accept them on the right port number, which is hard enough to guess
for(MT_t::waiters_t::iterator mthread=MT->d_waiters.begin(); mthread!=MT->d_waiters.end(); ++mthread) {
if(pident.fd==mthread->key.fd && mthread->key.remote==pident.remote && mthread->key.type == pident.type &&
}
}
else if(fd >= 0) {
+ /* we either found a waiter (1) or encountered an issue (-1), it's up to us to clean the socket anyway */
t_udpclientsocks->returnSocket(fd);
}
}
checkProtobufExport(luaconfsLocal);
checkOutgoingProtobufExport(luaconfsLocal);
#endif /* HAVE_PROTOBUF */
+#ifdef HAVE_FSTRM
+ checkFrameStreamExport(luaconfsLocal);
+#endif
PacketID pident;
DomainInfo di;
try {
- B.getDomainInfo(zone, di);
+ if (!B.getDomainInfo(zone, di)) {
+ cout<<"[Error] Unable to get domain information for zone '"<<zone<<"'"<<endl;
+ return 1;
+ }
} catch(const PDNSException &e) {
if (di.kind == DomainInfo::Slave) {
cout<<"[Error] non-IP address for masters: "<<e.reason<<endl;
sort(keys.begin(),keys.end());
reverse(keys.begin(),keys.end());
- bool shown=false;
for(const auto& key : keys) {
string algname = DNSSECKeeper::algorithm2name(key.d_algorithm);
cout << "DNSKEY = " <<zone.toString()<<" IN DNSKEY "<< key.getZoneRepresentation() << "; ( " + algname + " ) " <<endl;
}
- if (shown) continue;
- shown=true;
-
const std::string prefix(exportDS ? "" : "DS = ");
cout<<prefix<<zone.toString()<<" IN DS "<<makeDSFromDNSKey(zone, key, DNSSECKeeper::SHA1).getZoneRepresentation() << " ; ( SHA1 digest )" << endl;
cout<<prefix<<zone.toString()<<" IN DS "<<makeDSFromDNSKey(zone, key, DNSSECKeeper::SHA256).getZoneRepresentation() << " ; ( SHA256 digest )" << endl;
return 0;
}
try {
- SSQLite3 db(cmds[1], true); // create=ok
+ SSQLite3 db(cmds[1], "", true); // create=ok
vector<string> statements;
stringtok(statements, sqlCreate, ";");
for(const string& statement : statements) {
LUA=65402
};
+ QType(typeenum orig) : code(orig)
+ {
+ }
+
typedef pair<string,uint16_t> namenum;
static vector<namenum> names;
--- /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
+
+#ifdef HAVE_FSTRM
+#include "dnstap.hh"
+#endif /* HAVE_FSTRM */
+
+class RecDnstapMessage : public DnstapMessage
+{
+public:
+ RecDnstapMessage(const std::string& identity, const ComboAddress* requestor, const ComboAddress* responder, bool isTCP, const char* packet, const size_t len, const struct timespec* queryTime, const struct timespec* responseTime)
+ : DnstapMessage(identity, requestor, responder, isTCP, packet, len, queryTime, responseTime) {
+ const struct dnsheader* dh = reinterpret_cast<const struct dnsheader*>(packet);
+ dnstap::Message* message = proto_message.mutable_message();
+ message->set_type(!dh->qr ? dnstap::Message_Type_RESOLVER_QUERY : dnstap::Message_Type_RESOLVER_RESPONSE);
+ }
+};
}
#endif /* HAVE_PROTOBUF */
+#ifdef HAVE_FSTRM
+typedef std::unordered_map<std::string, boost::variant<bool, uint64_t, std::string, std::vector<std::pair<int,std::string> > > > frameStreamOptions_t;
+
+static void parseFrameStreamOptions(boost::optional<frameStreamOptions_t> vars, FrameStreamExportConfig& config)
+{
+ if (!vars) {
+ return;
+ }
+
+ if (vars->count("logQueries")) {
+ config.logQueries = boost::get<bool>((*vars)["logQueries"]);
+ }
+ if (vars->count("logResponses")) {
+ config.logResponses = boost::get<bool>((*vars)["logResponses"]);
+ }
+
+ if (vars->count("bufferHint")) {
+ config.bufferHint = boost::get<uint64_t>((*vars)["bufferHint"]);
+ }
+ if (vars->count("flushTimeout")) {
+ config.flushTimeout = boost::get<uint64_t>((*vars)["flushTimeout"]);
+ }
+ if (vars->count("inputQueueSize")) {
+ config.inputQueueSize = boost::get<uint64_t>((*vars)["inputQueueSize"]);
+ }
+ if (vars->count("outputQueueSize")) {
+ config.outputQueueSize = boost::get<uint64_t>((*vars)["outputQueueSize"]);
+ }
+ if (vars->count("queueNotifyThreshold")) {
+ config.queueNotifyThreshold = boost::get<uint64_t>((*vars)["queueNotifyThreshold"]);
+ }
+ if (vars->count("reopenInterval")) {
+ config.reopenInterval = boost::get<uint64_t>((*vars)["reopenInterval"]);
+ }
+}
+#endif /* HAVE_FSTRM */
+
void loadRecursorLuaConfig(const std::string& fname, luaConfigDelayedThreads& delayedThreads)
{
LuaConfigItems lci;
});
#endif
+#ifdef HAVE_FSTRM
+ Lua.writeFunction("dnstapFrameStreamServer", [&lci](boost::variant<const std::string, const std::unordered_map<int, std::string>> servers, boost::optional<frameStreamOptions_t> vars) {
+ if (!lci.frameStreamExportConfig.enabled) {
+
+ lci.frameStreamExportConfig.enabled = true;
+
+ try {
+ if (servers.type() == typeid(std::string)) {
+ auto server = boost::get<const std::string>(servers);
+ if (!boost::starts_with(server, "/")) {
+ ComboAddress parsecheck(server);
+ }
+ lci.frameStreamExportConfig.servers.emplace_back(server);
+ }
+ else {
+ auto serversMap = boost::get<const std::unordered_map<int,std::string>>(servers);
+ for (const auto& serverPair : serversMap) {
+ lci.frameStreamExportConfig.servers.emplace_back(serverPair.second);
+ }
+ }
+
+ parseFrameStreamOptions(vars, lci.frameStreamExportConfig);
+ }
+ catch(std::exception& e) {
+ g_log<<Logger::Error<<"Error reading config for dnstap framestream logger: "<<e.what()<<endl;
+ }
+ catch(PDNSException& e) {
+ g_log<<Logger::Error<<"Error reading config for dnstap framestream logger: "<<e.reason<<endl;
+ }
+ }
+ else {
+ g_log<<Logger::Error<<"Only one dnstapFrameStreamServer() directive can be configured, we already have "<<lci.frameStreamExportConfig.servers.at(0)<<endl;
+ }
+ });
+#endif /* HAVE_FSTRM */
+
try {
Lua.executeCode(ifs);
g_luaconfs.setState(lci);
bool taggedOnly{false};
};
+struct FrameStreamExportConfig
+{
+ std::vector<string> servers;
+ bool enabled{false};
+ bool logQueries{true};
+ bool logResponses{true};
+ unsigned bufferHint{0};
+ unsigned flushTimeout{0};
+ unsigned inputQueueSize{0};
+ unsigned outputQueueSize{0};
+ unsigned queueNotifyThreshold{0};
+ unsigned reopenInterval{0};
+};
+
struct TrustAnchorFileInfo {
uint32_t interval{24};
std::string fname;
map<DNSName,std::string> negAnchors;
ProtobufExportConfig protobufExportConfig;
ProtobufExportConfig outgoingProtobufExportConfig;
+ FrameStreamExportConfig frameStreamExportConfig;
+
/* we need to increment this every time the configuration
is reloaded, so we know if we need to reload the protobuf
remote loggers */
#include "dnsrecords.hh"
#include "version.hh"
+#ifdef HAVE_LUA_RECORDS
+#include "minicurl.hh"
+#endif /* HAVE_LUA_RECORDS */
+
time_t s_starttime;
string s_programname="pdns"; // used in packethandler.cc
/* setup rng */
dns_random_init();
+#ifdef HAVE_LUA_RECORDS
+ MiniCurl::init();
+#endif /* HAVE_LUA_RECORDS */
+
if(!::arg()["load-modules"].empty()) {
vector<string> modules;
}
}
- if(!::arg().mustDo("disable-tcp"))
- TN=new TCPNameserver;
+ TN = make_unique<TCPNameserver>();
}
catch(const ArgException &A) {
g_log<<Logger::Error<<"Fatal error: "<<A.reason<<endl;
doctrees
latex
PowerDNS-Recursor.pdf
+/*.pb.cc
+/*.pb.h
-DNODCACHEDIR=\"$(nodcachedir)\"
endif
+if FSTRM
+AM_CPPFLAGS += \
+ $(FSTRM_CFLAGS)
+endif
+
AM_LDFLAGS = \
$(PROGRAM_LDFLAGS) \
$(THREADFLAGS)
NOTICE \
opensslsigners.hh opensslsigners.cc \
portsmplexer.cc \
+ dnstap.proto dnstap.cc dnstap.hh fstrm_logger.cc fstrm_logger.hh rec-dnstap.hh \
rrd/* \
html incfiles \
test_libcrypto \
ednsoptions.cc ednsoptions.hh \
ednssubnet.cc ednssubnet.hh \
filterpo.cc filterpo.hh \
+ fstrm_logger.cc fstrm_logger.hh \
gettime.cc gettime.hh \
gss_context.cc gss_context.hh \
iputils.hh iputils.cc \
dnsmessage.pb.cc: dnsmessage.proto
$(AM_V_GEN)$(PROTOC) --cpp_out=./ $<
+if FSTRM
+dnstap.pb.cc: dnstap.proto
+ $(AM_V_GEN)$(PROTOC) -I$(srcdir) --cpp_out=./ $<
+endif
+
+
BUILT_SOURCES += dnsmessage.pb.cc
pdns_recursor_LDADD += $(PROTOBUF_LIBS)
nodist_pdns_recursor_SOURCES = dnsmessage.pb.cc dnsmessage.pb.h
+nodist_testrunner_SOURCES = dnsmessage.pb.cc dnsmessage.pb.h
+
+if FSTRM
+BUILT_SOURCES += dnstap.pb.cc
+pdns_recursor.$(OBJEXT): dnstap.pb.cc dnsmessage.pb.cc
+testrunner$(OBJEXT): dnstap.pb.cc dnsmessage.pb.cc
+nodist_pdns_recursor_SOURCES += dnstap.pb.cc dnstap.pb.h
+nodist_testrunner_SOURCES += dnstap.pb.cc dnstap.pb.h
+else
pdns_recursor.$(OBJEXT): dnsmessage.pb.cc
+testrunner$(OBJEXT): dnsmessage.pb.cc
+endif
-nodist_testrunner_SOURCES = dnsmessage.pb.cc dnsmessage.pb.h
testrunner_LDADD += $(PROTOBUF_LIBS)
-testrunner$(OBJEXT): dnsmessage.pb.cc
endif
endif
+if FSTRM
+pdns_recursor_SOURCES += \
+ dnstap.cc dnstap.hh rec-dnstap.hh
+
+pdns_recursor_LDADD += \
+ $(FSTRM_LIBS)
+endif
+
rec_control_SOURCES = \
arguments.cc arguments.hh \
dnsname.hh dnsname.cc \
[nodcachedir="$withval"]
)
+PDNS_CHECK_DNSTAP
+
AC_MSG_CHECKING([whether we will enable compiler security checks])
AC_ARG_ENABLE([hardening],
[AS_HELP_STRING([--disable-hardening], [disable compiler security checks @<:@default=no@:>@])],
[AC_MSG_NOTICE([nod: yes])],
[AC_MSG_NOTICE([nod: no])]
)
+AM_COND_IF([FSTRM],
+ [AC_MSG_NOTICE([dnstap: yes])],
+ [AC_MSG_NOTICE([dnstap: no])]
+)
AC_MSG_NOTICE([Context library: $pdns_context_library])
AC_MSG_NOTICE([])
--- /dev/null
+../dnstap.cc
\ No newline at end of file
--- /dev/null
+../dnstap.hh
\ No newline at end of file
--- /dev/null
+../dnstap.proto
\ No newline at end of file
Changelogs for 4.1.x
====================
+.. changelog::
+ :version: 4.1.13
+ :released: 21st of May 2019
+
+ .. change::
+ :tags: Improvements, Performance
+ :pullreq: 7673
+ :tickets: 7661
+
+ Add the ``disable-real-memory-usage`` setting to skip expensive
+ collection of detailed memory usage info.
+
+ .. change::
+ :tags: Bug Fixes, DNSSEC
+ :pullreq: 7816
+ :tickets: 7714
+
+ Fix DNSSEC validation of wildcards expanded onto themselves.
+
.. changelog::
:version: 4.1.12
:released: 2nd of April 2019
Changelogs for 4.2.x
====================
+.. changelog::
+ :version: 4.2.0-rc1
+ :released: 23th of May 2019
+
+ .. change::
+ :tags: Bug Fixes, SNMP
+ :pullreq: 7826
+
+ Fix the detection of ``snmp_select_info2()``.
+
+ .. change::
+ :tags: Bug Fixes, Internals
+ :pullreq: 7813
+ :tickets: 7272
+
+ Ensure a valid range to ``string()`` in ``PacketReader::getUnquotedText()``
+
+ .. change::
+ :tags: Improvements, SNMP
+ :pullreq: 7818
+
+ Use ``net-snmp-config --netsnmp-agent-libs`` instead of ``--agent-libs``.
+
.. changelog::
:version: 4.2.0-beta1
:released: 7th of May 2019
The protocol buffers message types can be found in the `dnsmessage.proto <https://github.com/PowerDNS/pdns/blob/master/pdns/dnsmessage.proto>`_ file and is included here:
.. literalinclude:: ../../../dnsmessage.proto
+
+Logging in ``dnstap`` format using framestreams
+-----------------------------------------------
+Define the following function to enable logging of outgoing queries and/or responses in ``dnstap`` format.
+The recursor must have been built with configure ``--enable-dnstap`` to make this feature available.
+
+.. function:: dnstapFrameStreamServer(servers, [, options])
+
+ .. versionadded:: 4.3.0
+
+ Send dnstap formatted message to one or more framestream servers for outgoing queries and/or incoming responses.
+
+ :param servers: Either a pathname of a unix domain socket starting with a slash or the IP:port to connect to, or a list of those. If more than one server is configured, all messages are sent to every server.
+ :type servers: string or list of strings
+ :param table options: A table with ``key=value`` pairs with options.
+
+ Options:
+
+ * ``logQueries=true``: bool - log oputgoing queries
+ * ``logResponses=true``: bool - log incoming responses
+
+ The follwing options apply to the settings of the framestream library. Refer to the documentation of that
+ library for the default values, exact description and allowable values for these options.
+ For all these options, absence or a zero value has the effect of using the library-provided default value.
+
+ * ``bufferHint=0``: unsigned
+ * ``flushTimeout=0``: unsigned
+ * ``inputQueueSize=0``: unsigned
+ * ``outputQueueSize=0``: unsigned
+ * ``queueNotifyThreshold=0``: unsigned
+ * ``reopenInterval=0``: unsigned
+
--- /dev/null
+Newly Observed Domain Tracking
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+A common security technique for detecting domains that may be suspicious or be associated with bad actors such as hosting malware, phishing or botnet command and control, is to investigate domains that haven't been seen before, i.e. are newly observed.
+
+Deciding whether a domain is truly a new domain would involve deterministic methods, such as maintaining a database of all domains ever seen, and comparing all domain lookups against that database. Such a mechanism would not be scalable in a recursor, and so is best suited to offline analysis. However, determining candidate domains for such an offline service is a problem that can be solved in the recursor, given that sending all domain lookups to such an offline service would still be prohibitely costly, and given that the true number of newly observed domains is likely to be relatively small in a given time period.
+
+A simple method to determine a candidate domain would simply be to check if the domain was not in the recursor cache; indeed this is a method used by many security researchers. However, while that does produce a smaller list of candidate domains, cache misses are still relatively common, particularly in deployments where techniques such as EDNS client-subnet are used.
+
+Therefore, a feature has been developed for the recursor which uses probablistic data structures (specifically a Stable Bloom Filter (SBF): [http://webdocs.cs.ualberta.ca/~drafiei/papers/DupDet06Sigmod.pdf]). This recursor feature is named "Newly Observed Domain" or "NOD" for short.
+
+The use of a probablistic data structure means that the memory and CPU usage for the NOD feature is minimal, however it does mean that there can be false positives (a domain flagged as new when it is not), and false negatives (a domain that is new is not detected). The size of the SBF data structure can be tuned to reduce the FP/FN rate, although it is created with a default size (67108864 cells) that should provide a reasonably low FP/FN rate. To configure a different size use the ``new-domain-db-size`` setting to specify a higher or lower cell count. Each cell consumes 1-bit of RAM (per recursor thread) and 1-byte of disk space.
+
+NOD is disabled by default, and must be enabled through the use of the following setting in recursor.conf:
+
+``new-domain-tracking=yes``
+
+Once enabled the recursor will keep track of previously seen domains using the SBF data structure, which is periodically persisted to the directory specified in the ``new-domain-history-dir``, which defaults to /var/lib/pdns-recursor/nod.
+
+Administrators may wish to prevent certain domains or subdomains from ever triggering the NOD algorithm, in which case those domains must be added to the ``new-domain-whitelist`` setting as a comma separated list. No domain (or subdomain of a domain) listed will be considered a newly observed domain.
+
+There are several ways to receive the information about newly observed domains:
+
+Logging
++++++++
+
+The setting ``new-domain-log`` is enabled by default once the NOD feature is enabled, and will log the newly observed domain to the recursor logfile.
+
+DNS Lookup
+++++++++++
+
+The setting ``new-domain-lookup=<base domain>`` will cause the recursor to isse a DNS A record lookup to ``<newly observed domain>.<base domain>``. This can be a suitable method to send NOD data to an offsite or remote partner, however care should be taken to ensure that data is not leaked inadvertently.
+
+Protobuf Logging
+++++++++++++++++
+
+If both NOD and protobuf logging are enabled, then the ``newlyObservedDomain`` field of the protobuf message emitted by the recursor will be set to true. Additionally newly observed domains will be tagged in the protobuf stream using the tag ``pdns-nod`` by default. The setting ``new-domain-pb-tag=<tag>`` can be used to alter the tag.
+
+Unique Domain Response
+~~~~~~~~~~~~~~~~~~~~~~
+
+A similar feature to NOD is Unique Domain Response (UDR). This feature uses the same probablistic data structures as NOD to store information about unique responses for a given lookup domain. Determining if a particular response is unique for a given lookup domain is extremly useful for determining potential security issues such as:
+
+* Fast-Flux Domain Names
+* Cache-Poisoning Attacks
+* Botnet Command and Control Servers
+ etc.
+
+This is because well-behaved domains tend to return fairly stable results to DNS record lookups, and thus domains which don't exhibit this behaviour may be suspsicious or may indicate a domain under attack.
+
+UDR is disabled by default - to enable it, set ``unique-response-tracking=yes`` in recursor.conf.
+
+The data is persisted to /var/log/pdns-recursor/udr by default, which can be changed with the setting ``unique-response-history-dir=<new directory>``.
+
+The SBF (which is maintained separately per recursor thread) cell size defaults to 67108864, which can be changed using the setting ``unique-response-db-size``. The same caveats regarding FPs/FNs apply as for NOD.
+
+Similarly to NOD, unique domain responses can be tracked using several mechanisms:
+
+Logging
++++++++
+
+The setting ``unique-response-log`` is enabled by default once the NOD feature is enabled, and will log the newly observed domain to the recursor logfile.
+
+Protobuf Logging
+++++++++++++++++
+
+If both UDR and protobuf logging are enabled, then unique domain responses will be tagged in the protobuf stream using the tag ``pdns-udr`` by default. The setting ``unique-response-pb-tag=<tag>`` can be used to alter the tag.
.. _securitypolling:
.. include:: common/secpoll.rst
+
+.. _nod_udr:
+
+.. include:: nod_udr.rst
--- /dev/null
+../fstrm_logger.cc
\ No newline at end of file
--- /dev/null
+../fstrm_logger.hh
\ No newline at end of file
<!-- ========================= CONTENT ========================= -->
<div class="topbar">
- <label>API Key: <input type="password" id="password" value=""></label>
- <button>Submit</button><!-- Just a trick to make the user click outside of the input field -->
- <span id="connection-status" style="display:none">No server connection or incorrect key</span>
+ <span id="connection-status" style="display:none">No server connection</span>
<span id="connection-error"></span>
</div>
$('#' + name).html(h);
};
- var password = $("#password").val();
- $("#password").change(function (e) {
- password = $("#password").val();
- update();
- });
-
var qpsgraph = new Rickshaw.Graph({
element: document.getElementById("qpschart"),
width: 400,
var jsonstatParams = function (command, name, filtered) {
var d = {
- 'api-key': password,
'command': command,
'name': name
};
function update() {
$.ajax({
- url: 'api/v1/servers/localhost/statistics?api-key=' + password,
+ url: 'api/v1/servers/localhost/statistics',
type: 'GET',
dataType: 'json',
success: function (adata, x, y) {
if (!version) {
$.ajax({
- url: 'api/v1/servers/localhost?api-key=' + password, type: 'GET', dataType: 'json',
+ url: 'api/v1/servers/localhost', type: 'GET', dataType: 'json',
success: function (data) {
version = "PowerDNS " + data["daemon_type"] + " " + data["version"];
}
--- /dev/null
+../../../m4/pdns_check_dnstap.m4
\ No newline at end of file
--- /dev/null
+../rec-dnstap.hh
\ No newline at end of file
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, const std::shared_ptr<std::vector<std::unique_ptr<RemoteLogger>>>& outgoingLoggers, const std::set<uint16_t>& exportTypes, LWResult* res, bool* chained)
+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, const std::shared_ptr<std::vector<std::unique_ptr<RemoteLogger>>>& outgoingLoggers, const std::shared_ptr<std::vector<std::unique_ptr<FrameStreamLogger>>>& fstrmLoggers,const std::set<uint16_t>& exportTypes, LWResult* res, bool* chained)
{
return 0;
}
DNSResourceRecord rec;
di.backend->lookup(QType(QType::ANY), rr->d_name, nullptr, di.id);
while (di.backend->get(rec)) {
- if (rec.qtype != QType::CNAME && rec.qtype != QType::RRSIG) {
+ if (rec.qtype != QType::CNAME && rec.qtype != QType::ENT && rec.qtype != QType::RRSIG) {
// leave database handle in a consistent state
while (di.backend->get(rec))
;
len = htons(packet.size());
if(sock.write((char *) &len, 2) != 2)
throw PDNSException("tcp write failed");
- sock.writen(string((char*)&*packet.begin(), (char*)&*packet.end()));
+ sock.writen(string((char*)&packet[0], packet.size()));
if(sock.read((char *) &len, 2) != 2)
throw PDNSException("tcp read failed");
if(sock.write((char *) &len, 2) != 2)
throw PDNSException("tcp write failed");
- sock.writen(string((char*)&*packet.begin(), (char*)&*packet.end()));
+ sock.writen(string(packet.begin(), packet.end()));
bool isNSEC3 = false;
int soacount=0;
#include "statbag.hh"
#include <boost/array.hpp>
#include "ednssubnet.hh"
+
+#ifdef HAVE_LIBCURL
+#include "minicurl.hh"
+#endif
+
StatBag S;
bool hidettl=false;
void usage() {
cerr<<"sdig"<<endl;
- cerr<<"Syntax: sdig IP-ADDRESS PORT QUESTION QUESTION-TYPE [dnssec] [recurse] [showflags] [hidesoadetails] [hidettl] [tcp] [ednssubnet SUBNET/MASK] [xpf XPFDATA]"<<endl;
+ cerr<<"Syntax: sdig IP-ADDRESS-OR-DOH-URL PORT QUESTION QUESTION-TYPE [dnssec] [ednssubnet SUBNET/MASK] [hidesoadetails] [hidettl] [recurse] [showflags] [tcp] [xpf XPFDATA]"<<endl;
}
const string nameForClass(uint16_t qclass, uint16_t qtype)
bool tcp=false;
bool showflags=false;
bool hidesoadetails=false;
+ bool doh=false;
boost::optional<Netmask> ednsnm;
uint16_t xpfcode = 0, xpfversion = 0, xpfproto = 0;
char *xpfsrc = NULL, *xpfdst = NULL;
}
string reply;
- ComboAddress dest(argv[1] + (*argv[1]=='@'), atoi(argv[2]));
+ string question(packet.begin(), packet.end());
+ ComboAddress dest;
+ if(*argv[1]=='h') {
+ doh = true;
+ }
+ else {
+ dest = ComboAddress(argv[1] + (*argv[1]=='@'), atoi(argv[2]));
+ }
- if(tcp) {
+ if(doh) {
+#ifdef HAVE_LIBCURL
+ MiniCurl mc;
+ MiniCurl::MiniCurlHeaders mch;
+ mch.insert(std::make_pair("Content-Type", "application/dns-message"));
+ mch.insert(std::make_pair("Accept", "application/dns-message"));
+ reply = mc.postURL(argv[1], question, mch);
+#else
+ throw PDNSException("please link sdig against libcurl for DoH support");
+#endif
+ }
+ else if(tcp) {
Socket sock(dest.sin4.sin_family, SOCK_STREAM);
sock.connect(dest);
uint16_t len;
if(sock.write((char *) &len, 2) != 2)
throw PDNSException("tcp write failed");
- sock.writen(string((char*)&*packet.begin(), (char*)&*packet.end()));
+ sock.writen(question);
if(sock.read((char *) &len, 2) != 2)
throw PDNSException("tcp read failed");
else //udp
{
Socket sock(dest.sin4.sin_family, SOCK_DGRAM);
- sock.sendTo(string((char*)&*packet.begin(), (char*)&*packet.end()), dest);
+ sock.sendTo(question, dest);
int result=waitForData(sock.getHandle(), 10);
if(result < 0)
throw std::runtime_error("Error waiting for data: "+string(strerror(errno)));
cerr<<"EDNS Subnet response: "<<reso.source.toString()<<", scope: "<<reso.scope.toString()<<", family = "<<reso.scope.getNetwork().sin4.sin_family<<endl;
}
}
-
+ else if(iter->first == EDNSOptionCode::PADDING) {
+ cerr<<"EDNS Padding size: "<<(iter->second.size())<<endl;
+ }
else {
cerr<<"Have unknown option "<<(int)iter->first<<endl;
}
return true;
}
+
+DNSZoneRecord makeEditedDNSZRFromSOAData(DNSSECKeeper& dk, const SOAData& sd, DNSResourceRecord::Place place) {
+ SOAData edited = sd;
+ edited.serial = calculateEditSOA(sd.serial, dk, sd.qname);
+
+ DNSRecord soa;
+ soa.d_name = sd.qname;
+ soa.d_type = QType::SOA;
+ soa.d_ttl = sd.ttl;
+ soa.d_place = place;
+ soa.d_content = makeSOAContent(edited);
+
+ DNSZoneRecord dzr;
+ dzr.domain_id = sd.domain_id;
+ dzr.signttl = sd.ttl;
+ dzr.auth = true;
+ dzr.dr = soa;
+
+ return dzr;
+}
};
// Constructor.
-SSQLite3::SSQLite3( const std::string & database, bool creat )
+SSQLite3::SSQLite3( const std::string & database, const std::string & journalmode, bool creat )
{
if (access( database.c_str(), F_OK ) == -1){
if (!creat)
m_dolog = 0;
m_in_transaction = false;
sqlite3_busy_handler(m_pDB, busyHandler, 0);
+
+ if(journalmode.length())
+ execute("PRAGMA journal_mode="+journalmode);
}
void SSQLite3::setLog(bool state)
protected:
public:
//! Constructor.
- SSQLite3( const std::string & database, bool creat=false );
+ SSQLite3( const std::string & database, const std::string & journalmode, bool creat=false);
//! Destructor.
~SSQLite3();
}
}
- if(argc < 2) {
+ if(argc <= 1) {
usage();
exit(EXIT_FAILURE);
}
+ string type(argc == 2 ? "A" : argv[2]);
+
::arg().set("resolver","Use this resolver for ALIAS and the internal stub resolver")="no";
reportAllTypes();
vector<DNSZoneRecord> ret;
- int res=stubDoResolve(DNSName(argv[1]), DNSRecordContent::TypeToNumber(argv[2]), ret);
+ int res=stubDoResolve(DNSName(argv[1]), DNSRecordContent::TypeToNumber(type), ret);
cout<<"res: "<<res<<endl;
for(const auto& r : ret) {
ret = d_asyncResolve(ip, sendQname, type, doTCP, sendRDQuery, EDNSLevel, now, srcmask, ctx, res, chained);
}
else {
- ret=asyncresolve(ip, sendQname, type, doTCP, sendRDQuery, EDNSLevel, now, srcmask, ctx, d_outgoingProtobufServers, luaconfsLocal->outgoingProtobufExportConfig.exportTypes, res, chained);
+ ret=asyncresolve(ip, sendQname, type, doTCP, sendRDQuery, EDNSLevel, now, srcmask, ctx, d_outgoingProtobufServers, d_frameStreamServers, luaconfsLocal->outgoingProtobufExportConfig.exportTypes, res, chained);
}
if(ret < 0) {
return ret; // transport error, nothing to learn here
vector<ComboAddress> SyncRes::getAddrs(const DNSName &qname, unsigned int depth, set<GetBestNSAnswer>& beenthere, bool cacheOnly)
{
typedef vector<DNSRecord> res_t;
- res_t res;
-
typedef vector<ComboAddress> ret_t;
ret_t ret;
- QType type;
bool oldCacheOnly = d_cacheonly;
bool oldRequireAuthData = d_requireAuthData;
bool oldValidationRequested = d_DNSSECValidationRequested;
d_DNSSECValidationRequested = false;
d_cacheonly = cacheOnly;
- for(int j=1; j<2+s_doIPv6; j++)
- {
- bool done=false;
- switch(j) {
- case 0:
- type = QType::ANY;
- break;
- case 1:
- type = QType::A;
- break;
- case 2:
- type = QType::AAAA;
- break;
- }
-
- vState newState = Indeterminate;
- if(!doResolve(qname, type, res,depth+1, beenthere, newState) && !res.empty()) { // this consults cache, OR goes out
- for(res_t::const_iterator i=res.begin(); i!= res.end(); ++i) {
- if(i->d_type == QType::A || i->d_type == QType::AAAA) {
- if(auto rec = getRR<ARecordContent>(*i))
- ret.push_back(rec->getCA(53));
- else if(auto aaaarec = getRR<AAAARecordContent>(*i))
- ret.push_back(aaaarec->getCA(53));
- done=true;
+ vState newState = Indeterminate;
+ res_t resv4;
+ // If IPv4 ever becomes second class, we should revisit this
+ if (doResolve(qname, QType::A, resv4, depth+1, beenthere, newState) == 0) { // this consults cache, OR goes out
+ for (auto const &i : resv4) {
+ if (i.d_type == QType::A) {
+ if (auto rec = getRR<ARecordContent>(i)) {
+ ret.push_back(rec->getCA(53));
}
}
}
- if(done) {
- if(j==1 && s_doIPv6) { // we got an A record, see if we have some AAAA lying around
- vector<DNSRecord> cset;
- if(t_RC->get(d_now.tv_sec, qname, QType(QType::AAAA), false, &cset, d_cacheRemote) > 0) {
- for(auto k=cset.cbegin();k!=cset.cend();++k) {
- if(k->d_ttl > (unsigned int)d_now.tv_sec ) {
- if (auto drc = getRR<AAAARecordContent>(*k)) {
- ComboAddress ca=drc->getCA(53);
- ret.push_back(ca);
- }
- }
- }
- }
+ }
+ if (s_doIPv6) {
+ if (ret.empty()) {
+ // We did not find IPv4 addresses, try to get IPv6 ones
+ newState = Indeterminate;
+ res_t resv6;
+ if (doResolve(qname, QType::AAAA, resv6, depth+1, beenthere, newState) == 0) { // this consults cache, OR goes out
+ for (const auto &i : resv6) {
+ if (i.d_type == QType::AAAA) {
+ if (auto rec = getRR<AAAARecordContent>(i))
+ ret.push_back(rec->getCA(53));
+ }
+ }
+ }
+ } else {
+ // We have some IPv4 records, don't bother with going out to get IPv6, but do consult the cache
+ // Once IPv6 adoption matters, this needs to be revisited
+ res_t cset;
+ if (t_RC->get(d_now.tv_sec, qname, QType(QType::AAAA), false, &cset, d_cacheRemote) > 0) {
+ for (const auto &i : cset) {
+ if (i.d_ttl > (unsigned int)d_now.tv_sec ) {
+ if (auto rec = getRR<AAAARecordContent>(i)) {
+ ret.push_back(rec->getCA(53));
+ }
+ }
+ }
}
- break;
}
}
t_sstorage.nsSpeeds[qname].purge(speeds);
if(ret.size() > 1) {
- random_shuffle(ret.begin(), ret.end(), dns_random);
+ random_shuffle(ret.begin(), ret.end());
speedOrderCA so(speeds);
stable_sort(ret.begin(), ret.end(), so);
*flawedNSSet = false;
if(t_RC->get(d_now.tv_sec, subdomain, QType(QType::NS), false, &ns, d_cacheRemote) > 0) {
+ bestns.reserve(ns.size());
+
for(auto k=ns.cbegin();k!=ns.cend(); ++k) {
if(k->d_ttl > (unsigned int)d_now.tv_sec ) {
vector<DNSRecord> aset;
struct speedOrder
{
- speedOrder(map<DNSName,double> &speeds) : d_speeds(speeds) {}
- bool operator()(const DNSName &a, const DNSName &b) const
+ bool operator()(const std::pair<DNSName, double> &a, const std::pair<DNSName, double> &b) const
{
- return d_speeds[a] < d_speeds[b];
+ return a.second < b.second;
}
- map<DNSName, double>& d_speeds;
};
-inline vector<DNSName> SyncRes::shuffleInSpeedOrder(NsSet &tnameservers, const string &prefix)
+inline std::vector<std::pair<DNSName, double>> SyncRes::shuffleInSpeedOrder(NsSet &tnameservers, const string &prefix)
{
- vector<DNSName> rnameservers;
+ std::vector<std::pair<DNSName, double>> rnameservers;
rnameservers.reserve(tnameservers.size());
for(const auto& tns: tnameservers) {
- rnameservers.push_back(tns.first);
+ double speed = t_sstorage.nsSpeeds[tns.first].get(&d_now);
+ rnameservers.push_back({tns.first, speed});
if(tns.first.empty()) // this was an authoritative OOB zone, don't pollute the nsSpeeds with that
return rnameservers;
}
- map<DNSName, double> speeds;
- for(const auto& val: rnameservers) {
- double speed;
- speed=t_sstorage.nsSpeeds[val].get(&d_now);
- speeds[val]=speed;
- }
- random_shuffle(rnameservers.begin(),rnameservers.end(), dns_random);
- speedOrder so(speeds);
+ random_shuffle(rnameservers.begin(),rnameservers.end());
+ speedOrder so;
stable_sort(rnameservers.begin(),rnameservers.end(), so);
if(doLog()) {
LOG(prefix<<"Nameservers: ");
- for(vector<DNSName>::const_iterator i=rnameservers.begin();i!=rnameservers.end();++i) {
- if(i!=rnameservers.begin()) {
+ for(auto i=rnameservers.begin();i!=rnameservers.end();++i) {
+ if(i!=rnameservers.begin()) {
LOG(", ");
if(!((i-rnameservers.begin())%3)) {
LOG(endl<<prefix<<" ");
}
}
- LOG(i->toLogString()<<"(" << (boost::format("%0.2f") % (speeds[*i]/1000.0)).str() <<"ms)");
+ LOG(i->first.toLogString()<<"(" << (boost::format("%0.2f") % (i->second/1000.0)).str() <<"ms)");
}
LOG(endl);
}
speed=t_sstorage.nsSpeeds[nsName].get(&d_now);
speeds[val]=speed;
}
- random_shuffle(nameservers.begin(),nameservers.end(), dns_random);
+ random_shuffle(nameservers.begin(),nameservers.end());
speedOrderCA so(speeds);
stable_sort(nameservers.begin(),nameservers.end(), so);
return false;
}
-vector<ComboAddress> SyncRes::retrieveAddressesForNS(const std::string& prefix, const DNSName& qname, vector<DNSName >::const_iterator& tns, const unsigned int depth, set<GetBestNSAnswer>& beenthere, const vector<DNSName >& rnameservers, NsSet& nameservers, bool& sendRDQuery, bool& pierceDontQuery, bool& flawedNSSet, bool cacheOnly)
+vector<ComboAddress> SyncRes::retrieveAddressesForNS(const std::string& prefix, const DNSName& qname, std::vector<std::pair<DNSName, double>>::const_iterator& tns, const unsigned int depth, set<GetBestNSAnswer>& beenthere, const vector<std::pair<DNSName, double>>& rnameservers, NsSet& nameservers, bool& sendRDQuery, bool& pierceDontQuery, bool& flawedNSSet, bool cacheOnly)
{
vector<ComboAddress> result;
- if(!tns->empty()) {
- LOG(prefix<<qname<<": Trying to resolve NS '"<<*tns<< "' ("<<1+tns-rnameservers.begin()<<"/"<<(unsigned int)rnameservers.size()<<")"<<endl);
- result = getAddrs(*tns, depth+2, beenthere, cacheOnly);
+ if(!tns->first.empty()) {
+ LOG(prefix<<qname<<": Trying to resolve NS '"<<tns->first<< "' ("<<1+tns-rnameservers.begin()<<"/"<<(unsigned int)rnameservers.size()<<")"<<endl);
+ result = getAddrs(tns->first, depth+2, beenthere, cacheOnly);
pierceDontQuery=false;
}
else {
LOG(prefix<<qname<<": Domain has hardcoded nameserver");
- if(nameservers[*tns].first.size() > 1) {
+ if(nameservers[tns->first].first.size() > 1) {
LOG("s");
}
LOG(endl);
- sendRDQuery = nameservers[*tns].second;
- result = shuffleForwardSpeed(nameservers[*tns].first, doLog() ? (prefix+qname.toString()+": ") : string(), sendRDQuery);
+ sendRDQuery = nameservers[tns->first].second;
+ result = shuffleForwardSpeed(nameservers[tns->first].first, doLog() ? (prefix+qname.toString()+": ") : string(), sendRDQuery);
pierceDontQuery=true;
}
return result;
if(lwr.d_tcbit) {
*truncated = true;
- if (doTCP && !dontThrottle) {
+ if (doTCP) {
LOG(prefix<<qname<<": truncated bit set, over TCP?"<<endl);
- /* let's treat that as a ServFail answer from this server */
- t_sstorage.throttle.throttle(d_now.tv_sec, boost::make_tuple(remoteIP, qname, qtype.getCode()), 60, 3);
+ if (!dontThrottle) {
+ /* let's treat that as a ServFail answer from this server */
+ t_sstorage.throttle.throttle(d_now.tv_sec, boost::make_tuple(remoteIP, qname, qtype.getCode()), 60, 3);
+ }
return false;
}
+ LOG(prefix<<qname<<": truncated bit set, over UDP"<<endl);
return true;
}
LOG(endl);
for(;;) { // we may get more specific nameservers
- vector<DNSName > rnameservers = shuffleInSpeedOrder(nameservers, doLog() ? (prefix+qname.toString()+": ") : string() );
+ auto rnameservers = shuffleInSpeedOrder(nameservers, doLog() ? (prefix+qname.toString()+": ") : string() );
for(auto tns=rnameservers.cbegin();;++tns) {
if(tns==rnameservers.cend()) {
bool cacheOnly = false;
// this line needs to identify the 'self-resolving' behaviour
- if(qname == *tns && (qtype.getCode() == QType::A || qtype.getCode() == QType::AAAA)) {
+ if(qname == tns->first && (qtype.getCode() == QType::A || qtype.getCode() == QType::AAAA)) {
/* we might have a glue entry in cache so let's try this NS
but only if we have enough in the cache to know how to reach it */
LOG(prefix<<qname<<": Using NS to resolve itself, but only using what we have in cache ("<<(1+tns-rnameservers.cbegin())<<"/"<<rnameservers.size()<<")"<<endl);
bool sendRDQuery=false;
boost::optional<Netmask> ednsmask;
LWResult lwr;
- const bool wasForwarded = tns->empty() && (!nameservers[*tns].first.empty());
+ const bool wasForwarded = tns->first.empty() && (!nameservers[tns->first].first.empty());
int rcode = RCode::NoError;
bool gotNewServers = false;
- if(tns->empty() && !wasForwarded) {
+ if(tns->first.empty() && !wasForwarded) {
LOG(prefix<<qname<<": Domain is out-of-band"<<endl);
/* setting state to indeterminate since validation is disabled for local auth zone,
and Insecure would be misleading. */
remoteIPs = retrieveAddressesForNS(prefix, qname, tns, depth, beenthere, rnameservers, nameservers, sendRDQuery, pierceDontQuery, flawedNSSet, cacheOnly);
if(remoteIPs.empty()) {
- LOG(prefix<<qname<<": Failed to get IP for NS "<<*tns<<", trying next if available"<<endl);
+ LOG(prefix<<qname<<": Failed to get IP for NS "<<tns->first<<", trying next if available"<<endl);
flawedNSSet=true;
continue;
}
else {
bool hitPolicy{false};
- LOG(prefix<<qname<<": Resolved '"<<auth<<"' NS "<<*tns<<" to: ");
+ LOG(prefix<<qname<<": Resolved '"<<auth<<"' NS "<<tns->first<<" to: ");
for(remoteIP = remoteIPs.cbegin(); remoteIP != remoteIPs.cend(); ++remoteIP) {
if(remoteIP != remoteIPs.cbegin()) {
LOG(", ");
bool truncated = false;
bool gotAnswer = doResolveAtThisIP(prefix, qname, qtype, lwr, ednsmask, auth, sendRDQuery,
- *tns, *remoteIP, false, &truncated);
+ tns->first, *remoteIP, false, &truncated);
if (gotAnswer && truncated ) {
/* retry, over TCP this time */
gotAnswer = doResolveAtThisIP(prefix, qname, qtype, lwr, ednsmask, auth, sendRDQuery,
- *tns, *remoteIP, true, &truncated);
+ tns->first, *remoteIP, true, &truncated);
}
if (!gotAnswer) {
continue;
}
- LOG(prefix<<qname<<": Got "<<(unsigned int)lwr.d_records.size()<<" answers from "<<*tns<<" ("<< remoteIP->toString() <<"), rcode="<<lwr.d_rcode<<" ("<<RCode::to_s(lwr.d_rcode)<<"), aa="<<lwr.d_aabit<<", in "<<lwr.d_usec/1000<<"ms"<<endl);
+ LOG(prefix<<qname<<": Got "<<(unsigned int)lwr.d_records.size()<<" answers from "<<tns->first<<" ("<< remoteIP->toString() <<"), rcode="<<lwr.d_rcode<<" ("<<RCode::to_s(lwr.d_rcode)<<"), aa="<<lwr.d_aabit<<", in "<<lwr.d_usec/1000<<"ms"<<endl);
/* // for you IPv6 fanatics :-)
if(remoteIP->sin4.sin_family==AF_INET6)
*/
// cout<<"msec: "<<lwr.d_usec/1000.0<<", "<<g_avgLatency/1000.0<<'\n';
- t_sstorage.nsSpeeds[tns->empty()? DNSName(remoteIP->toStringWithPort()) : *tns].submit(*remoteIP, lwr.d_usec, &d_now);
+ t_sstorage.nsSpeeds[tns->first.empty()? DNSName(remoteIP->toStringWithPort()) : tns->first].submit(*remoteIP, lwr.d_usec, &d_now);
/* we have received an answer, are we done ? */
bool done = processAnswer(depth, lwr, qname, qtype, auth, wasForwarded, ednsmask, sendRDQuery, nameservers, ret, luaconfsLocal->dfe, &gotNewServers, &rcode, state);
#ifdef HAVE_PROTOBUF
#include <boost/uuid/uuid.hpp>
+#ifdef HAVE_FSTRM
+#include "fstrm_logger.hh"
+#endif /* HAVE_FSTRM */
#endif
extern GlobalStateHolder<SuffixMatchNode> g_dontThrottleNames;
}
#endif
+#ifdef HAVE_FSTRM
+ void setFrameStreamServers(std::shared_ptr<std::vector<std::unique_ptr<FrameStreamLogger>>>& servers)
+ {
+ d_frameStreamServers = servers;
+ }
+#endif /* HAVE_FSTRM */
+
void setAsyncCallback(asyncresolve_t func)
{
d_asyncResolve = func;
void getBestNSFromCache(const DNSName &qname, const QType &qtype, vector<DNSRecord>&bestns, bool* flawedNSSet, unsigned int depth, set<GetBestNSAnswer>& beenthere);
DNSName getBestNSNamesFromCache(const DNSName &qname, const QType &qtype, NsSet& nsset, bool* flawedNSSet, unsigned int depth, set<GetBestNSAnswer>&beenthere);
- inline vector<DNSName> shuffleInSpeedOrder(NsSet &nameservers, const string &prefix);
+ inline vector<std::pair<DNSName, double>> shuffleInSpeedOrder(NsSet &nameservers, const string &prefix);
inline vector<ComboAddress> shuffleForwardSpeed(const vector<ComboAddress> &rnameservers, const string &prefix, const bool wasRd);
bool moreSpecificThan(const DNSName& a, const DNSName &b) const;
vector<ComboAddress> getAddrs(const DNSName &qname, unsigned int depth, set<GetBestNSAnswer>& beenthere, bool cacheOnly);
bool nameserverIPBlockedByRPZ(const DNSFilterEngine& dfe, const ComboAddress&);
bool throttledOrBlocked(const std::string& prefix, const ComboAddress& remoteIP, const DNSName& qname, const QType& qtype, bool pierceDontQuery);
- vector<ComboAddress> retrieveAddressesForNS(const std::string& prefix, const DNSName& qname, vector<DNSName >::const_iterator& tns, const unsigned int depth, set<GetBestNSAnswer>& beenthere, const vector<DNSName >& rnameservers, NsSet& nameservers, bool& sendRDQuery, bool& pierceDontQuery, bool& flawedNSSet, bool cacheOnly);
+ vector<ComboAddress> retrieveAddressesForNS(const std::string& prefix, const DNSName& qname, vector<std::pair<DNSName, double>>::const_iterator& tns, const unsigned int depth, set<GetBestNSAnswer>& beenthere, const vector<std::pair<DNSName, double>>& rnameservers, NsSet& nameservers, bool& sendRDQuery, bool& pierceDontQuery, bool& flawedNSSet, bool cacheOnly);
void sanitizeRecords(const std::string& prefix, LWResult& lwr, const DNSName& qname, const QType& qtype, const DNSName& auth, bool wasForwarded, bool rdQuery);
RCode::rcodes_ updateCacheFromRecords(unsigned int depth, LWResult& lwr, const DNSName& qname, const QType& qtype, const DNSName& auth, bool wasForwarded, const boost::optional<Netmask>, vState& state, bool& needWildcardProof, bool& gatherWildcardProof, unsigned int& wildcardLabelsCount, bool sendRDQuery);
shared_ptr<RecursorLua4> d_pdl;
boost::optional<Netmask> d_outgoingECSNetwork;
std::shared_ptr<std::vector<std::unique_ptr<RemoteLogger>>> d_outgoingProtobufServers{nullptr};
+ std::shared_ptr<std::vector<std::unique_ptr<FrameStreamLogger>>> d_frameStreamServers{nullptr};
#ifdef HAVE_PROTOBUF
boost::optional<const boost::uuids::uuid&> d_initialRequestId;
#endif
virtual size_t write(const void* buffer, size_t bufferSize, unsigned int writeTimeout) = 0;
virtual IOState tryWrite(std::vector<uint8_t>& buffer, size_t& pos, size_t toWrite) = 0;
virtual IOState tryRead(std::vector<uint8_t>& buffer, size_t& pos, size_t toRead) = 0;
+ virtual std::string getServerNameIndication() = 0;
virtual void close() = 0;
protected:
}
}
- bool writeSizeAndMsg(const void* buffer, size_t bufferSize, unsigned int writeTimeout)
+ std::string getServerNameIndication()
{
if (d_conn) {
- uint16_t size = htons(bufferSize);
- if (d_conn->write(&size, sizeof(size), writeTimeout) != sizeof(size)) {
- return false;
- }
- return (d_conn->write(buffer, bufferSize, writeTimeout) == bufferSize);
- }
- else {
- return sendSizeAndMsgWithTimeout(d_socket, bufferSize, static_cast<const char*>(buffer), writeTimeout, nullptr, nullptr, 0, 0, 0);
+ return d_conn->getServerNameIndication();
}
+ return std::string();
}
private:
bool d_auth;
};
- DNSZoneRecord makeEditedDNSZRFromSOAData(DNSSECKeeper& dk, const SOAData& sd)
- {
- SOAData edited = sd;
- edited.serial = calculateEditSOA(sd.serial, dk, sd.qname);
-
- DNSRecord soa;
- soa.d_name = sd.qname;
- soa.d_type = QType::SOA;
- soa.d_ttl = sd.ttl;
- soa.d_place = DNSResourceRecord::ANSWER;
- soa.d_content = makeSOAContent(edited);
-
- DNSZoneRecord dzr;
- dzr.auth = true;
- dzr.dr = soa;
-
- return dzr;
- }
-
shared_ptr<DNSPacket> getFreshAXFRPacket(shared_ptr<DNSPacket> q)
{
shared_ptr<DNSPacket> ret = shared_ptr<DNSPacket>(q->replyPacket());
if(d_rsock.write((char *) &len, 2) != 2)
throw PDNSException("tcp write failed");
- d_rsock.writen(string((char*)&*packet.begin(), (char*)&*packet.end()));
+ d_rsock.writen(string(packet.begin(), packet.end()));
int bread=d_rsock.read((char *) &len, 2);
if( bread <0)
"PKCS#11 " <<
#endif
#ifdef HAVE_PROTOBUF
- "protobuf " <<
+"protobuf " <<
+#endif
+#ifdef HAVE_FSTRM
+"dnstap-framestream " <<
#endif
#ifdef REMOTEBACKEND_ZEROMQ
"remotebackend-zeromq " <<
return false;
}
-static void apiWrapper(WebServer::HandlerFunction handler, HttpRequest* req, HttpResponse* resp, const string &apikey) {
+void WebServer::apiWrapper(WebServer::HandlerFunction handler, HttpRequest* req, HttpResponse* resp, bool allowPassword) {
if (optionsHandler(req, resp)) return;
resp->headers["access-control-allow-origin"] = "*";
- if (apikey.empty()) {
+ if (d_apikey.empty()) {
g_log<<Logger::Error<<req->logprefix<<"HTTP API Request \"" << req->url.path << "\": Authentication failed, API Key missing in config" << endl;
throw HttpUnauthorizedException("X-API-Key");
}
- bool auth_ok = req->compareHeader("x-api-key", apikey) || req->getvars["api-key"] == apikey;
-
+
+ bool auth_ok = req->compareHeader("x-api-key", d_apikey) || req->getvars["api-key"] == d_apikey;
+
+ if (!auth_ok && allowPassword) {
+ if (!d_webserverPassword.empty()) {
+ auth_ok = req->compareAuthorization(d_webserverPassword);
+ } else {
+ auth_ok = true;
+ }
+ }
+
if (!auth_ok) {
g_log<<Logger::Error<<req->logprefix<<"HTTP Request \"" << req->url.path << "\": Authentication by API Key failed" << endl;
throw HttpUnauthorizedException("X-API-Key");
}
}
-void WebServer::registerApiHandler(const string& url, HandlerFunction handler) {
- HandlerFunction f = boost::bind(&apiWrapper, handler, _1, _2, d_apikey);
+void WebServer::registerApiHandler(const string& url, HandlerFunction handler, bool allowPassword) {
+ HandlerFunction f = boost::bind(&WebServer::apiWrapper, this, handler, _1, _2, allowPassword);
registerBareHandler(url, f);
- d_registerApiHandlerCalled = true;
}
-static void webWrapper(WebServer::HandlerFunction handler, HttpRequest* req, HttpResponse* resp, const string &password) {
- if (!password.empty()) {
- bool auth_ok = req->compareAuthorization(password);
+void WebServer::webWrapper(WebServer::HandlerFunction handler, HttpRequest* req, HttpResponse* resp) {
+ if (!d_webserverPassword.empty()) {
+ bool auth_ok = req->compareAuthorization(d_webserverPassword);
if (!auth_ok) {
g_log<<Logger::Debug<<req->logprefix<<"HTTP Request \"" << req->url.path << "\": Web Authentication failed" << endl;
throw HttpUnauthorizedException("Basic");
}
void WebServer::registerWebHandler(const string& url, HandlerFunction handler) {
- HandlerFunction f = boost::bind(&webWrapper, handler, _1, _2, d_webserverPassword);
+ HandlerFunction f = boost::bind(&WebServer::webWrapper, this, handler, _1, _2);
registerBareHandler(url, f);
}
virtual ~WebServer() { };
void setApiKey(const string &apikey) {
- if (d_registerApiHandlerCalled) {
- throw PDNSException("registerApiHandler has been called, can not change apikey");
- }
d_apikey = apikey;
}
void setPassword(const string &password) {
- if (d_registerWebHandlerCalled) {
- throw PDNSException("registerWebHandler has been called, can not change password");
- }
d_webserverPassword = password;
}
void handleRequest(HttpRequest& request, HttpResponse& resp) const;
typedef boost::function<void(HttpRequest* req, HttpResponse* resp)> HandlerFunction;
- void registerApiHandler(const string& url, HandlerFunction handler);
+ void registerApiHandler(const string& url, HandlerFunction handler, bool allowPassword=false);
void registerWebHandler(const string& url, HandlerFunction handler);
enum class LogLevel : uint8_t {
std::shared_ptr<Server> d_server;
std::string d_apikey;
- bool d_registerApiHandlerCalled{false};
-
+ void apiWrapper(WebServer::HandlerFunction handler, HttpRequest* req, HttpResponse* resp, bool allowPassword);
std::string d_webserverPassword;
- bool d_registerWebHandlerCalled{false};
+ void webWrapper(WebServer::HandlerFunction handler, HttpRequest* req, HttpResponse* resp);
NetmaskGroup d_acl;
}
}
-static void gatherRecords(const Json container, const DNSName& qname, const QType qtype, const int ttl, vector<DNSResourceRecord>& new_records, vector<DNSResourceRecord>& new_ptrs) {
+static void gatherRecords(const string& logprefix, const Json container, const DNSName& qname, const QType qtype, const int ttl, vector<DNSResourceRecord>& new_records, vector<DNSResourceRecord>& new_ptrs) {
UeberBackend B;
DNSResourceRecord rr;
rr.qname = qname;
if ((rr.qtype.getCode() == QType::A || rr.qtype.getCode() == QType::AAAA) &&
boolFromJson(record, "set-ptr", false) == true) {
+
+ g_log<<Logger::Warning<<logprefix<<"API call uses deprecated set-ptr feature, please remove it"<<endl;
+
DNSResourceRecord ptr;
makePtr(rr, &ptr);
}
if (rrset["records"].is_array()) {
int ttl = intFromJson(rrset, "ttl");
- gatherRecords(rrset, qname, qtype, ttl, new_records, new_ptrs);
+ gatherRecords(req->logprefix, rrset, qname, qtype, ttl, new_records, new_ptrs);
}
if (rrset["comments"].is_array()) {
gatherComments(rrset, qname, qtype, new_comments);
// ttl shouldn't be part of DELETE, and it shouldn't be required if we don't get new records.
int ttl = intFromJson(rrset, "ttl");
// new_ptrs is merged.
- gatherRecords(rrset, qname, qtype, ttl, new_records, new_ptrs);
+ gatherRecords(req->logprefix, rrset, qname, qtype, ttl, new_records, new_ptrs);
for(DNSResourceRecord& rr : new_records) {
rr.domain_id = di.id;
di.backend->lookup(QType(QType::ANY), qname);
DNSResourceRecord rr;
while (di.backend->get(rr)) {
- if (qtype.getCode() == 0) {
+ if (rr.qtype.getCode() == QType::ENT) {
ent_present = true;
+ /* that's fine, we will override it */
+ continue;
}
if (qtype.getCode() != rr.qtype.getCode()
&& (exclusiveEntryTypes.count(qtype.getCode()) != 0
d_ws->bind();
// legacy dispatch
- d_ws->registerApiHandler("/jsonstat", boost::bind(&RecursorWebServer::jsonstat, this, _1, _2));
+ d_ws->registerApiHandler("/jsonstat", boost::bind(&RecursorWebServer::jsonstat, this, _1, _2), true);
d_ws->registerApiHandler("/api/v1/servers/localhost/cache/flush", &apiServerCacheFlush);
d_ws->registerApiHandler("/api/v1/servers/localhost/config/allow-from", &apiServerConfigAllowFrom);
d_ws->registerApiHandler("/api/v1/servers/localhost/config", &apiServerConfig);
d_ws->registerApiHandler("/api/v1/servers/localhost/rpzstatistics", &apiServerRPZStats);
d_ws->registerApiHandler("/api/v1/servers/localhost/search-data", &apiServerSearchData);
- d_ws->registerApiHandler("/api/v1/servers/localhost/statistics", &apiServerStatistics);
+ d_ws->registerApiHandler("/api/v1/servers/localhost/statistics", &apiServerStatistics, true);
d_ws->registerApiHandler("/api/v1/servers/localhost/zones/<id>", &apiServerZoneDetail);
d_ws->registerApiHandler("/api/v1/servers/localhost/zones", &apiServerZones);
- d_ws->registerApiHandler("/api/v1/servers/localhost", &apiServerDetail);
+ d_ws->registerApiHandler("/api/v1/servers/localhost", &apiServerDetail, true);
d_ws->registerApiHandler("/api/v1/servers", &apiServer);
d_ws->registerApiHandler("/api", &apiDiscovery);
WEBPORT = 5556
DNSPORT = 5300
APIKEY = '1234567890abcdefghijklmnopq-key'
+WEBPASSWORD = 'something'
PDNSUTIL_CMD = [os.environ.get("PDNSUTIL", "../pdns/pdnsutil"), "--config-dir=."]
NAMED_CONF_TPL = """
common_args = [
"--daemon=no", "--socket-dir=.", "--config-dir=.",
"--local-address=127.0.0.1", "--local-port="+str(DNSPORT),
- "--webserver=yes", "--webserver-port="+str(WEBPORT), "--webserver-address=127.0.0.1", "--webserver-password=something",
+ "--webserver=yes", "--webserver-port="+str(WEBPORT), "--webserver-address=127.0.0.1",
+ "--webserver-password="+WEBPASSWORD,
"--api-key="+APIKEY
]
test_env = {}
test_env.update(os.environ)
test_env.update({
+ 'WEBPASSWORD': WEBPASSWORD,
'WEBPORT': str(WEBPORT),
'APIKEY': APIKEY,
'DAEMON': daemon,
r = requests.get(self.url("/api/v1/servers/localhost"))
self.assertEquals(r.status_code, requests.codes.unauthorized)
+ def test_index_html(self):
+ r = requests.get(self.url("/"), auth=('admin', self.server_web_password))
+ self.assertEquals(r.status_code, requests.codes.ok)
+
def test_split_request(self):
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
+import requests
+import unittest
from test_helper import ApiTestCase, is_auth, is_recursor
r = self.session.get(self.url("/api/v1/servers/localhost/statistics?statistic=uptimeAAAA"))
self.assertEquals(r.status_code, 422)
self.assertIn("Unknown statistic name", r.json()['error'])
+
+ @unittest.skipIf(is_auth(), "Not applicable")
+ def test_read_statistics_using_password(self):
+ r = requests.get(self.url("/api/v1/servers/localhost/statistics"), auth=('admin', self.server_web_password))
+ self.assertEquals(r.status_code, requests.codes.ok)
+ self.assert_success_json(r)
self.assertIsNone(dbrecs[0]['ordername'])
def test_cname_at_ent_place(self):
- name, payload, zone = self.create_zone(api_rectify=True)
+ name, payload, zone = self.create_zone(dnssec=True, api_rectify=True)
rrset = {
'changetype': 'replace',
'name': 'sub2.sub1.' + name,
self.server_address = '127.0.0.1'
self.server_port = int(os.environ.get('WEBPORT', '5580'))
self.server_url = 'http://%s:%s/' % (self.server_address, self.server_port)
+ self.server_web_password = os.environ.get('WEBPASSWORD', 'MISSING')
self.session = requests.Session()
self.session.headers = {'X-API-Key': os.environ.get('APIKEY', 'changeme-key'), 'Origin': 'http://%s:%s' % (self.server_address, self.server_port)}
_confdir = 'auth'
_authPort = 5300
+ _config_params = []
+
+ _config_template_default = """
+module-dir=../regression-tests/modules
+daemon=no
+local-ipv6=
+bind-config={confdir}/named.conf
+bind-dnssec-db={bind_dnssec_db}
+socket-dir={confdir}
+cache-ttl=0
+negquery-cache-ttl=0
+query-cache-ttl=0
+log-dns-queries=yes
+log-dns-details=yes
+loglevel=9
+distributor-threads=1"""
+
+ _config_template = ""
+
_root_DS = "63149 13 1 a59da3f5c1b97fcd5fa2b3b2b0ac91d38a60d33a"
# The default SOA for zones in the authoritative servers
def generateAuthConfig(cls, confdir):
bind_dnssec_db = os.path.join(confdir, 'bind-dnssec.sqlite3')
+ params = tuple([getattr(cls, param) for param in cls._config_params])
+
with open(os.path.join(confdir, 'pdns.conf'), 'w') as pdnsconf:
- pdnsconf.write("""
-module-dir=../regression-tests/modules
-launch=bind geoip
-daemon=no
-local-ipv6=
-bind-config={confdir}/named.conf
-bind-dnssec-db={bind_dnssec_db}
-socket-dir={confdir}
-cache-ttl=0
-negquery-cache-ttl=0
-query-cache-ttl=0
-log-dns-queries=yes
-log-dns-details=yes
-loglevel=9
-geoip-database-files=../modules/geoipbackend/regression-tests/GeoLiteCity.mmdb
-edns-subnet-processing=yes
-expand-alias=yes
-resolver={prefix}.1:5301
-any-to-tcp=no
-distributor-threads=1""".format(confdir=confdir, prefix=cls._PREFIX,
- bind_dnssec_db=bind_dnssec_db))
+ pdnsconf.write(cls._config_template_default.format(
+ confdir=confdir, prefix=cls._PREFIX,
+ bind_dnssec_db=bind_dnssec_db))
+ pdnsconf.write(cls._config_template % params)
pdnsutilCmd = [os.environ['PDNSUTIL'],
'--config-dir=%s' % confdir,
class TestALIAS(AuthTest):
+ _config_template = """
+expand-alias=yes
+resolver=%s.1:5301
+any-to-tcp=no
+launch=bind
+"""
+
+ _config_params = ['_PREFIX']
+
_zones = {
'example.org': """
example.org. 3600 IN SOA {soa}
self._set_headers()
class TestLuaRecords(AuthTest):
+ _config_template = """
+geoip-database-files=../modules/geoipbackend/regression-tests/GeoLiteCity.mmdb
+edns-subnet-processing=yes
+launch=bind geoip
+any-to-tcp=no
+"""
+
_zones = {
'example.org': """
example.org. 3600 IN SOA {soa}
[custom_extensions]
basicConstraints = CA:true
keyUsage = cRLSign, keyCertSign
+
+[CA_default]
+copy_extensions = copy
encrypt_key = no
prompt = no
distinguished_name = server_distinguished_name
+req_extensions = v3_req
[server_distinguished_name]
CN = tls.tests.dnsdist.org
OU = PowerDNS.com BV
countryName = NL
+[v3_req]
+basicConstraints = CA:FALSE
+keyUsage = nonRepudiation, digitalSignature, keyEncipherment
+subjectAltName = @alt_names
+
+[alt_names]
+DNS.1 = tls.tests.dnsdist.org
+DNS.2 = powerdns.com
# Generate a new server certificate request
openssl req -new -newkey rsa:2048 -nodes -keyout server.key -out server.csr -config configServer.conf
# Sign the server cert
-openssl x509 -req -days 1 -CA ca.pem -CAkey ca.key -CAcreateserial -in server.csr -out server.pem
+openssl x509 -req -days 1 -CA ca.pem -CAkey ca.key -CAcreateserial -in server.csr -out server.pem -extfile configServer.conf -extensions v3_req
# Generate a chain
cat server.pem ca.pem > server.chain
url = 'http://127.0.0.1:' + str(self._webServerPort) + path
r = requests.get(url, headers=headers, timeout=self._webTimeout)
self.assertEquals(r.status_code, 401)
+
def testBasicAuthOnly(self):
"""
API: Basic Authentication Only
'rule-drop', 'rule-nxdomain', 'rule-refused', 'self-answered', 'downstream-timeouts',
'downstream-send-errors', 'trunc-failures', 'no-policy', 'latency0-1',
'latency1-10', 'latency10-50', 'latency50-100', 'latency100-1000',
- 'latency-slow', 'latency-avg100', 'latency-avg1000', 'latency-avg10000',
- 'latency-avg1000000', 'uptime', 'real-memory-usage', 'noncompliant-queries',
+ 'latency-slow', 'latency-sum', 'latency-count', 'latency-avg100', 'latency-avg1000',
+ 'latency-avg10000', 'latency-avg1000000', 'uptime', 'real-memory-usage', 'noncompliant-queries',
'noncompliant-responses', 'rdqueries', 'empty-queries', 'cache-hits',
'cache-misses', 'cpu-user-msec', 'cpu-sys-msec', 'fd-usage', 'dyn-blocked',
'dyn-block-nmg-size', 'rule-servfail', 'security-status']
class DNSDistDOHTest(DNSDistTest):
@classmethod
- def getDOHGetURL(cls, baseurl, query):
- wire = query.to_wire()
+ def getDOHGetURL(cls, baseurl, query, rawQuery=False):
+ if rawQuery:
+ wire = query
+ else:
+ wire = query.to_wire()
param = base64.urlsafe_b64encode(wire).decode('UTF8').rstrip('=')
return baseurl + "?dns=" + param
return conn
@classmethod
- def sendDOHQuery(cls, port, servername, baseurl, query, response=None, timeout=2.0, caFile=None, useQueue=True):
- url = cls.getDOHGetURL(baseurl, query)
+ def sendDOHQuery(cls, port, servername, baseurl, query, response=None, timeout=2.0, caFile=None, useQueue=True, rawQuery=False, customHeaders=[]):
+ url = cls.getDOHGetURL(baseurl, query, rawQuery)
conn = cls.openDOHConnection(port, caFile=caFile, timeout=timeout)
#conn.setopt(pycurl.VERBOSE, True)
conn.setopt(pycurl.URL, url)
conn.setopt(pycurl.RESOLVE, ["%s:%d:127.0.0.1" % (servername, port)])
conn.setopt(pycurl.SSL_VERIFYPEER, 1)
conn.setopt(pycurl.SSL_VERIFYHOST, 2)
+ conn.setopt(pycurl.HTTPHEADER, customHeaders)
+ if caFile:
+ conn.setopt(pycurl.CAINFO, caFile)
+
+ if response:
+ cls._toResponderQueue.put(response, True, timeout)
+
+ receivedQuery = None
+ message = None
+ data = conn.perform_rb()
+ rcode = conn.getinfo(pycurl.RESPONSE_CODE)
+ if rcode == 200:
+ message = dns.message.from_wire(data)
+
+ if useQueue and not cls._fromResponderQueue.empty():
+ receivedQuery = cls._fromResponderQueue.get(True, timeout)
+
+ return (receivedQuery, message)
+
+ @classmethod
+ def sendDOHPostQuery(cls, port, servername, baseurl, query, response=None, timeout=2.0, caFile=None, useQueue=True, rawQuery=False):
+ url = baseurl
+ conn = cls.openDOHConnection(port, caFile=caFile, timeout=timeout)
+ #conn.setopt(pycurl.VERBOSE, True)
+ conn.setopt(pycurl.URL, url)
+ conn.setopt(pycurl.RESOLVE, ["%s:%d:127.0.0.1" % (servername, port)])
+ conn.setopt(pycurl.SSL_VERIFYPEER, 1)
+ conn.setopt(pycurl.SSL_VERIFYHOST, 2)
+ conn.setopt(pycurl.POST, True)
+ data = query
+ if not rawQuery:
+ data = data.to_wire()
+
+ conn.setopt(pycurl.POSTFIELDS, data)
+
if caFile:
conn.setopt(pycurl.CAINFO, caFile)
addAction("drop.doh.tests.powerdns.com.", DropAction())
addAction("refused.doh.tests.powerdns.com.", RCodeAction(DNSRCode.REFUSED))
addAction("spoof.doh.tests.powerdns.com.", SpoofAction("1.2.3.4"))
+ addAction(HTTPHeaderRule("X-PowerDNS", "^[a]{5}$"), SpoofAction("2.3.4.5"))
+ addAction(HTTPPathRule("/PowerDNS"), SpoofAction("3.4.5.6"))
"""
_config_params = ['_testServerPort', '_dohServerPort', '_serverCert', '_serverKey']
self.checkQueryEDNSWithoutECS(expectedQuery, receivedQuery)
self.assertEquals(response, receivedResponse)
+ def testDOHSimplePOST(self):
+ """
+ DOH: Simple POST query
+ """
+ name = 'simple-post.doh.tests.powerdns.com.'
+ query = dns.message.make_query(name, 'A', 'IN', use_edns=False)
+ query.id = 0
+ expectedQuery = dns.message.make_query(name, 'A', 'IN', use_edns=True, payload=4096)
+ expectedQuery.id = 0
+ response = dns.message.make_response(query)
+ rrset = dns.rrset.from_text(name,
+ 3600,
+ dns.rdataclass.IN,
+ dns.rdatatype.A,
+ '127.0.0.1')
+ response.answer.append(rrset)
+
+ (receivedQuery, receivedResponse) = self.sendDOHPostQuery(self._dohServerPort, self._serverName, self._dohBaseURL, query, response=response, caFile=self._caCert)
+ self.assertTrue(receivedQuery)
+ self.assertTrue(receivedResponse)
+ receivedQuery.id = expectedQuery.id
+ self.assertEquals(expectedQuery, receivedQuery)
+ self.checkQueryEDNSWithoutECS(expectedQuery, receivedQuery)
+ self.assertEquals(response, receivedResponse)
+
def testDOHExistingEDNS(self):
"""
DOH: Existing EDNS
name = 'drop.doh.tests.powerdns.com.'
query = dns.message.make_query(name, 'A', 'IN')
(_, receivedResponse) = self.sendDOHQuery(self._dohServerPort, self._serverName, self._dohBaseURL, caFile=self._caCert, query=query, response=None, useQueue=False)
- print(receivedResponse)
self.assertEquals(receivedResponse, None)
def testRefused(self):
(_, receivedResponse) = self.sendDOHQuery(self._dohServerPort, self._serverName, self._dohBaseURL, caFile=self._caCert, query=query, response=None, useQueue=False)
self.assertEquals(receivedResponse, expectedResponse)
+ def testDOHInvalid(self):
+ """
+ DOH: Invalid query
+ """
+ name = 'invalid.doh.tests.powerdns.com.'
+ invalidQuery = dns.message.make_query(name, 'A', 'IN', use_edns=False)
+ invalidQuery.id = 0
+ # first an invalid query
+ invalidQuery = invalidQuery.to_wire()
+ invalidQuery = invalidQuery[:-5]
+ (_, receivedResponse) = self.sendDOHQuery(self._dohServerPort, self._serverName, self._dohBaseURL, caFile=self._caCert, query=invalidQuery, response=None, useQueue=False, rawQuery=True)
+ self.assertEquals(receivedResponse, None)
+
+ # and now a valid one
+ query = dns.message.make_query(name, 'A', 'IN', use_edns=False)
+ query.id = 0
+ expectedQuery = dns.message.make_query(name, 'A', 'IN', use_edns=True, payload=4096)
+ expectedQuery.id = 0
+ response = dns.message.make_response(query)
+ rrset = dns.rrset.from_text(name,
+ 3600,
+ dns.rdataclass.IN,
+ dns.rdatatype.A,
+ '127.0.0.1')
+ response.answer.append(rrset)
+ (receivedQuery, receivedResponse) = self.sendDOHQuery(self._dohServerPort, self._serverName, self._dohBaseURL, query, response=response, caFile=self._caCert)
+ self.assertTrue(receivedQuery)
+ self.assertTrue(receivedResponse)
+ receivedQuery.id = expectedQuery.id
+ self.assertEquals(expectedQuery, receivedQuery)
+ self.checkQueryEDNSWithoutECS(expectedQuery, receivedQuery)
+ self.assertEquals(response, receivedResponse)
+
+ def testDOHWithoutQuery(self):
+ """
+ DOH: Empty GET query
+ """
+ name = 'empty-get.doh.tests.powerdns.com.'
+ url = self._dohBaseURL
+ conn = self.openDOHConnection(self._dohServerPort, self._caCert, timeout=2.0)
+ conn.setopt(pycurl.URL, url)
+ conn.setopt(pycurl.RESOLVE, ["%s:%d:127.0.0.1" % (self._serverName, self._dohServerPort)])
+ conn.setopt(pycurl.SSL_VERIFYPEER, 1)
+ conn.setopt(pycurl.SSL_VERIFYHOST, 2)
+ conn.setopt(pycurl.CAINFO, self._caCert)
+ data = conn.perform_rb()
+ rcode = conn.getinfo(pycurl.RESPONSE_CODE)
+ self.assertEquals(rcode, 400)
+
+ def testDOHEmptyPOST(self):
+ """
+ DOH: Empty POST query
+ """
+ name = 'empty-post.doh.tests.powerdns.com.'
+
+ (_, receivedResponse) = self.sendDOHPostQuery(self._dohServerPort, self._serverName, self._dohBaseURL, query="", rawQuery=True, response=None, caFile=self._caCert)
+ self.assertEquals(receivedResponse, None)
+
+ # and now a valid one
+ query = dns.message.make_query(name, 'A', 'IN', use_edns=False)
+ query.id = 0
+ expectedQuery = dns.message.make_query(name, 'A', 'IN', use_edns=True, payload=4096)
+ expectedQuery.id = 0
+ response = dns.message.make_response(query)
+ rrset = dns.rrset.from_text(name,
+ 3600,
+ dns.rdataclass.IN,
+ dns.rdatatype.A,
+ '127.0.0.1')
+ response.answer.append(rrset)
+ (receivedQuery, receivedResponse) = self.sendDOHPostQuery(self._dohServerPort, self._serverName, self._dohBaseURL, query, response=response, caFile=self._caCert)
+ self.assertTrue(receivedQuery)
+ self.assertTrue(receivedResponse)
+ receivedQuery.id = expectedQuery.id
+ self.assertEquals(expectedQuery, receivedQuery)
+ self.checkQueryEDNSWithoutECS(expectedQuery, receivedQuery)
+ self.assertEquals(response, receivedResponse)
+
+ def testHeaderRule(self):
+ """
+ DOH: HeaderRule
+ """
+ name = 'header-rule.doh.tests.powerdns.com.'
+ query = dns.message.make_query(name, 'A', 'IN')
+ query.id = 0
+ query.flags &= ~dns.flags.RD
+ expectedResponse = dns.message.make_response(query)
+ rrset = dns.rrset.from_text(name,
+ 3600,
+ dns.rdataclass.IN,
+ dns.rdatatype.A,
+ '2.3.4.5')
+ expectedResponse.answer.append(rrset)
+
+ # this header should match
+ (_, receivedResponse) = self.sendDOHQuery(self._dohServerPort, self._serverName, self._dohBaseURL, caFile=self._caCert, query=query, response=None, useQueue=False, customHeaders=['x-powerdnS: aaaaa'])
+ self.assertEquals(receivedResponse, expectedResponse)
+
+ expectedQuery = dns.message.make_query(name, 'A', 'IN', use_edns=True, payload=4096)
+ expectedQuery.flags &= ~dns.flags.RD
+ expectedQuery.id = 0
+ response = dns.message.make_response(query)
+ rrset = dns.rrset.from_text(name,
+ 3600,
+ dns.rdataclass.IN,
+ dns.rdatatype.A,
+ '127.0.0.1')
+ response.answer.append(rrset)
+
+ # this content of the header should NOT match
+ (receivedQuery, receivedResponse) = self.sendDOHQuery(self._dohServerPort, self._serverName, self._dohBaseURL, query, response=response, caFile=self._caCert, customHeaders=['x-powerdnS: bbbbb'])
+ self.assertTrue(receivedQuery)
+ self.assertTrue(receivedResponse)
+ receivedQuery.id = expectedQuery.id
+ self.assertEquals(expectedQuery, receivedQuery)
+ self.checkQueryEDNSWithoutECS(expectedQuery, receivedQuery)
+ self.assertEquals(response, receivedResponse)
+
+ def testHTTPPath(self):
+ """
+ DOH: HTTPPath
+ """
+ name = 'http-path.doh.tests.powerdns.com.'
+ query = dns.message.make_query(name, 'A', 'IN')
+ query.id = 0
+ query.flags &= ~dns.flags.RD
+ expectedResponse = dns.message.make_response(query)
+ rrset = dns.rrset.from_text(name,
+ 3600,
+ dns.rdataclass.IN,
+ dns.rdatatype.A,
+ '3.4.5.6')
+ expectedResponse.answer.append(rrset)
+
+ # this path should match
+ (_, receivedResponse) = self.sendDOHQuery(self._dohServerPort, self._serverName, self._dohBaseURL + 'PowerDNS', caFile=self._caCert, query=query, response=None, useQueue=False)
+ self.assertEquals(receivedResponse, expectedResponse)
+
+ expectedQuery = dns.message.make_query(name, 'A', 'IN', use_edns=True, payload=4096)
+ expectedQuery.id = 0
+ expectedQuery.flags &= ~dns.flags.RD
+ response = dns.message.make_response(query)
+ rrset = dns.rrset.from_text(name,
+ 3600,
+ dns.rdataclass.IN,
+ dns.rdatatype.A,
+ '127.0.0.1')
+ response.answer.append(rrset)
+
+ # this path should NOT match
+ (receivedQuery, receivedResponse) = self.sendDOHQuery(self._dohServerPort, self._serverName, self._dohBaseURL + "PowerDNS2", query, response=response, caFile=self._caCert)
+ self.assertTrue(receivedQuery)
+ self.assertTrue(receivedResponse)
+ receivedQuery.id = expectedQuery.id
+ self.assertEquals(expectedQuery, receivedQuery)
+ self.checkQueryEDNSWithoutECS(expectedQuery, receivedQuery)
+ self.assertEquals(response, receivedResponse)
class TestDOHAddingECS(DNSDistDOHTest):
_config_template = """
newServer{address="127.0.0.1:%s"}
addTLSLocal("127.0.0.1:%s", "%s", "%s")
+ addAction(SNIRule("powerdns.com"), SpoofAction("1.2.3.4"))
"""
_config_params = ['_testServerPort', '_tlsServerPort', '_serverCert', '_serverKey']
receivedQuery.id = query.id
self.assertEquals(query, receivedQuery)
self.assertEquals(response, receivedResponse)
+
+ def testTLSSNIRouting(self):
+ """
+ TLS: SNI Routing
+ """
+ name = 'sni.tls.tests.powerdns.com.'
+ query = dns.message.make_query(name, 'A', 'IN', use_edns=False)
+ query.flags &= ~dns.flags.RD
+ response = dns.message.make_response(query)
+ rrset = dns.rrset.from_text(name,
+ 3600,
+ dns.rdataclass.IN,
+ dns.rdatatype.A,
+ '127.0.0.1')
+ response.answer.append(rrset)
+ expectedResponse = dns.message.make_response(query)
+ rrset = dns.rrset.from_text(name,
+ 3600,
+ dns.rdataclass.IN,
+ dns.rdatatype.A,
+ '1.2.3.4')
+ expectedResponse.answer.append(rrset)
+
+ # this SNI should match so we should get a spoofed answer
+ conn = self.openTLSConnection(self._tlsServerPort, 'powerdns.com', self._caCert)
+
+ self.sendTCPQueryOverConnection(conn, query, response=None)
+ receivedResponse = self.recvTCPResponseOverConnection(conn, useQueue=False)
+ self.assertTrue(receivedResponse)
+ self.assertEquals(expectedResponse, receivedResponse)
+
+ # this one should not
+ conn = self.openTLSConnection(self._tlsServerPort, self._serverName, self._caCert)
+
+ self.sendTCPQueryOverConnection(conn, query, response=response)
+ (receivedQuery, receivedResponse) = self.recvTCPResponseOverConnection(conn, useQueue=True)
+ self.assertTrue(receivedQuery)
+ self.assertTrue(receivedResponse)
+ receivedQuery.id = query.id
+ self.assertEquals(query, receivedQuery)
+ self.assertEquals(response, receivedResponse)
bindwait
$SDIG 127.0.0.1 $port minimal.com SOA | LC_ALL=C sort
+$SDIG 127.0.0.1 $port minimal.com MX | LC_ALL=C sort
+$SDIG 127.0.0.1 $port nx.minimal.com MX | LC_ALL=C sort
sleep 15
+echo midnight has passed
$SDIG 127.0.0.1 $port minimal.com SOA | LC_ALL=C sort
-
+$SDIG 127.0.0.1 $port minimal.com MX | LC_ALL=C sort
+$SDIG 127.0.0.1 $port nx.minimal.com MX | LC_ALL=C sort
+
kill $(cat pdns*.pid)
rm pdns*.pid
0 minimal.com. IN SOA 120 ns1.example.com. ahu.example.com. 2000083846 28800 7200 604800 86400
Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
Reply to question for qname='minimal.com.', qtype=SOA
+1 minimal.com. IN SOA 120 ns1.example.com. ahu.example.com. 2000083846 28800 7200 604800 86400
+Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
+Reply to question for qname='minimal.com.', qtype=MX
+1 minimal.com. IN SOA 120 ns1.example.com. ahu.example.com. 2000083846 28800 7200 604800 86400
+Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
+Reply to question for qname='nx.minimal.com.', qtype=MX
+midnight has passed
0 minimal.com. IN SOA 120 ns1.example.com. ahu.example.com. 2000083847 28800 7200 604800 86400
Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
Reply to question for qname='minimal.com.', qtype=SOA
+1 minimal.com. IN SOA 120 ns1.example.com. ahu.example.com. 2000083847 28800 7200 604800 86400
+Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
+Reply to question for qname='minimal.com.', qtype=MX
+1 minimal.com. IN SOA 120 ns1.example.com. ahu.example.com. 2000083847 28800 7200 604800 86400
+Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
+Reply to question for qname='nx.minimal.com.', qtype=MX
pip install -r requirements.txt
protoc -I=../pdns/ --python_out=. ../pdns/dnsmessage.proto
+protoc -I=../pdns/ --python_out=. ../pdns/dnstap.proto
+
mkdir -p configs
LIBFAKETIME_DEFAULT=/usr/local/lib/faketime/libfaketime.1.dylib
LIBAUTHBIND_DEFAULT=""
fi
+if [ $(uname -s) = "OpenBSD" ]; then
+ # OpenBSD is not /really/ supported here; it works for some tests, and then you might need sudo.
+ LIBFAKETIME_DEFAULT=""
+ LIBAUTHBIND_DEFAULT=""
+fi
export LIBFAKETIME=${LIBFAKETIME:-$LIBFAKETIME_DEFAULT}
export LIBAUTHBIND=${LIBAUTHBIND:-$LIBAUTHBIND_DEFAULT}
if [ "${PDNS_DEBUG}" = "YES" ]; then
set -x
fi
+
+if ! "$PDNSRECURSOR" --version 2>&1 | grep Features | grep -q dnstap-framestream; then
+ export NODNSTAPTESTS=1
+fi
+
+if [ "${LIBAUTHBIND}" != "" -o "${LIBFAKETIME}" != "" ]; then
LD_PRELOAD="${LIBASAN} ${LIBAUTHBIND} ${LIBFAKETIME}" nosetests -I test_WellKnown.py --with-xunit $@
+else
+nosetests -I test_WellKnown.py --with-xunit $@
+fi
--- /dev/null
+import dns
+import dnsmessage_pb2
+import os
+import socket
+import struct
+import sys
+import threading
+import time
+
+import dns
+import dnstap_pb2
+
+FSTRM_CONTROL_ACCEPT = 0x01
+FSTRM_CONTROL_START = 0x02
+FSTRM_CONTROL_STOP = 0x03
+FSTRM_CONTROL_READY = 0x04
+FSTRM_CONTROL_FINISH = 0x05
+
+# Python2/3 compatibility hacks
+try:
+ from queue import Queue
+except ImportError:
+ from Queue import Queue
+
+try:
+ range = xrange
+except NameError:
+ pass
+
+from nose import SkipTest
+from recursortests import RecursorTest
+
+def checkDnstapBase(testinstance, dnstap, protocol, initiator):
+ testinstance.assertTrue(dnstap)
+ testinstance.assertTrue(dnstap.HasField('identity'))
+ #testinstance.assertEqual(dnstap.identity, b'a.server')
+ testinstance.assertTrue(dnstap.HasField('version'))
+ #testinstance.assertIn(b'dnsdist ', dnstap.version)
+ testinstance.assertTrue(dnstap.HasField('type'))
+ testinstance.assertEqual(dnstap.type, dnstap.MESSAGE)
+ testinstance.assertTrue(dnstap.HasField('message'))
+ testinstance.assertTrue(dnstap.message.HasField('socket_protocol'))
+ testinstance.assertEqual(dnstap.message.socket_protocol, protocol)
+ testinstance.assertTrue(dnstap.message.HasField('socket_family'))
+ testinstance.assertEquals(dnstap.message.socket_family, dnstap_pb2.INET)
+ #
+ # We cannot check the query address and port since we only log outgoing queries via dnstap
+ #
+ #testinstance.assertTrue(dnstap.message.HasField('query_address'))
+ #testinstance.assertEquals(socket.inet_ntop(socket.AF_INET, dnstap.message.query_address), initiator)
+ testinstance.assertTrue(dnstap.message.HasField('response_address'))
+ testinstance.assertEquals(socket.inet_ntop(socket.AF_INET, dnstap.message.response_address), initiator)
+ testinstance.assertTrue(dnstap.message.HasField('response_port'))
+ testinstance.assertEquals(dnstap.message.response_port, 53)
+
+
+def checkDnstapQuery(testinstance, dnstap, protocol, query, initiator='127.0.0.1'):
+ testinstance.assertEquals(dnstap.message.type, dnstap_pb2.Message.RESOLVER_QUERY)
+ checkDnstapBase(testinstance, dnstap, protocol, initiator)
+
+ testinstance.assertTrue(dnstap.message.HasField('query_time_sec'))
+ testinstance.assertTrue(dnstap.message.HasField('query_time_nsec'))
+
+ testinstance.assertTrue(dnstap.message.HasField('query_message'))
+ #
+ # We cannot compare the incoming query with the outgoing one
+ # The IDs and some other fields will be different
+ #
+ wire_message = dns.message.from_wire(dnstap.message.query_message)
+ #testinstance.assertEqual(wire_message, query)
+
+
+def checkDnstapExtra(testinstance, dnstap, expected):
+ testinstance.assertTrue(dnstap.HasField('extra'))
+ testinstance.assertEqual(dnstap.extra, expected)
+
+
+def checkDnstapNoExtra(testinstance, dnstap):
+ testinstance.assertFalse(dnstap.HasField('extra'))
+
+
+def checkDnstapResponse(testinstance, dnstap, protocol, response, initiator='127.0.0.1'):
+ testinstance.assertEquals(dnstap.message.type, dnstap_pb2.Message.RESOLVER_RESPONSE)
+ checkDnstapBase(testinstance, dnstap, protocol, initiator)
+
+ testinstance.assertTrue(dnstap.message.HasField('query_time_sec'))
+ testinstance.assertTrue(dnstap.message.HasField('query_time_nsec'))
+
+ testinstance.assertTrue(dnstap.message.HasField('response_time_sec'))
+ testinstance.assertTrue(dnstap.message.HasField('response_time_nsec'))
+
+ testinstance.assertTrue(dnstap.message.response_time_sec > dnstap.message.query_time_sec or \
+ dnstap.message.response_time_nsec > dnstap.message.query_time_nsec)
+
+ testinstance.assertTrue(dnstap.message.HasField('response_message'))
+ wire_message = dns.message.from_wire(dnstap.message.response_message)
+ testinstance.assertEqual(wire_message, response)
+
+def fstrm_get_control_frame_type(data):
+ (t,) = struct.unpack("!L", data[0:4])
+ return t
+
+
+def fstrm_make_control_frame_reply(cft, data):
+ if cft == FSTRM_CONTROL_READY:
+ # Reply with ACCEPT frame and content-type
+ contenttype = b'protobuf:dnstap.Dnstap'
+ frame = struct.pack('!LLL', FSTRM_CONTROL_ACCEPT, 1,
+ len(contenttype)) + contenttype
+ buf = struct.pack("!LL", 0, len(frame)) + frame
+ return buf
+ elif cft == FSTRM_CONTROL_START:
+ return None
+ else:
+ raise Exception('unhandled control frame ' + cft)
+
+
+def fstrm_read_and_dispatch_control_frame(conn):
+ data = conn.recv(4)
+ if not data:
+ raise Exception('length of control frame payload could not be read')
+ (datalen,) = struct.unpack("!L", data)
+ data = conn.recv(datalen)
+ cft = fstrm_get_control_frame_type(data)
+ reply = fstrm_make_control_frame_reply(cft, data)
+ if reply:
+ conn.send(reply)
+ return cft
+
+
+def fstrm_handle_bidir_connection(conn, on_data):
+ data = None
+ while True:
+ data = conn.recv(4)
+ if not data:
+ break
+ (datalen,) = struct.unpack("!L", data)
+ if datalen == 0:
+ # control frame length follows
+ cft = fstrm_read_and_dispatch_control_frame(conn)
+ if cft == FSTRM_CONTROL_STOP:
+ break
+ else:
+ # data frame
+ data = conn.recv(datalen)
+ if not data:
+ break
+
+ on_data(data)
+
+
+
+class DNSTapServerParams:
+ def __init__(self, port):
+ self.queue = Queue()
+ self.port = port
+
+
+DNSTapServerParameters = DNSTapServerParams(4243)
+DNSTapListeners = []
+
+class TestRecursorDNSTap(RecursorTest):
+ @classmethod
+ def FrameStreamUnixListener(cls, conn, param):
+ while True:
+ try:
+ fstrm_handle_bidir_connection(conn, lambda data: \
+ param.queue.put(data, True, timeout=2.0))
+ except socket.error as e:
+ if e.errno == 9:
+ break
+ printf("Unexpected socket error %d", e)
+ sys.exit(1)
+ conn.close()
+
+ @classmethod
+ def FrameStreamUnixListenerMain(cls, param):
+ sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+ sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1)
+ try:
+ sock.bind(("127.0.0.1", param.port))
+ except socket.error as e:
+ print("Error binding in the framestream listener: %s" % str(e))
+ sys.exit(1)
+ DNSTapListeners.append(sock)
+ sock.listen(100)
+ while True:
+ (conn, _) = sock.accept()
+ print("Accepting connection")
+ listener = threading.Thread(name='DNSTap Worker', target=cls.FrameStreamUnixListener, args=[conn, param])
+ listener.setDaemon(True)
+ listener.start()
+
+ sock.close()
+
+ @classmethod
+ def setUpClass(cls):
+
+ if os.environ.get("NODNSTAPTESTS") == "1":
+ raise SkipTest("Not Yet Supported")
+
+ cls.setUpSockets()
+
+ cls.startResponders()
+
+ listener = threading.Thread(name='DNSTap Listener', target=cls.FrameStreamUnixListenerMain, args=[DNSTapServerParameters])
+ listener.setDaemon(True)
+ listener.start()
+
+
+ confdir = os.path.join('configs', cls._confdir)
+ cls.createConfigDir(confdir)
+
+ cls.generateRecursorConfig(confdir)
+ cls.startRecursor(confdir, cls._recursorPort)
+
+ def setUp(self):
+ # Make sure the queue is empty, in case
+ # a previous test failed
+ while not DNSTapServerParameters.queue.empty():
+ DNSTapServerParameters.queue.get(False)
+
+ @classmethod
+ def generateRecursorConfig(cls, confdir):
+ authzonepath = os.path.join(confdir, 'example.zone')
+ with open(authzonepath, 'w') as authzone:
+ authzone.write("""$ORIGIN example.
+@ 3600 IN SOA {soa}
+a 3600 IN A 192.0.2.42
+tagged 3600 IN A 192.0.2.84
+query-selected 3600 IN A 192.0.2.84
+answer-selected 3600 IN A 192.0.2.84
+types 3600 IN A 192.0.2.84
+types 3600 IN AAAA 2001:DB8::1
+types 3600 IN TXT "Lorem ipsum dolor sit amet"
+types 3600 IN MX 10 a.example.
+types 3600 IN SPF "v=spf1 -all"
+types 3600 IN SRV 10 20 443 a.example.
+cname 3600 IN CNAME a.example.
+
+""".format(soa=cls._SOA))
+ super(TestRecursorDNSTap, cls).generateRecursorConfig(confdir)
+
+ @classmethod
+ def tearDownClass(cls):
+ cls.tearDownRecursor()
+ for listerner in DNSTapListeners:
+ listerner.close()
+
+class DNSTapDefaultTest(TestRecursorDNSTap):
+ """
+ This test makes sure that we correctly export outgoing queries over DNSTap.
+ It must be improved and setup env so we can check for incoming responses, but makes sure for now
+ that the recursor at least connects to the DNSTap server.
+ """
+
+ _confdir = 'DNSTapDefault'
+ _config_template = """
+auth-zones=example=configs/%s/example.zone""" % _confdir
+ _lua_config_file = """
+ dnstapFrameStreamServer({"127.0.0.1:%d"})
+ """ % (DNSTapServerParameters.port)
+
+ def getFirstDnstap(self):
+ data = DNSTapServerParameters.queue.get(True, timeout=2.0)
+ self.assertTrue(data)
+ dnstap = dnstap_pb2.Dnstap()
+ dnstap.ParseFromString(data)
+ return dnstap
+
+ def testA(self):
+
+ name = 'www.example.org.'
+ query = dns.message.make_query(name, 'A', want_dnssec=True)
+ query.flags |= dns.flags.RD
+ res = self.sendUDPQuery(query)
+
+ # check the DNSTap messages corresponding to the UDP query and answer
+ # check the dnstap message corresponding to the UDP query
+ dnstap = self.getFirstDnstap()
+
+ checkDnstapQuery(self, dnstap, dnstap_pb2.UDP, query, '127.0.0.8')
+ # We don't expect a response
+ checkDnstapNoExtra(self, dnstap)
+
+class DNSTapLogNoQueriesTest(TestRecursorDNSTap):
+ """
+ This test makes sure that we correctly export outgoing queries over DNSTap.
+ It must be improved and setup env so we can check for incoming responses, but makes sure for now
+ that the recursor at least connects to the DNSTap server.
+ """
+
+ _confdir = 'DNSTapLogNoQueries'
+ _config_template = """
+auth-zones=example=configs/%s/example.zone""" % _confdir
+ _lua_config_file = """
+ dnstapFrameStreamServer({"127.0.0.1:%d"}, {logQueries=false})
+ """ % (DNSTapServerParameters.port)
+
+ def testA(self):
+ name = 'www.example.org.'
+ query = dns.message.make_query(name, 'A', want_dnssec=True)
+ query.flags |= dns.flags.RD
+ res = self.sendUDPQuery(query)
+
+ # We don't expect anything
+ self.assertTrue(DNSTapServerParameters.queue.empty())
+
touch tests/verify-dnssec-zone/allow-missing
```
+Getting required daemons from Docker
+------------------------------------
+
+Please keep in mind that databases may need a few seconds to start up.
+
+'MySQL':
+```sh
+docker run -p 3306:3306 --rm -d -e MYSQL_ALLOW_EMPTY_PASSWORD=1 mariadb
+GMYSQLHOST=127.0.0.1 ./start-test-stop 5300 gmysql
+```
+
+Postgres:
+```sh
+docker run -p 5432:5432 --rm -d postgres
+GPGSQLUSER=postgres PGHOST=127.0.0.1 ./start-test-stop 5300 gpgsql
+```
result=" Failed test $a"
echo $testname >> failed_tests
failed=$[$failed+1]
+ if [ "$FIX_TESTS" == "YES" ]
+ then
+ mv -f $a/real_result $expected
+ result="$result (fixed)"
+ fi
else
result=" Expected failure for reason $reason, test passed $a"
echo $testname >> passed_tests
export PDNSUTIL=${PDNSUTIL:-${PWD}/../pdns/pdnsutil}
export PDNSCONTROL=${PDNSCONTROL:-${PWD}/../pdns/pdns_control}
export RESOLVERIP=${RESOLVERIP:-8.8.8.8}
+export FIX_TESTS=${FIX_TESTS:-NO}
ALGORITHM=${ALGORITHM:="hmac-md5"}
cleandig $RECORDNAME A hidesoadetails
cleandig $RECORDNAME CNAME hidesoadetails
+echo '* Add a record that will result in an ENT being inserted at the upcoming CNAME'
+cleannsupdate <<!
+server $nameserver $port
+zone test.dyndns
+update add sub.$RECORDNAME. 3600 A 192.0.2.1
+send
+answer
+!
+
echo '* Add a CNAME'
cleannsupdate <<!
server $nameserver $port
answer
!
+echo '* Delete the now useless record inserted to get an ENT there'
+cleannsupdate <<!
+server $nameserver $port
+zone test.dyndns
+update delete sub.$RECORDNAME. 3600 A 192.0.2.1
+send
+answer
+!
+
echo '* Attempt to add an A record (should be REFUSED)'
cleannsupdate <<!
server $nameserver $port
1 test.dyndns. IN SOA 3600 ns1.test.dyndns. ahu.example.dyndns. [serial] 28800 7200 604800 86400
Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
Reply to question for qname='cname-and-other.test.dyndns.', qtype=CNAME
+* Add a record that will result in an ENT being inserted at the upcoming CNAME
+Answer:
+;; ->>HEADER<<- opcode: UPDATE, status: NOERROR, id: [id]
+;; flags: qr aa; ZONE: 1, PREREQ: 0, UPDATE: 0, ADDITIONAL: 0
+;; ZONE SECTION:
+;test.dyndns. IN SOA
+
* Add a CNAME
Answer:
;; ->>HEADER<<- opcode: UPDATE, status: NOERROR, id: [id]
;; ZONE SECTION:
;test.dyndns. IN SOA
+* Delete the now useless record inserted to get an ENT there
+Answer:
+;; ->>HEADER<<- opcode: UPDATE, status: NOERROR, id: [id]
+;; flags: qr aa; ZONE: 1, PREREQ: 0, UPDATE: 0, ADDITIONAL: 0
+;; ZONE SECTION:
+;test.dyndns. IN SOA
+
* Attempt to add an A record (should be REFUSED)
Answer:
;; ->>HEADER<<- opcode: UPDATE, status: REFUSED, id: [id]
UDP dns packets can only be 512 bytes long - when they are longer, they need
to get truncated, and have the 'TC' bit set, to inform the client that they
need to requery over TCP. This query however does not need truncation, since
-the information that causes things to go over limit is 'additional'.
+the information that causes things to go over limit are 'courtesy' additionals.
+But truncation is harmless and will avoid a lot of additional complexity.
+https://tools.ietf.org/html/rfc4472#appendix-B
--- /dev/null
+Rcode: 0 (No Error), RD: 0, QR: 1, TC: 1, AA: 1, opcode: 0
+Reply to question for qname='together-too-much.example.com.', qtype=MX
+++ /dev/null
-0 together-too-much.example.com. IN MX 120 25 toomuchinfo-X.example.com.
-0 together-too-much.example.com. IN MX 120 25 toomuchinfo-X.example.com.
-2 toomuchinfo-X.example.com. IN A 120 192.168.99.
-2 toomuchinfo-X.example.com. IN A 120 192.168.99.
-2 toomuchinfo-X.example.com. IN A 120 192.168.99.
-2 toomuchinfo-X.example.com. IN A 120 192.168.99.
-2 toomuchinfo-X.example.com. IN A 120 192.168.99.
-2 toomuchinfo-X.example.com. IN A 120 192.168.99.
-2 toomuchinfo-X.example.com. IN A 120 192.168.99.
-2 toomuchinfo-X.example.com. IN A 120 192.168.99.
-2 toomuchinfo-X.example.com. IN A 120 192.168.99.
-2 toomuchinfo-X.example.com. IN A 120 192.168.99.
-2 toomuchinfo-X.example.com. IN A 120 192.168.99.
-2 toomuchinfo-X.example.com. IN A 120 192.168.99.
-2 toomuchinfo-X.example.com. IN A 120 192.168.99.
-2 toomuchinfo-X.example.com. IN A 120 192.168.99.
-2 toomuchinfo-X.example.com. IN A 120 192.168.99.
-2 toomuchinfo-X.example.com. IN A 120 192.168.99.
-2 toomuchinfo-X.example.com. IN A 120 192.168.99.
-2 toomuchinfo-X.example.com. IN A 120 192.168.99.
-2 toomuchinfo-X.example.com. IN A 120 192.168.99.
-2 toomuchinfo-X.example.com. IN A 120 192.168.99.
-2 toomuchinfo-X.example.com. IN A 120 192.168.99.
-2 toomuchinfo-X.example.com. IN A 120 192.168.99.
-2 toomuchinfo-X.example.com. IN A 120 192.168.99.
-2 toomuchinfo-X.example.com. IN A 120 192.168.99.
-2 toomuchinfo-X.example.com. IN A 120 192.168.99.
-Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
-Reply to question for qname='together-too-much.example.com.', qtype=MX