]> git.ipfire.org Git - thirdparty/pdns.git/commitdiff
Merge pull request #8777 from omoerbeek/rec-wip-qname-vs-ds
authorOtto Moerbeek <otto.moerbeek@open-xchange.com>
Tue, 11 Feb 2020 16:08:28 +0000 (17:08 +0100)
committerGitHub <noreply@github.com>
Tue, 11 Feb 2020 16:08:28 +0000 (17:08 +0100)
rec: QNAME minimization sometimes uses 1 label too many

89 files changed:
.circleci/config.yml
build-scripts/docker/generate-repo-files.sh
builder-support/debian/dnsdist/debian-buster/dnsdist.postinst
builder-support/debian/dnsdist/debian-buster/rules
builder-support/debian/dnsdist/debian-jessie/dnsdist.postinst
builder-support/debian/dnsdist/debian-jessie/rules
builder-support/debian/dnsdist/debian-stretch/dnsdist.postinst
builder-support/debian/dnsdist/debian-stretch/rules
builder-support/dockerfiles/Dockerfile.rpmbuild
builder-support/dockerfiles/Dockerfile.target.amazon-2
builder-support/dockerfiles/Dockerfile.target.centos-6
builder-support/dockerfiles/Dockerfile.target.centos-7
builder-support/dockerfiles/Dockerfile.target.centos-8
docs/backends/bind.rst
docs/backends/generic-mysql.rst
docs/backends/generic-odbc.rst
docs/backends/generic-postgresql.rst
docs/backends/generic-sqlite3.rst
docs/common/security-policy.rst
docs/guides/basic-database.rst
docs/http-api/swagger/authoritative-api-swagger.yaml
docs/manpages/pdns_control.1.rst
docs/secpoll.zone
docs/tsig.rst
modules/bindbackend/bindbackend2.cc
modules/bindbackend/bindbackend2.hh
modules/opendbxbackend/odbxprivate.cc [deleted file]
pdns/common_startup.cc
pdns/dnsdist-console.cc
pdns/dnsdist-ecs.cc
pdns/dnsdist-ecs.hh
pdns/dnsdist-lua-actions.cc
pdns/dnsdist-lua-bindings-dnsquestion.cc
pdns/dnsdist-lua-bindings.cc
pdns/dnsdist-lua-vars.cc
pdns/dnsdist-lua.cc
pdns/dnsdist-lua.hh
pdns/dnsdist-web.cc
pdns/dnsdist.cc
pdns/dnsdist.hh
pdns/dnsdistdist/Makefile.am
pdns/dnsdistdist/configure.ac
pdns/dnsdistdist/dnsdist-prometheus.hh [new file with mode: 0644]
pdns/dnsdistdist/dnsdist-systemd.cc [new file with mode: 0644]
pdns/dnsdistdist/dnsdist-systemd.hh [new file with mode: 0644]
pdns/dnsdistdist/dnsdist.service.in
pdns/dnsdistdist/docs/guides/downstreams.rst
pdns/dnsdistdist/docs/guides/dynblocks.rst
pdns/dnsdistdist/docs/reference/config.rst
pdns/dnsdistdist/docs/reference/constants.rst
pdns/dnsdistdist/docs/reference/dq.rst
pdns/dnsdistdist/docs/rules-actions.rst
pdns/dnsdistdist/docs/statistics.rst
pdns/dnsdistdist/doh.cc
pdns/dnsdistdist/libssl.cc
pdns/dnsdistdist/m4/pdns_with_service_user.m4 [new symlink]
pdns/dnsdistdist/tcpiohandler.cc
pdns/dnslabeltext.rl
pdns/dnsname.cc
pdns/dnsname.hh
pdns/dnsparser.cc
pdns/dnsparser.hh
pdns/dnswriter.hh
pdns/doh.hh
pdns/ednsoptions.cc
pdns/ednsoptions.hh
pdns/misc.cc
pdns/misc.hh
pdns/rec-lua-conf.cc
pdns/rec-lua-conf.hh
pdns/rec_channel_rec.cc
pdns/recursordist/docs/changelog/4.3.rst
pdns/recursordist/docs/lua-scripting/dq.rst
pdns/recursordist/docs/metrics.rst
pdns/recursordist/rec_metrics.hh
pdns/rpzloader.cc
pdns/rpzloader.hh
pdns/tcpreceiver.cc
pdns/test-dnsdist_cc.cc
pdns/ws-api.cc
regression-tests.dnsdist/dnsdisttests.py
regression-tests.dnsdist/test_API.py
regression-tests.dnsdist/test_Advanced.py
regression-tests.dnsdist/test_DOH.py
regression-tests.dnsdist/test_EdnsClientSubnet.py
regression-tests.dnsdist/test_Spoofing.py
regression-tests.nobackend/counters/command
regression-tests.nobackend/default-publish-cds/command
regression-tests.nobackend/default-publish-cds/expected_result

index 21c78ea4c08174a728318fde4d1f1c16743d4478..945022d48efe6de98d6754a32a5208a872acfb45 100644 (file)
@@ -26,7 +26,7 @@ commands:
               git clone --depth 1 --branch $CIRCLE_BRANCH $CIRCLE_REPOSITORY_URL /opt/project
             fi
             cd /opt/project
-            git show -s
+            git --no-pager show -s
 
   get-workspace:
     description: "Attach workspace to /opt and symlink checkout into home"
index 6397ce10357bf003384b290ecff44fe38edc03e2..9026e32755cdeb9a996ec7f42e26f4e19c6d1000 100755 (executable)
@@ -13,45 +13,63 @@ if [ "$1" = "" -o "$1" = "-?" -o "$1" = "-h" -o "$1" = "--help" ]; then
     exit 1
 fi
 
+
 write_centos()
 {
     OS=centos
     VERSION=$1
     PKG=$2
     CMD=$3
+
     cat <<EOF > Dockerfile.$RELEASE.$OS-$VERSION
 FROM $OS:$VERSION
 
 RUN yum install -y epel-release bind-utils
 EOF
+
     if [ "$VERSION" = "6" -o "$VERSION" = "7" ]; then
         cat <<EOF >> Dockerfile.$RELEASE.$OS-$VERSION
 RUN yum install -y yum-plugin-priorities
 EOF
     fi
+
     cat <<EOF >> Dockerfile.$RELEASE.$OS-$VERSION
 RUN curl -o /etc/yum.repos.d/powerdns-$RELEASE.repo https://repo.powerdns.com/repo-files/$OS-$RELEASE.repo
 RUN yum install -y $PKG
+EOF
+
+    if [ "$RELEASE" = "rec-43" ]; then
+    cat <<EOF >> Dockerfile.$RELEASE.$OS-$VERSION
+
+RUN mkdir /var/run/pdns-recursor
+EOF
+    fi
+
+    cat <<EOF >> Dockerfile.$RELEASE.$OS-$VERSION
 
 CMD $CMD --version
 EOF
 }
 
+
 write_debian_or_ubuntu()
 {
     OS=$1
     VERSION=$2
     PKG=$3
     CMD=$4
+
     cat <<EOF > pdns.list.$RELEASE.$OS-$VERSION
 deb [arch=amd64] http://repo.powerdns.com/$OS $VERSION-$RELEASE main
 EOF
+
     # if not exists
     cat <<EOF > pdns.debian-and-ubuntu
 Package: pdns-*
 Pin: origin repo.powerdns.com
 Pin-Priority: 600
 EOF
+
     cat <<EOF > Dockerfile.$RELEASE.$OS-$VERSION
 FROM $OS:$VERSION
 
@@ -64,21 +82,34 @@ COPY pdns.list.$RELEASE.$OS-$VERSION /etc/apt/sources.list.d/pdns.list
 RUN curl https://repo.powerdns.com/FD380FBB-pub.asc | apt-key add -
 RUN apt-get update
 RUN apt-get install -y $PKG
+EOF
+
+    if [ "$RELEASE" = "rec-43" ]; then
+    cat <<EOF >> Dockerfile.$RELEASE.$OS-$VERSION
+
+RUN mkdir /var/run/pdns-recursor
+EOF
+    fi
+
+    cat <<EOF >> Dockerfile.$RELEASE.$OS-$VERSION
 
 CMD $CMD --version
 EOF
 }
 
+
 write_debian()
 {
     write_debian_or_ubuntu debian $1 $2 $3
 }
 
+
 write_ubuntu()
 {
     write_debian_or_ubuntu ubuntu $1 $2 $3
 }
 
+
 RELEASE=$1
 
 if [ "$RELEASE" = "auth-40" ]; then
@@ -96,15 +127,7 @@ elif [ "$RELEASE" = "auth-41" ]; then
     write_ubuntu trusty pdns-server pdns_server
     write_ubuntu xenial pdns-server pdns_server
     write_ubuntu bionic pdns-server pdns_server
-elif [ "$RELEASE" = "auth-42" ]; then
-    write_centos 6 pdns pdns_server
-    write_centos 7 pdns pdns_server
-    write_centos 8 pdns pdns_server
-    write_debian stretch pdns-server pdns_server
-    write_debian buster pdns-server pdns_server
-    write_ubuntu xenial pdns-server pdns_server
-    write_ubuntu bionic pdns-server pdns_server
-elif [ "$RELEASE" = "auth-43" ]; then
+elif [ "$RELEASE" = "auth-42" -o "$RELEASE" = "auth-43" ]; then
     write_centos 6 pdns pdns_server
     write_centos 7 pdns pdns_server
     write_centos 8 pdns pdns_server
@@ -127,15 +150,7 @@ elif [ "$RELEASE" = "rec-41" ]; then
     write_ubuntu trusty pdns-recursor pdns_recursor
     write_ubuntu xenial pdns-recursor pdns_recursor
     write_ubuntu bionic pdns-recursor pdns_recursor
-elif [ "$RELEASE" = "rec-42" ]; then
-    write_centos 6 pdns-recursor pdns_recursor
-    write_centos 7 pdns-recursor pdns_recursor
-    write_centos 8 pdns-recursor pdns_recursor
-    write_debian stretch pdns-recursor pdns_recursor
-    write_debian buster pdns-recursor pdns_recursor
-    write_ubuntu xenial pdns-recursor pdns_recursor
-    write_ubuntu bionic pdns-recursor pdns_recursor
-elif [ "$RELEASE" = "rec-43" ]; then
+elif [ "$RELEASE" = "rec-42" -o "$RELEASE" = "rec-43" ]; then
     write_centos 6 pdns-recursor pdns_recursor
     write_centos 7 pdns-recursor pdns_recursor
     write_centos 8 pdns-recursor pdns_recursor
index 319a26406a4ff8a367cc228ed1953382f706791c..8f7a7ce1811d71ed2837024f028d53166bd9ec7e 100644 (file)
@@ -18,6 +18,12 @@ case "$1" in
 
     adduser --force-badname --system --home /nonexistent --group \
         --no-create-home --quiet _dnsdist || true
+
+    if [ "`stat -c '%U:%G' /etc/dnsdist/dnsdist.conf`" = "root:root" ]; then
+      chown root:_dnsdist /etc/dnsdist/dnsdist.conf
+      # Make sure that dnsdist can read it; the default used to be 0600
+      chmod g+r /etc/dnsdist/dnsdist.conf
+    fi
   ;;
 
   abort-upgrade|abort-remove|abort-deconfigure)
index 9f49e0b6ce063374c1d3fa2520750b3e21acf79f..42533bfda07d6bbc2127c1fbc49d4efe15c7fdf7 100755 (executable)
@@ -51,6 +51,8 @@ override_dh_auto_configure:
          --with-ebpf \
          --with-lua=luajit \
          --with-protobuf \
+         --with-service-user='_dnsdist' \
+         --with-service-group='_dnsdist' \
          $(CONFIGURE_ARGS)
 
 override_dh_auto_build-arch:
@@ -58,8 +60,6 @@ override_dh_auto_build-arch:
 
 override_dh_install:
        dh_auto_install
-       echo Patching uid and git into debian/dnsdist/lib/systemd/system/*.service
-       perl -pi -e 's/(^ExecStart=.*)/$$1 -u _dnsdist -g _dnsdist/' debian/dnsdist/lib/systemd/system/*.service
 ifeq ($(DEB_HOST_ARCH_BITS),32)
        echo RestrictAddressFamilies is broken on 32bit, removing it from service file
        perl -ni -e 'print unless /RestrictAddressFamilies/' debian/dnsdist/lib/systemd/system/*.service
@@ -75,3 +75,8 @@ override_dh_installexamples:
 override_dh_installinit:
        # do nothing here. avoids referencing a non-existant init script.
 
+override_dh_fixperms:
+       dh_fixperms
+        # these files often contain passwords. 640 as it is chowned to root:_dnsdist
+       touch debian/dnsdist/etc/dnsdist/dnsdist.conf
+       chmod 0640 debian/dnsdist/etc/dnsdist/dnsdist.conf
index 319a26406a4ff8a367cc228ed1953382f706791c..8f7a7ce1811d71ed2837024f028d53166bd9ec7e 100644 (file)
@@ -18,6 +18,12 @@ case "$1" in
 
     adduser --force-badname --system --home /nonexistent --group \
         --no-create-home --quiet _dnsdist || true
+
+    if [ "`stat -c '%U:%G' /etc/dnsdist/dnsdist.conf`" = "root:root" ]; then
+      chown root:_dnsdist /etc/dnsdist/dnsdist.conf
+      # Make sure that dnsdist can read it; the default used to be 0600
+      chmod g+r /etc/dnsdist/dnsdist.conf
+    fi
   ;;
 
   abort-upgrade|abort-remove|abort-deconfigure)
index e0f70b6c9a0ae74bca3f8e97ae607b60f1f60274..053c23e5ccb8a20d3f48829c3346cee4033f9b10 100755 (executable)
@@ -47,6 +47,8 @@ override_dh_auto_configure:
          --with-ebpf \
          --with-lua=luajit \
          --with-protobuf \
+         --with-service-user='_dnsdist' \
+         --with-service-group='_dnsdist' \
          $(CONFIGURE_ARGS)
 
 override_dh_auto_build-arch:
@@ -54,8 +56,6 @@ override_dh_auto_build-arch:
 
 override_dh_install:
        dh_install
-       echo Patching uid and git into debian/dnsdist/lib/systemd/system/*.service
-       perl -pi -e 's/(^ExecStart=.*)/$$1 -u _dnsdist -g _dnsdist/' debian/dnsdist/lib/systemd/system/*.service
 ifeq ($(DEB_HOST_ARCH_BITS),32)
        echo RestrictAddressFamilies is broken on 32bit, removing it from service file
        perl -ni -e 'print unless /RestrictAddressFamilies/' debian/dnsdist/lib/systemd/system/*.service
@@ -74,3 +74,9 @@ override_dh_strip:
 override_dh_installinit:
        dh_installinit
        dh_systemd_start -pdnsdist --restart-after-upgrade dnsdist.service
+
+override_dh_fixperms:
+       dh_fixperms
+        # these files often contain passwords. 640 as it is chowned to root:_dnsdist
+       touch debian/dnsdist/etc/dnsdist/dnsdist.conf
+       chmod 0640 debian/dnsdist/etc/dnsdist/dnsdist.conf
index 319a26406a4ff8a367cc228ed1953382f706791c..8f7a7ce1811d71ed2837024f028d53166bd9ec7e 100644 (file)
@@ -18,6 +18,12 @@ case "$1" in
 
     adduser --force-badname --system --home /nonexistent --group \
         --no-create-home --quiet _dnsdist || true
+
+    if [ "`stat -c '%U:%G' /etc/dnsdist/dnsdist.conf`" = "root:root" ]; then
+      chown root:_dnsdist /etc/dnsdist/dnsdist.conf
+      # Make sure that dnsdist can read it; the default used to be 0600
+      chmod g+r /etc/dnsdist/dnsdist.conf
+    fi
   ;;
 
   abort-upgrade|abort-remove|abort-deconfigure)
index 4ae2ee8a810c18eaa88726d3301b6f610248ab89..e058f0e252e5e3a10ca8ae5c9648f4273d929562 100755 (executable)
@@ -50,6 +50,8 @@ override_dh_auto_configure:
          --with-ebpf \
          --with-lua=luajit \
          --with-protobuf \
+         --with-service-user='_dnsdist' \
+         --with-service-group='_dnsdist' \
          $(CONFIGURE_ARGS)
 
 override_dh_auto_build-arch:
@@ -57,8 +59,6 @@ override_dh_auto_build-arch:
 
 override_dh_install:
        dh_auto_install
-       echo Patching uid and git into debian/dnsdist/lib/systemd/system/*.service
-       perl -pi -e 's/(^ExecStart=.*)/$$1 -u _dnsdist -g _dnsdist/' debian/dnsdist/lib/systemd/system/*.service
 ifeq ($(DEB_HOST_ARCH_BITS),32)
        echo RestrictAddressFamilies is broken on 32bit, removing it from service file
        perl -ni -e 'print unless /RestrictAddressFamilies/' debian/dnsdist/lib/systemd/system/*.service
@@ -74,3 +74,8 @@ override_dh_installexamples:
 override_dh_installinit:
        # do nothing here. avoids referencing a non-existant init script.
 
+override_dh_fixperms:
+       dh_fixperms
+        # these files often contain passwords. 640 as it is chowned to root:_dnsdist
+       touch debian/dnsdist/etc/dnsdist/dnsdist.conf
+       chmod 0640 debian/dnsdist/etc/dnsdist/dnsdist.conf
index 4f48621df5fef9f8c95db6333822c286431a95d8..85bb0940cf40a7c2adac8fa7465451c7468a0699 100644 (file)
@@ -1,5 +1,6 @@
 FROM dist-base as package-builder
-RUN yum install -y rpm-build rpmdevtools /usr/bin/python3 && \
+RUN touch /var/lib/rpm/* && \
+    yum install -y rpm-build rpmdevtools /usr/bin/python3 && \
     yum groupinstall -y "Development Tools" && \
     rpmdev-setuptree
 
@@ -22,7 +23,7 @@ ADD builder-support/specs/ /pdns/builder-support/specs
 RUN find /pdns/builder-support/specs/ -not -name '*.spec' -exec ln -s {} /root/rpmbuild/SOURCES/ \;
 
 @IF [ ! -z "$M_authoritative" ]
-RUN if $(grep -q 'release 6' /etc/redhat-release); then \
+RUN touch /var/lib/rpm/* && if $(grep -q 'release 6' /etc/redhat-release); then \
       scl enable devtoolset-7 -- builder/helpers/build-specs.sh builder-support/specs/pdns.spec; \
     else \
       builder/helpers/build-specs.sh builder-support/specs/pdns.spec; \
@@ -30,7 +31,7 @@ RUN if $(grep -q 'release 6' /etc/redhat-release); then \
 @ENDIF
 
 @IF [ ! -z "$M_recursor" ]
-RUN if $(grep -q 'release 6' /etc/redhat-release); then \
+RUN touch /var/lib/rpm/* &&  if $(grep -q 'release 6' /etc/redhat-release); then \
       scl enable devtoolset-7 -- builder/helpers/build-specs.sh builder-support/specs/pdns-recursor.spec; \
     else \
       builder/helpers/build-specs.sh builder-support/specs/pdns-recursor.spec; \
@@ -42,14 +43,14 @@ RUN if $(grep -q 'release 6' /etc/redhat-release); then \
       true ; \
     else \
       mkdir /libh2o && cd /libh2o && \
-      yum install -y curl openssl-devel cmake && \
+      touch /var/lib/rpm/* && yum install -y curl openssl-devel cmake && \
       curl -L https://github.com/h2o/h2o/archive/v2.2.6.tar.gz | tar xz && \
       CFLAGS='-fPIC' cmake -DWITH_PICOTLS=off -DWITH_BUNDLED_SSL=off -DWITH_MRUBY=off -DCMAKE_INSTALL_PREFIX=/opt ./h2o-2.2.6 && \
       make install && \
       cd /pdns; \
     fi
 
-RUN if $(grep -q 'release 6' /etc/redhat-release); then \
+RUN touch /var/lib/rpm/* && if $(grep -q 'release 6' /etc/redhat-release); then \
       scl enable devtoolset-7 -- builder/helpers/build-specs.sh builder-support/specs/dnsdist.spec; \
     else \
       builder/helpers/build-specs.sh builder-support/specs/dnsdist.spec; \
index 43b17135a2782358f9e94ef38d77cf3b9d87ceaa..32b7953b9a423087da30a62df4fd3f528d4f3b7b 100644 (file)
@@ -5,7 +5,7 @@
 # Put only the bare minimum of common commands here, without dev tools
 FROM amazonlinux:2 as dist-base
 ARG BUILDER_CACHE_BUSTER=
-RUN yum install -y https://dl.fedoraproject.org/pub/epel/epel-release-latest-7.noarch.rpm
+RUN touch /var/lib/rpm/* && yum install -y https://dl.fedoraproject.org/pub/epel/epel-release-latest-7.noarch.rpm
 
 # Do the actual rpm build
 @INCLUDE Dockerfile.rpmbuild
index f60feef89b9ecfb7452482885940a284d37609cc..28e73cf1604b64f06534491ecc198e2925e4d4c7 100644 (file)
@@ -6,8 +6,8 @@
 FROM centos:6 as dist-base
 ARG BUILDER_CACHE_BUSTER=
 RUN which yum
-RUN yum clean all
-RUN yum install -y --verbose epel-release centos-release-scl-rh && \
+RUN touch /var/lib/rpm/* && yum clean all
+RUN touch /var/lib/rpm/* && yum install -y --verbose epel-release centos-release-scl-rh && \
     yum install -y --nogpgcheck devtoolset-7-gcc-c++
 
 # Do the actual rpm build
index 631308285dbe969916dcab2654ded73af4b91d04..68acb73001dfe974d355766b4991759891e3015a 100644 (file)
@@ -5,7 +5,7 @@
 # Put only the bare minimum of common commands here, without dev tools
 FROM centos:7 as dist-base
 ARG BUILDER_CACHE_BUSTER=
-RUN yum install -y epel-release
+RUN touch /var/lib/rpm/* && yum install -y epel-release
 
 # Do the actual rpm build
 @INCLUDE Dockerfile.rpmbuild
index 0a123c546ff9c7f01fc458c0906f3d2d113bd6ca..02316043cbee6bd1647d91a3181f6514cb64ce5c 100644 (file)
@@ -5,7 +5,7 @@
 # Put only the bare minimum of common commands here, without dev tools
 FROM centos:8 as dist-base
 ARG BUILDER_CACHE_BUSTER=
-RUN yum install -y epel-release && \
+RUN touch /var/lib/rpm/* && yum install -y epel-release && \
     dnf install -y 'dnf-command(config-manager)' && \
     dnf config-manager --set-enabled PowerTools
 
index 1e20a167003fcb638ff023c4ac2ec7fcac41b905..8ea0d0a260e054135f41bf5d15b39cd3b71e6767 100644 (file)
@@ -144,7 +144,16 @@ will be loaded at first request.
 .. note::
   This does not add the zone to the :ref:`setting-bind-config` file.
 
-``bind-domain-status <domain> [domain]``
+``bind-domain-extended-status [domain ...]``
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+.. versionadded:: 4.3.0
+
+Output an extended status of a domain or domains, containing much more information than
+the simple domain status, like the number of records currently loaded, whether pdns
+is master or slave for the domain, the list of masters, various timers, etc
+
+``bind-domain-status [domain ...]``
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
 Output status of domain or domains. Can be one of:
index 73aa0f54b3d58b348d3769f91416bcec03248003..5d8b5916373f4d3c59d3044b7e451cf4cd1dd398 100644 (file)
@@ -153,6 +153,6 @@ Only enable this if you are certain you need to. For more discussion, see https:
 Default Schema
 --------------
 
-This is the 4.2 schema. Please find `the 4.1 schema on GitHub <https://github.com/PowerDNS/pdns/blob/rel/auth-4.1.x/modules/gmysqlbackend/schema.mysql.sql>`_.
+This is the 4.3 schema. Please find `the 4.2 schema <https://github.com/PowerDNS/pdns/blob/rel/auth-4.2.x/modules/gmysqlbackend/schema.mysql.sql>`_ and `the 4.1 schema <https://github.com/PowerDNS/pdns/blob/rel/auth-4.1.x/modules/gmysqlbackend/schema.mysql.sql>`_ on GitHub.
 
 .. literalinclude:: ../../modules/gmysqlbackend/schema.mysql.sql
index 2b1400ae6fd0c0a9ad6b80d517d259f91551225a..639b52dad89fad991d95f26918c56820cda12cd4 100644 (file)
@@ -111,7 +111,7 @@ For convenience, a schema for MS SQL Server has been created: (Note:
 This schema can also be found in the PowerDNS source as
 ``modules/godbcbackend/schema.mssql.sql``).
 
-This is the schema for 4.2. For 4.1, please find `the 4.1 schema on GitHub <https://github.com/PowerDNS/pdns/blob/rel/auth-4.1.x/modules/godbcbackend/schema.mssql.sql>`_.
+This is the schema for 4.3. Please find `the 4.2 schema <https://github.com/PowerDNS/pdns/blob/rel/auth-4.2.x/modules/godbcbackend/schema.mssql.sql>`_ and `the 4.1 schema <https://github.com/PowerDNS/pdns/blob/rel/auth-4.1.x/modules/godbcbackend/schema.mssql.sql>`_ on GitHub.
 
 .. literalinclude:: ../../modules/godbcbackend/schema.mssql.sql
    :language: SQL
index c771876834136dce31f870f09a054cc66974ebe4..f65b4bf970316465e1f73423e7b3803d9233b992 100644 (file)
@@ -94,7 +94,7 @@ Default: "".
 Default schema
 --------------
 
-This is the 4.2 schema. Please find `the 4.1 schema on GitHub <https://github.com/PowerDNS/pdns/blob/rel/auth-4.1.x/modules/gpgsqlbackend/schema.pgsql.sql>`_.
+This is the 4.3 schema. Please find `the 4.2 schema <https://github.com/PowerDNS/pdns/blob/rel/auth-4.2.x/modules/gpgsqlbackend/schema.pgsql.sql>`_ and the `the 4.1 schema <https://github.com/PowerDNS/pdns/blob/rel/auth-4.1.x/modules/gpgsqlbackend/schema.pgsql.sql>`_ on GitHub.
 
 .. literalinclude:: ../../modules/gpgsqlbackend/schema.pgsql.sql
    :language: SQL
index e2c595720321e2efe28a4b01a7a0d96fb80357d9..c3a5d4a538493bc2c224c5f34e27b563d4fc9336 100644 (file)
@@ -33,8 +33,8 @@ Setting up the database
 ------------------------
 
 Before you can use this backend you first have to set it up and fill it
-with data. The default setup conforms to the following schema in 4.2.
-If you have not upgraded to 4.2, please use `the 4.1 schema on GitHub <https://github.com/PowerDNS/pdns/blob/rel/auth-4.1.x/modules/gsqlite3backend/schema.sqlite3.sql>`_.
+with data. The default setup conforms to the following schema in 4.3.
+If you have not upgraded to 4.3, please use `the 4.2 schema <https://github.com/PowerDNS/pdns/blob/rel/auth-4.2.x/modules/gsqlite3backend/schema.sqlite3.sql>`_ or `the 4.1 schema <https://github.com/PowerDNS/pdns/blob/rel/auth-4.1.x/modules/gsqlite3backend/schema.sqlite3.sql>`_ on GitHub.
 
 .. literalinclude:: ../../modules/gsqlite3backend/schema.sqlite3.sql
 
index 6fef5efad9009bb4f7d99c9650c01a28edcf2e7a..672fc7d64f3d5525da2a8ffa65974efac128ad8b 100644 (file)
@@ -1,7 +1,7 @@
 PowerDNS Security Policy
 ------------------------
 
-If you have a security problem to report, please email us at both security@powerdns.com and ahu@ds9a.nl.
+If you have a security problem to report, please email us at both peter.van-dijk@powerdns.com and remi.gacogne@powerdns.com.
 In case you want to encrypt your report using PGP, please use: https://www.powerdns.com/powerdns-keyblock.asc
 
 Please do not mail security issues to public lists, nor file a ticket, unless we do not get back to you in a timely manner.
index bca3a9cee1f5b25dc1e3ab9320bb6f5b2e9f83da..b5e05a1f3d6b6fd12f72529cdb78168d46121e9d 100644 (file)
@@ -46,9 +46,9 @@ Example: configuring MySQL
 --------------------------
 
 Connect to MySQL as a user with sufficient privileges and issue the
-following commands below if you are running the 4.2 or master version of PowerDNS:
+following commands below if you are running the 4.3 or master version of PowerDNS:
 
-Please find `the 4.1 schema on GitHub <https://github.com/PowerDNS/pdns/blob/rel/auth-4.1.x/modules/gmysqlbackend/schema.mysql.sql>`_.
+Please find `the 4.2 schema <https://github.com/PowerDNS/pdns/blob/rel/auth-4.2.x/modules/gmysqlbackend/schema.mysql.sql>`_ and `the 4.1 schema <https://github.com/PowerDNS/pdns/blob/rel/auth-4.1.x/modules/gmysqlbackend/schema.mysql.sql>`_ on GitHub.
 
 
 .. literalinclude:: ../../modules/gmysqlbackend/schema.mysql.sql
index ab0cbdf90359c2d34b61b13bf473aae4272bf6f2..be8ca834a2a4827e2b5f403aeae81833a98f411e 100644 (file)
@@ -412,7 +412,12 @@ paths:
           description: |
             When set to the name of a specific statistic, only this value is returned.
             If no statistic with that name exists, the response has a 422 status and an error message.
-
+        - name: includerings
+          in: query
+          required: false
+          type: boolean
+          default: true
+          description: '“true” (default) or “false”, whether to include the Ring items, which can contain thousands of log messages or queried domains. Setting this to ”false” may make the response a lot smaller.'
       responses:
         '200':
           description: List of Statistic Items
index fa28d9b15788cffa4fdae2cb4899856a57e76dba..04fc888261f8ad9491471e744e2246e130a3a98a 100644 (file)
@@ -34,6 +34,14 @@ When using the BIND backend, add a zone. This zone is added in-memory
 and served immediately. Note that this does not add the zone to the
 bind-config file. *FILENAME* must be an absolute path.
 
+bind-domain-extended-status [*DOMAIN*...]
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Output an extended status of all domains, containing much more information than
+the simple domain status, like the number of records currently loaded, whether pdns
+is master or slave for the domain, the list of masters, various timers, etc
+Optionally, append *DOMAIN*\ s to get the status of specific zones.
+
 bind-domain-status [*DOMAIN*...]
 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
index 82c9d37578700c3566ec68bee11ea71ef4d1a9ea..ff9e4f4823efadc945d80a746ae1d67c812d842d 100644 (file)
@@ -1,4 +1,4 @@
-@       86400   IN  SOA pdns-public-ns1.powerdns.com. pieter\.lexis.powerdns.com. 2020013100 10800 3600 604800 10800
+@       86400   IN  SOA pdns-public-ns1.powerdns.com. pieter\.lexis.powerdns.com. 2020020300 10800 3600 604800 10800
 @       3600    IN  NS  pdns-public-ns1.powerdns.com.
 @       3600    IN  NS  pdns-public-ns2.powerdns.com.
 
@@ -207,7 +207,8 @@ recursor-4.3.0-alpha1.security-status                   60 IN TXT "2 Unsupported
 recursor-4.3.0-alpha2.security-status                   60 IN TXT "2 Unsupported pre-release (no known vulnerabilities)"
 recursor-4.3.0-alpha3.security-status                   60 IN TXT "2 Unsupported pre-release (no known vulnerabilities)"
 recursor-4.3.0-beta1.security-status                    60 IN TXT "2 Unsupported pre-release (no known vulnerabilities)"
-recursor-4.3.0-beta2.security-status                    60 IN TXT "1 OK"
+recursor-4.3.0-beta2.security-status                    60 IN TXT "2 Unsupported pre-release (no known vulnerabilities)"
+recursor-4.3.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/"
index df97df2e74eba1e8d6aacfa6db1cfb2830badd35..0716c9487e0ffee0bd5db71b97e87d28d4b7a881 100644 (file)
@@ -33,7 +33,7 @@ with the key name in the content field. For example::
 
     $ dig -t axfr powerdnssec.org @127.0.0.1 -y 'test:kp4/24gyYsEzbuTVJRUMoqGFmN3LYgVDzJ/3oRSP7ys='
 
-Another of importing and activating TSIG keys into the database is using
+Another way of importing and activating TSIG keys into the database is using
 :doc:`pdnsutil <manpages/pdnsutil.1>`:
 
 .. code-block:: shell
index cd1ddbfe0f132a2349b16390e2b7f1d2114b9b29..fe383aa8b956aa36debeae73663c295c417dcf06 100644 (file)
@@ -567,16 +567,17 @@ string Bind2Backend::DLReloadNowHandler(const vector<string>&parts, Utility::pid
 string Bind2Backend::DLDomStatusHandler(const vector<string>&parts, Utility::pid_t ppid)
 {
   ostringstream ret;
-      
+
   if(parts.size() > 1) {
     for(vector<string>::const_iterator i=parts.begin()+1;i<parts.end();++i) {
       BB2DomainInfo bbd;
-      if(safeGetBBDomainInfo(DNSName(*i), &bbd)) {     
+      if(safeGetBBDomainInfo(DNSName(*i), &bbd)) {
         ret<< *i << ": "<< (bbd.d_loaded ? "": "[rejected]") <<"\t"<<bbd.d_status<<"\n";
-    }
-      else
+      }
+      else {
         ret<< *i << " no such domain\n";
-    }    
+      }
+    }
   }
   else {
     ReadLock rl(&s_state_lock);
@@ -591,6 +592,69 @@ string Bind2Backend::DLDomStatusHandler(const vector<string>&parts, Utility::pid
   return ret.str();
 }
 
+static void printDomainExtendedStatus(ostringstream& ret, const BB2DomainInfo& info)
+{
+  ret << info.d_name << ": " << std::endl;
+  ret << "\t Status: " << info.d_status << std::endl;
+  ret << "\t Internal ID: " << info.d_id << std::endl;
+  ret << "\t On-disk file: " << info.d_filename << " (" << info.d_ctime << ")" << std::endl;
+  ret << "\t Kind: ";
+  switch (info.d_kind) {
+  case DomainInfo::Master:
+    ret << "Master";
+    break;
+  case DomainInfo::Slave:
+    ret << "Slave";
+    break;
+  default:
+    ret << "Native";
+  }
+  ret << std::endl;
+  ret << "\t Masters: " << std::endl;
+  for (const auto& master : info.d_masters) {
+    ret << "\t\t - " << master.toStringWithPort() << std::endl;
+  }
+  ret << "\t Also Notify: " << std::endl;
+  for (const auto& also : info.d_also_notify) {
+    ret << "\t\t - " << also << std::endl;
+  }
+  ret << "\t Number of records: " << info.d_records.getEntriesCount() << std::endl;
+  ret << "\t Loaded: " << info.d_loaded << std::endl;
+  ret << "\t Check now: " << info.d_checknow << std::endl;
+  ret << "\t Check interval: " << info.getCheckInterval() << std::endl;
+  ret << "\t Last check: " << info.d_lastcheck << std::endl;
+  ret << "\t Last notified: " << info.d_lastnotified << std::endl;
+}
+
+string Bind2Backend::DLDomExtendedStatusHandler(const vector<string>&parts, Utility::pid_t ppid)
+{
+  ostringstream ret;
+
+  if (parts.size() > 1) {
+    for (const auto& part : parts) {
+      BB2DomainInfo bbd;
+      if (safeGetBBDomainInfo(DNSName(part), &bbd)) {
+        printDomainExtendedStatus(ret, bbd);
+      }
+      else {
+        ret << part << " no such domain" << std::endl;
+      }
+    }
+  }
+  else {
+    ReadLock rl(&s_state_lock);
+    for (const auto& state : s_state) {
+      printDomainExtendedStatus(ret, state);
+    }
+  }
+
+  if (ret.str().empty()) {
+    ret << "no domains passed" << std::endl;
+  }
+
+  return ret.str();
+}
+
 string Bind2Backend::DLListRejectsHandler(const vector<string>&parts, Utility::pid_t ppid)
 {
   ostringstream ret;
@@ -676,6 +740,7 @@ Bind2Backend::Bind2Backend(const string &suffix, bool loadZones)
   extern DynListener *dl;
   dl->registerFunc("BIND-RELOAD-NOW", &DLReloadNowHandler, "bindbackend: reload domains", "<domains>");
   dl->registerFunc("BIND-DOMAIN-STATUS", &DLDomStatusHandler, "bindbackend: list status of all domains", "[domains]");
+  dl->registerFunc("BIND-DOMAIN-EXTENDED-STATUS", &DLDomExtendedStatusHandler, "bindbackend: list the extended status of all domains", "[domains]");
   dl->registerFunc("BIND-LIST-REJECTS", &DLListRejectsHandler, "bindbackend: list rejected domains");
   dl->registerFunc("BIND-ADD-ZONE", &DLAddDomainHandler, "bindbackend: add zone", "<domain> <filename>");
 }
index 4140b0aa0ba2927370c3f4b8f1e137890398c102..dfb16198503f14f3cf71cc146a38e7cc25a7cf76 100644 (file)
@@ -121,6 +121,12 @@ public:
     return ret;
   }
 
+  size_t getEntriesCount() const
+  {
+    std::lock_guard<std::mutex> lock(s_lock);
+    return d_records->size();
+  }
+
 private:
   static std::mutex s_lock;
   shared_ptr<T> d_records;
@@ -136,6 +142,10 @@ public:
   bool current();
   //! configure how often this domain should be checked for changes (on disk)
   void setCheckInterval(time_t seconds);
+  time_t getCheckInterval() const
+  {
+    return d_checkinterval;
+  }
 
   DNSName d_name;   //!< actual name of the domain
   DomainInfo::DomainKind d_kind; //!< the kind of domain
@@ -299,6 +309,7 @@ private:
   static void insertRecord(std::shared_ptr<recordstorage_t>& records, const DNSName& zoneName, const DNSName &qname, const QType &qtype, const string &content, int ttl, const std::string& hashed=string(), bool *auth=nullptr);
   void reload() override;
   static string DLDomStatusHandler(const vector<string>&parts, Utility::pid_t ppid);
+  static string DLDomExtendedStatusHandler(const vector<string>&parts, Utility::pid_t ppid);
   static string DLListRejectsHandler(const vector<string>&parts, Utility::pid_t ppid);
   static string DLReloadNowHandler(const vector<string>&parts, Utility::pid_t ppid);
   static string DLAddDomainHandler(const vector<string>&parts, Utility::pid_t ppid);
diff --git a/modules/opendbxbackend/odbxprivate.cc b/modules/opendbxbackend/odbxprivate.cc
deleted file mode 100644 (file)
index 3f5d246..0000000
+++ /dev/null
@@ -1,289 +0,0 @@
-/*
- * This file is part of PowerDNS or dnsdist.
- * Copyright -- PowerDNS.COM B.V. and its contributors
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of version 2 of the GNU General Public License as
- * published by the Free Software Foundation.
- *
- * In addition, for the avoidance of any doubt, permission is granted to
- * link this program with OpenSSL and to (re)distribute the binaries
- * produced as the result of such linking.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-#ifdef HAVE_CONFIG_H
-#include "config.h"
-#endif
-#include "odbxbackend.hh"
-
-
-
-unsigned int odbx_host_index[2] = { 0, 0 };
-
-
-
-bool OdbxBackend::connectTo( const vector<string>& hosts, QueryType type )
-{
-        int err;
-        unsigned int h, i;
-        int idx = odbx_host_index[type]++ % hosts.size();
-
-
-        if( m_handle[type] != NULL )
-        {
-               odbx_unbind( m_handle[type] );
-               odbx_finish( m_handle[type] );
-               m_handle[type] = NULL;
-        }
-
-        if( type == WRITE && getArg( "backend" ) == "sqlite" )
-        {
-               g_log.log( m_myname + " Using same SQLite connection for reading and writing to '" + hosts[odbx_host_index[READ]] + "'", Logger::Notice );
-               m_handle[WRITE] = m_handle[READ];
-               return true;
-        }
-
-        for( i = 0; i < hosts.size(); i++ )
-        {
-               h = ( idx + i ) % hosts.size();
-
-               if( ( err = odbx_init( &(m_handle[type]), getArg( "backend" ).c_str(), hosts[h].c_str(), getArg( "port" ).c_str() ) ) == ODBX_ERR_SUCCESS )
-               {
-                       if( ( err = odbx_bind( m_handle[type], getArg( "database" ).c_str(), getArg( "username" ).c_str(), getArg( "password" ).c_str(), ODBX_BIND_SIMPLE ) ) == ODBX_ERR_SUCCESS )
-                       {
-                               g_log.log( m_myname + " Database connection (" + (type ? "write" : "read") + ") to '" + hosts[h] + "' succeeded", Logger::Notice );
-                               return true;
-                       }
-
-                       g_log.log( m_myname + " Unable to bind to database on host " + hosts[h] + " - " + string( odbx_error( m_handle[type], err ) ),  Logger::Error );
-                       continue;
-               }
-
-               g_log.log( m_myname + " Unable to connect to server on host " + hosts[h] + " - " + string( odbx_error( m_handle[type], err ) ),  Logger::Error );
-        }
-
-        m_handle[type] = NULL;
-        return false;
-}
-
-
-
-bool OdbxBackend::execStmt( const char* stmt, unsigned long length, QueryType type )
-{
-        int err;
-
-
-        DLOG( g_log.log( m_myname + " execStmt()", Logger::Debug ) );
-
-        if( m_qlog ) { g_log.log( m_myname + " Query: " + stmt, Logger::Info ); }
-
-        if( ( err = odbx_query( m_handle[type], stmt, length ) ) < 0 )
-        {
-               g_log.log( m_myname + " execStmt: Unable to execute query - " + string( odbx_error( m_handle[type], err ) ),  Logger::Error );
-
-               if( err != -ODBX_ERR_PARAM && odbx_error_type( m_handle[type], err ) > 0 ) { return false; }   // ODBX_ERR_PARAM workaround
-               if( !connectTo( m_hosts[type], type ) ) { return false; }
-               if( odbx_query( m_handle[type], stmt, length ) < 0 ) { return false; }
-        }
-
-        if( type == WRITE ) { while( getRecord( type ) ); }
-
-        return true;
-}
-
-
-
-bool OdbxBackend::getRecord( QueryType type )
-{
-        int err = 3;
-
-
-        DLOG( g_log.log( m_myname + " getRecord()", Logger::Debug ) );
-
-        do
-        {
-               if( err < 0 )
-               {
-                       g_log.log( m_myname + " getRecord: Unable to get next result - " + string( odbx_error( m_handle[type], err ) ),  Logger::Error );
-                       throw( PDNSException( "Error: odbx_result() failed" ) );
-               }
-
-               if( m_result != NULL )
-               {
-                       if( err == 3 )
-                       {
-                               if( ( err = odbx_row_fetch( m_result ) ) < 0 )
-                               {
-                                       g_log.log( m_myname + " getRecord: Unable to get next row - " + string( odbx_error( m_handle[type], err ) ),  Logger::Error );
-                                       throw( PDNSException( "Error: odbx_row_fetch() failed" ) );
-                               }
-
-                               if( err > 0 )
-                               {
-#ifdef VERBOSELOG
-                                       unsigned int i;
-                                       string fields;
-
-                                       for( i = 0; i < odbx_column_count( m_result ); i++ )
-                                       {
-                                               fields += string( odbx_column_name( m_result, i ) );
-
-                                               if( odbx_field_value( m_result, i ) != NULL )
-                                               {
-                                                       fields += "=" + string( odbx_field_value( m_result, i ) ) + ", ";
-                                               }
-                                               else
-                                               {
-                                                       fields += "=NULL, ";
-                                               }
-                                       }
-
-                                       g_log.log( m_myname + " Values: " + fields,  Logger::Error );
-#endif
-                                       return true;
-                               }
-
-                       }
-
-                       odbx_result_free( m_result );
-                       m_result = NULL;
-               }
-        }
-        while( ( err =  odbx_result( m_handle[type], &m_result, NULL, 0 ) ) != 0 );
-
-        m_result = NULL;
-        return false;
-}
-
-
-
-string OdbxBackend::escape( const string& str, QueryType type )
-{
-        int err;
-        unsigned long len = sizeof( m_escbuf );
-
-
-        DLOG( g_log.log( m_myname + " escape(string)", Logger::Debug ) );
-
-        if( ( err = odbx_escape( m_handle[type], str.c_str(), str.size(), m_escbuf, &len ) ) < 0 )
-        {
-               g_log.log( m_myname + " escape(string): Unable to escape string - " + string( odbx_error( m_handle[type], err ) ),  Logger::Error );
-
-               if( err != -ODBX_ERR_PARAM && odbx_error_type( m_handle[type], err ) > 0 ) { throw( runtime_error( "odbx_escape() failed" ) ); }   // ODBX_ERR_PARAM workaround
-               if( !connectTo( m_hosts[type], type ) ) { throw( runtime_error( "odbx_escape() failed" ) ); }
-               if( odbx_escape( m_handle[type], str.c_str(), str.size(), m_escbuf, &len ) < 0 ) { throw( runtime_error( "odbx_escape() failed" ) ); }
-        }
-
-        return string( m_escbuf, len );
-}
-
-
-
-bool OdbxBackend::getDomainList( const string& stmt, vector<DomainInfo>* domains, bool (*check_fcn)(uint32_t,uint32_t,SOAData*,DomainInfo*) )
-{
-        const char* tmp;
-        uint32_t nlast, nserial;
-
-        SOAData sd;
-
-        DLOG( g_log.log( m_myname + " getDomainList()", Logger::Debug ) );
-
-        if( !execStmt( stmt.c_str(), stmt.size(), READ ) ) { return false; }
-        if( !getRecord( READ ) ) { return false; }
-
-        do
-        {
-               DomainInfo di;
-               nlast = 0;
-               nserial = 0;
-               sd.serial = 0;
-               sd.refresh = 0;
-
-               if( ( tmp = odbx_field_value( m_result, 6 ) ) != NULL )
-               {
-                       fillSOAData( string( tmp, odbx_field_length( m_result, 6 ) ), sd );
-               }
-
-               if( !sd.serial && ( tmp = odbx_field_value( m_result, 5 ) ) != NULL )
-               {
-                       sd.serial = strtol( tmp, NULL, 10 );
-               }
-
-               if( ( tmp = odbx_field_value( m_result, 4 ) ) != NULL )
-               {
-                       nserial = strtol( tmp, NULL, 10 );
-               }
-
-               if( ( tmp = odbx_field_value( m_result, 3 ) ) != NULL )
-               {
-                       nlast = strtol( tmp, NULL, 10 );
-               }
-
-               if( (*check_fcn)( nlast, nserial, &sd, &di ) )
-               {
-                       if( ( tmp = odbx_field_value( m_result, 2 ) ) != NULL )
-                       {
-                               vector<string> masters;
-                               stringtok(masters, string( tmp, odbx_field_length( m_result, 2 )), ", \t" );
-                               for(const auto& m : masters)
-                               {
-                                       di.masters.emplace_back(m, 53);
-                               }
-                       }
-
-                       if( ( tmp = odbx_field_value( m_result, 1 ) ) != NULL )
-                       {
-                               di.zone = DNSName( string(tmp, odbx_field_length( m_result, 1 )) );
-                       }
-
-                       if( ( tmp = odbx_field_value( m_result, 0 ) ) != NULL )
-                       {
-                               di.id = strtol( tmp, NULL, 10 );
-                       }
-
-                       di.last_check = nlast;
-                       di.notified_serial = nserial;
-                       di.serial = sd.serial;
-                       di.backend = this;
-
-                       domains->push_back( di );
-               }
-        }
-        while( getRecord( READ ) );
-
-        return true;
-}
-
-
-
-bool checkSlave( uint32_t nlast, uint32_t nserial, SOAData* sd, DomainInfo* di )
-{
-        if( nlast + sd->refresh < (uint32_t) time( 0 ) )
-        {
-               di->kind = DomainInfo::Slave;
-               return true;
-        }
-
-        return false;
-}
-
-
-
-bool checkMaster( uint32_t nlast, uint32_t nserial, SOAData* sd, DomainInfo* di )
-{
-        if( nserial != sd->serial )
-        {
-               di->kind = DomainInfo::Master;
-               return true;
-        }
-
-        return false;
-}
index c8c24ede7835c2c4b62fefee2d92dc6b873f7acd..8da7f9c10c558c0f6812733f861c5483fdb36e32 100644 (file)
@@ -346,6 +346,12 @@ void declareStats(void)
 
   S.declare("sys-msec", "Number of msec spent in system time", getSysUserTimeMsec);
   S.declare("user-msec", "Number of msec spent in user time", getSysUserTimeMsec);
+
+#ifdef __linux__
+  S.declare("cpu-iowait", "Time spent waiting for I/O to complete by the whole system, in units of USER_HZ", getCPUIOWait);
+  S.declare("cpu-steal", "Stolen time, which is the time spent by the whole system in other operating systems when running in a virtualized environment, in units of USER_HZ", getCPUSteal);
+#endif
+
   S.declare("meta-cache-size", "Number of entries in the metadata cache", DNSSECKeeper::dbdnssecCacheSizes);
   S.declare("key-cache-size", "Number of entries in the key cache", DNSSECKeeper::dbdnssecCacheSizes);
   S.declare("signature-cache-size", "Number of entries in the signature cache", signatureCacheSize);
index 6c0f78205dac755d1d325aa8fc42a4621b4620f3..6551f3a8341839f04b92d2303957a58912627d4c 100644 (file)
@@ -515,6 +515,7 @@ const std::vector<ConsoleKeyword> g_consoleKeywords{
   { "setMaxTCPQueriesPerConnection", true, "n", "set the maximum number of queries in an incoming TCP connection. 0 means unlimited" },
   { "setMaxTCPQueuedConnections", true, "n", "set the maximum number of TCP connections queued (waiting to be picked up by a client thread)" },
   { "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 65535" },
+  { "SetNegativeAndSOAAction", "true", "nxd, zone, ttl, mname, rname, serial, refresh, retry, expire, minimum [, options]", "Turn a query into a NXDomain or NoData answer and sets a SOA record in the additional section" },
   { "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" },
   { "setPoolServerPolicyLua", true, "name, func, pool", "set the server selection policy for this pool to one named 'name' and provided by 'function'" },
@@ -571,6 +572,7 @@ const std::vector<ConsoleKeyword> g_consoleKeywords{
   { "SNMPTrapResponseAction", true, "[reason]", "send an SNMP trap, adding the optional `reason` string as the response description"},
   { "SpoofAction", true, "ip|list of ips [, options]", "forge a response with the specified IPv4 (for an A query) or IPv6 (for an AAAA). If you specify multiple addresses, all that match the query type (A, AAAA or ANY) will get spoofed in" },
   { "SpoofCNAMEAction", true, "cname [, options]", "Forge a response with the specified CNAME value" },
+  { "SpoofRawAction", true, "raw [, options]", "Forge a response with the specified record data as raw bytes" },
   { "SuffixMatchNodeRule", true, "smn[, quiet]", "Matches based on a group of domain suffixes for rapid testing of membership. Pass true as second parameter to prevent listing of all domains matched" },
   { "TagAction", true, "name, value", "set the tag named 'name' to the given value" },
   { "TagResponseAction", true, "name, value", "set the tag named 'name' to the given value" },
index 67fc5733728184d442c8793e3a91e35a5da3ced2..7cda963d4a1e27d56480f9512e0a1e70d8bd4eec 100644 (file)
@@ -117,6 +117,7 @@ int rewriteResponseWithoutEDNS(const std::string& initialPacket, vector<uint8_t>
       pr.xfrBlob(blob);
       pw.xfrBlob(blob);
     } else {
+
       pr.skip(ah.d_clen);
     }
   }
@@ -125,6 +126,192 @@ int rewriteResponseWithoutEDNS(const std::string& initialPacket, vector<uint8_t>
   return 0;
 }
 
+static bool addOrReplaceECSOption(std::vector<std::pair<uint16_t, std::string>>& options, bool& ecsAdded, bool overrideExisting, const string& newECSOption)
+{
+  for (auto it = options.begin(); it != options.end(); ) {
+    if (it->first == EDNSOptionCode::ECS) {
+      ecsAdded = false;
+
+      if (!overrideExisting) {
+        return false;
+      }
+
+      it = options.erase(it);
+    }
+    else {
+      ++it;
+    }
+  }
+
+  options.emplace_back(EDNSOptionCode::ECS, std::string(&newECSOption.at(EDNS_OPTION_CODE_SIZE + EDNS_OPTION_LENGTH_SIZE), newECSOption.size() - (EDNS_OPTION_CODE_SIZE + EDNS_OPTION_LENGTH_SIZE)));
+  return true;
+}
+
+static bool slowRewriteQueryWithExistingEDNS(const std::string& initialPacket, vector<uint8_t>& newContent, bool& ednsAdded, bool& ecsAdded, bool overrideExisting, const string& newECSOption)
+{
+  assert(initialPacket.size() >= sizeof(dnsheader));
+  const struct dnsheader* dh = reinterpret_cast<const struct dnsheader*>(initialPacket.data());
+
+  ecsAdded = false;
+  ednsAdded = true;
+
+  if (ntohs(dh->qdcount) == 0) {
+    return false;
+  }
+
+  if (ntohs(dh->arcount) == 0) {
+    throw std::runtime_error("slowRewriteQueryWithExistingEDNS() should not be called for queries that have no EDNS");
+  }
+
+  PacketReader pr(initialPacket);
+
+  size_t idx = 0;
+  DNSName rrname;
+  uint16_t qdcount = ntohs(dh->qdcount);
+  uint16_t ancount = ntohs(dh->ancount);
+  uint16_t nscount = ntohs(dh->nscount);
+  uint16_t arcount = ntohs(dh->arcount);
+  uint16_t rrtype;
+  uint16_t rrclass;
+  string blob;
+  struct dnsrecordheader ah;
+
+  rrname = pr.getName();
+  rrtype = pr.get16BitInt();
+  rrclass = pr.get16BitInt();
+
+  DNSPacketWriter pw(newContent, rrname, rrtype, rrclass, dh->opcode);
+  pw.getHeader()->id=dh->id;
+  pw.getHeader()->qr=dh->qr;
+  pw.getHeader()->aa=dh->aa;
+  pw.getHeader()->tc=dh->tc;
+  pw.getHeader()->rd=dh->rd;
+  pw.getHeader()->ra=dh->ra;
+  pw.getHeader()->ad=dh->ad;
+  pw.getHeader()->cd=dh->cd;
+  pw.getHeader()->rcode=dh->rcode;
+
+  /* consume remaining qd if any */
+  if (qdcount > 1) {
+    for(idx = 1; idx < qdcount; idx++) {
+      rrname = pr.getName();
+      rrtype = pr.get16BitInt();
+      rrclass = pr.get16BitInt();
+      (void) rrtype;
+      (void) rrclass;
+    }
+  }
+
+  /* copy AN and NS */
+  for (idx = 0; idx < ancount; idx++) {
+    rrname = pr.getName();
+    pr.getDnsrecordheader(ah);
+
+    pw.startRecord(rrname, ah.d_type, ah.d_ttl, ah.d_class, DNSResourceRecord::ANSWER, true);
+    pr.xfrBlob(blob);
+    pw.xfrBlob(blob);
+  }
+
+  for (idx = 0; idx < nscount; idx++) {
+    rrname = pr.getName();
+    pr.getDnsrecordheader(ah);
+
+    pw.startRecord(rrname, ah.d_type, ah.d_ttl, ah.d_class, DNSResourceRecord::AUTHORITY, true);
+    pr.xfrBlob(blob);
+    pw.xfrBlob(blob);
+  }
+
+  /* consume AR, looking for OPT */
+  for (idx = 0; idx < arcount; idx++) {
+    rrname = pr.getName();
+    pr.getDnsrecordheader(ah);
+
+    if (ah.d_type != QType::OPT) {
+      pw.startRecord(rrname, ah.d_type, ah.d_ttl, ah.d_class, DNSResourceRecord::ADDITIONAL, true);
+      pr.xfrBlob(blob);
+      pw.xfrBlob(blob);
+    } else {
+
+      ednsAdded = false;
+      pr.xfrBlob(blob);
+
+      std::vector<std::pair<uint16_t, std::string>> options;
+      getEDNSOptionsFromContent(blob, options);
+
+      EDNS0Record edns0;
+      static_assert(sizeof(edns0) == sizeof(ah.d_ttl), "sizeof(EDNS0Record) must match sizeof(uint32_t) AKA RR TTL size");
+      memcpy(&edns0, &ah.d_ttl, sizeof(edns0));
+
+      /* addOrReplaceECSOption will set it to false if there is already an existing option */
+      ecsAdded = true;
+      addOrReplaceECSOption(options, ecsAdded, overrideExisting, newECSOption);
+      pw.addOpt(ah.d_class, edns0.extRCode, edns0.extFlags, options, edns0.version);
+    }
+  }
+
+  if (ednsAdded) {
+    pw.addOpt(g_EdnsUDPPayloadSize, 0, 0, {{EDNSOptionCode::ECS, std::string(&newECSOption.at(EDNS_OPTION_CODE_SIZE + EDNS_OPTION_LENGTH_SIZE), newECSOption.size() - (EDNS_OPTION_CODE_SIZE + EDNS_OPTION_LENGTH_SIZE))}}, 0);
+    ecsAdded = true;
+  }
+
+  pw.commit();
+
+  return true;
+}
+
+static bool slowParseEDNSOptions(const char* packet, uint16_t const len, std::shared_ptr<std::map<uint16_t, EDNSOptionView> >& options)
+{
+  const struct dnsheader* dh = reinterpret_cast<const struct dnsheader*>(packet);
+
+  if (len < sizeof(dnsheader) || ntohs(dh->qdcount) == 0)
+ {
+    return false;
+  }
+
+  if (ntohs(dh->arcount) == 0) {
+    throw std::runtime_error("slowParseEDNSOptions() should not be called for queries that have no EDNS");
+  }
+
+  try {
+    uint64_t numrecords = ntohs(dh->ancount) + ntohs(dh->nscount) + ntohs(dh->arcount);
+    DNSPacketMangler dpm(const_cast<char*>(packet), len);
+    uint64_t n;
+    for(n=0; n < ntohs(dh->qdcount) ; ++n) {
+      dpm.skipDomainName();
+      /* type and class */
+      dpm.skipBytes(4);
+    }
+
+    for(n=0; n < numrecords; ++n) {
+      dpm.skipDomainName();
+
+      uint8_t section = n < ntohs(dh->ancount) ? 1 : (n < (ntohs(dh->ancount) + ntohs(dh->nscount)) ? 2 : 3);
+      uint16_t dnstype = dpm.get16BitInt();
+      dpm.get16BitInt();
+      dpm.skipBytes(4); /* TTL */
+
+      if(section == 3 && dnstype == QType::OPT) {
+        uint32_t offset = dpm.getOffset();
+        if (offset >= len) {
+          return false;
+        }
+        /* if we survive this call, we can parse it safely */
+        dpm.skipRData();
+        return getEDNSOptions(packet + offset, len - offset, *options) == 0;
+      }
+      else {
+        dpm.skipRData();
+      }
+    }
+  }
+  catch(...)
+  {
+    return false;
+  }
+
+  return true;
+}
+
 int locateEDNSOptRR(const std::string& packet, uint16_t * optStart, size_t * optLen, bool * last)
 {
   assert(optStart != NULL);
@@ -315,6 +502,11 @@ bool parseEDNSOptions(DNSQuestion& dq)
   }
 
   dq.ednsOptions = std::make_shared<std::map<uint16_t, EDNSOptionView> >();
+
+  if (ntohs(dq.dh->ancount) != 0 || ntohs(dq.dh->nscount) != 0 || (ntohs(dq.dh->arcount) != 0 && ntohs(dq.dh->arcount) != 1)) {
+    return slowParseEDNSOptions(reinterpret_cast<const char*>(dq.dh), dq.len, dq.ednsOptions);
+  }
+
   const char* packet = reinterpret_cast<const char*>(dq.dh);
 
   size_t remaining = 0;
@@ -329,7 +521,7 @@ bool parseEDNSOptions(DNSQuestion& dq)
   return false;
 }
 
-static bool addECSToExistingOPT(char* const packet, size_t const packetSize, uint16_t* const len, const string& newECSOption, unsigned char* optRDLen, bool* const ecsAdded)
+static bool addECSToExistingOPT(char* const packet, size_t const packetSize, uint16_t* const len, const string& newECSOption, unsigned char* optRDLen, bool& ecsAdded)
 {
   /* we need to add one EDNS0 ECS option, fixing the size of EDNS0 RDLENGTH */
   /* getEDNSOptionsStart has already checked that there is exactly one AR,
@@ -348,12 +540,12 @@ static bool addECSToExistingOPT(char* const packet, size_t const packetSize, uin
 
   memcpy(packet + *len, newECSOption.c_str(), newECSOptionSize);
   *len += newECSOptionSize;
-  *ecsAdded = true;
+  ecsAdded = true;
 
   return true;
 }
 
-static bool addEDNSWithECS(char* const packet, size_t const packetSize, uint16_t* const len, const string& newECSOption, bool* const ednsAdded, bool preserveTrailingData)
+static bool addEDNSWithECS(char* const packet, size_t const packetSize, uint16_t* const len, const string& newECSOption, bool& ednsAdded, bool& ecsAdded, bool preserveTrailingData)
 {
   /* we need to add a EDNS0 RR with one EDNS0 ECS option, fixing the AR count */
   string EDNSRR;
@@ -378,27 +570,50 @@ static bool addEDNSWithECS(char* const packet, size_t const packetSize, uint16_t
   uint16_t arcount = ntohs(dh->arcount);
   arcount++;
   dh->arcount = htons(arcount);
-  *ednsAdded = true;
+  ednsAdded = true;
+  ecsAdded = true;
 
   memcpy(packet + realPacketLen, EDNSRR.c_str(), EDNSRR.size());
 
   return true;
 }
 
-bool handleEDNSClientSubnet(char* const packet, const size_t packetSize, const unsigned int consumed, uint16_t* const len, bool* const ednsAdded, bool* const ecsAdded, bool overrideExisting, const string& newECSOption, bool preserveTrailingData)
+bool handleEDNSClientSubnet(char* const packet, const size_t packetSize, const unsigned int consumed, uint16_t* const len, bool& ednsAdded, bool& ecsAdded, bool overrideExisting, const string& newECSOption, bool preserveTrailingData)
 {
   assert(packet != nullptr);
   assert(len != nullptr);
   assert(consumed <= (size_t) *len);
-  assert(ednsAdded != nullptr);
-  assert(ecsAdded != nullptr);
+
+  const struct dnsheader* dh = reinterpret_cast<const struct dnsheader*>(packet);
+
+  if (ntohs(dh->ancount) != 0 || ntohs(dh->nscount) != 0 || (ntohs(dh->arcount) != 0 && ntohs(dh->arcount) != 1)) {
+    vector<uint8_t> newContent;
+    newContent.reserve(packetSize);
+
+    if (!slowRewriteQueryWithExistingEDNS(std::string(packet, *len), newContent, ednsAdded, ecsAdded, overrideExisting, newECSOption)) {
+      ednsAdded = false;
+      ecsAdded = false;
+      return false;
+    }
+
+    if (newContent.size() > packetSize) {
+      ednsAdded = false;
+      ecsAdded = false;
+      return false;
+    }
+
+    memcpy(packet, &newContent.at(0), newContent.size());
+    *len = newContent.size();
+    return true;
+  }
+
   uint16_t optRDPosition = 0;
   size_t remaining = 0;
 
   int res = getEDNSOptionsStart(packet, consumed, *len, &optRDPosition, &remaining);
 
   if (res != 0) {
-    return addEDNSWithECS(packet, packetSize, len, newECSOption, ednsAdded, preserveTrailingData);
+    return addEDNSWithECS(packet, packetSize, len, newECSOption, ednsAdded, ecsAdded, preserveTrailingData);
   }
 
   unsigned char* optRDLen = reinterpret_cast<unsigned char*>(packet) + optRDPosition;
@@ -422,7 +637,7 @@ bool handleEDNSClientSubnet(char* const packet, const size_t packetSize, const u
   return true;
 }
 
-bool handleEDNSClientSubnet(DNSQuestion& dq, bool* ednsAdded, bool* ecsAdded, bool preserveTrailingData)
+bool handleEDNSClientSubnet(DNSQuestion& dq, bool& ednsAdded, bool& ecsAdded, bool preserveTrailingData)
 {
   assert(dq.remote != nullptr);
   string newECSOption;
@@ -624,10 +839,6 @@ int rewriteResponseWithoutEDNSOption(const std::string& initialPacket, const uin
 
 bool addEDNS(dnsheader* dh, uint16_t& len, const size_t size, bool dnssecOK, uint16_t payloadSize, uint8_t ednsrcode)
 {
-  if (dh->arcount != 0) {
-    return false;
-  }
-
   std::string optRecord;
   generateOptRR(std::string(), optRecord, payloadSize, ednsrcode, dnssecOK);
 
@@ -638,7 +849,101 @@ bool addEDNS(dnsheader* dh, uint16_t& len, const size_t size, bool dnssecOK, uin
   char * optPtr = reinterpret_cast<char*>(dh) + len;
   memcpy(optPtr, optRecord.data(), optRecord.size());
   len += optRecord.size();
-  dh->arcount = htons(1);
+  dh->arcount = htons(ntohs(dh->arcount) + 1);
+
+  return true;
+}
+
+/*
+  This function keeps the existing header and DNSSECOK bit (if any) but wipes anything else,
+  generating a NXD or NODATA answer with a SOA record in the additional section.
+*/
+bool setNegativeAndAdditionalSOA(DNSQuestion& dq, bool nxd, const DNSName& zone, uint32_t ttl, const DNSName& mname, const DNSName& rname, uint32_t serial, uint32_t refresh, uint32_t retry, uint32_t expire, uint32_t minimum)
+{
+  if (ntohs(dq.dh->qdcount) != 1) {
+    return false;
+  }
+
+  assert(dq.consumed == dq.qname->wirelength());
+  size_t queryPartSize = sizeof(dnsheader) + dq.consumed + DNS_TYPE_SIZE + DNS_CLASS_SIZE;
+  if (dq.len < queryPartSize) {
+    /* something is already wrong, don't build on flawed foundations */
+    return false;
+  }
+
+  size_t available = dq.size - queryPartSize;
+  uint16_t qtype = htons(QType::SOA);
+  uint16_t qclass = htons(QClass::IN);
+  uint16_t rdLength = mname.wirelength() + rname.wirelength() + sizeof(serial) + sizeof(refresh) + sizeof(retry) + sizeof(expire) + sizeof(minimum);
+  size_t soaSize = zone.wirelength() + sizeof(qtype) + sizeof(qclass) + sizeof(ttl) + sizeof(rdLength) + rdLength;
+
+  if (soaSize > available) {
+    /* not enough space left to add the SOA, sorry! */
+    return false;
+  }
+
+  bool hadEDNS = false;
+  bool dnssecOK = false;
+
+  if (g_addEDNSToSelfGeneratedResponses) {
+    uint16_t payloadSize = 0;
+    uint16_t z = 0;
+    hadEDNS = getEDNSUDPPayloadSizeAndZ(reinterpret_cast<const char*>(dq.dh), dq.len, &payloadSize, &z);
+    if (hadEDNS) {
+      dnssecOK = z & EDNS_HEADER_FLAG_DO;
+    }
+  }
+
+  /* chop off everything after the question */
+  dq.len = queryPartSize;
+  if (nxd) {
+    dq.dh->rcode = RCode::NXDomain;
+  }
+  else {
+    dq.dh->rcode = RCode::NoError;
+  }
+  dq.dh->qr = true;
+  dq.dh->ancount = 0;
+  dq.dh->nscount = 0;
+  dq.dh->arcount = 0;
+
+  rdLength = htons(rdLength);
+  ttl = htonl(ttl);
+  serial = htonl(serial);
+  refresh = htonl(refresh);
+  retry = htonl(retry);
+  expire = htonl(expire);
+  minimum = htonl(minimum);
+
+  std::string soa;
+  soa.reserve(soaSize);
+  soa.append(zone.toDNSString());
+  soa.append(reinterpret_cast<const char*>(&qtype), sizeof(qtype));
+  soa.append(reinterpret_cast<const char*>(&qclass), sizeof(qclass));
+  soa.append(reinterpret_cast<const char*>(&ttl), sizeof(ttl));
+  soa.append(reinterpret_cast<const char*>(&rdLength), sizeof(rdLength));
+  soa.append(mname.toDNSString());
+  soa.append(rname.toDNSString());
+  soa.append(reinterpret_cast<const char*>(&serial), sizeof(serial));
+  soa.append(reinterpret_cast<const char*>(&refresh), sizeof(refresh));
+  soa.append(reinterpret_cast<const char*>(&retry), sizeof(retry));
+  soa.append(reinterpret_cast<const char*>(&expire), sizeof(expire));
+  soa.append(reinterpret_cast<const char*>(&minimum), sizeof(minimum));
+
+  if (soa.size() != soaSize) {
+    throw std::runtime_error("Unexpected SOA response size: " + std::to_string(soa.size()) + " vs " + std::to_string(soaSize));
+  }
+
+  memcpy(reinterpret_cast<char*>(dq.dh) + queryPartSize, soa.c_str(), soa.size());
+
+  dq.len += soa.size();
+
+  dq.dh->arcount = htons(1);
+
+  if (g_addEDNSToSelfGeneratedResponses) {
+    /* now we need to add a new OPT record */
+    return addEDNS(dq.dh, dq.len, dq.size, dnssecOK, g_PayloadSizeSelfGenAnswers, dq.ednsRCode);
+  }
 
   return true;
 }
index 767575723f059ca4320dfb89c88a055fb37fb300..293bc0cd926e01714b79e2f19b7564a7b68897b7 100644 (file)
@@ -37,9 +37,10 @@ int getEDNSOptionsStart(const char* packet, const size_t offset, const size_t le
 bool isEDNSOptionInOpt(const std::string& packet, const size_t optStart, const size_t optLen, const uint16_t optionCodeToFind, size_t* optContentStart = nullptr, uint16_t* optContentLen = nullptr);
 bool addEDNS(dnsheader* dh, uint16_t& len, const size_t size, bool dnssecOK, uint16_t payloadSize, uint8_t ednsrcode);
 bool addEDNSToQueryTurnedResponse(DNSQuestion& dq);
+bool setNegativeAndAdditionalSOA(DNSQuestion& dq, bool nxd, const DNSName& zone, uint32_t ttl, const DNSName& mname, const DNSName& rname, uint32_t serial, uint32_t refresh, uint32_t retry, uint32_t expire, uint32_t minimum);
 
-bool handleEDNSClientSubnet(DNSQuestion& dq, bool* ednsAdded, bool* ecsAdded, bool preserveTrailingData);
-bool handleEDNSClientSubnet(char* const packet, const size_t packetSize, const unsigned int consumed, uint16_t* const len, bool* const ednsAdded, bool* const ecsAdded, bool overrideExisting, const string& newECSOption, bool preserveTrailingData);
+bool handleEDNSClientSubnet(DNSQuestion& dq, bool& ednsAdded, bool& ecsAdded, bool preserveTrailingData);
+bool handleEDNSClientSubnet(char* packet, size_t packetSize, unsigned int consumed, uint16_t* len, bool& ednsAdded, bool& ecsAdded, bool overrideExisting, const string& newECSOption, bool preserveTrailingData);
 
 bool parseEDNSOptions(DNSQuestion& dq);
 
index cc714d23d36e1bc31dfe578eed317419f7c9d426..9134ddb7134d0ad0dd91d83861cbe552e49793ea 100644 (file)
@@ -183,7 +183,7 @@ DNSAction::Action TeeAction::operator()(DNSQuestion* dq, std::string* ruleresult
       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)) {
+      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;
       }
 
@@ -404,19 +404,24 @@ DNSAction::Action SpoofAction::operator()(DNSQuestion* dq, std::string* ruleresu
 {
   uint16_t qtype = dq->qtype;
   // do we even have a response?
-  if(d_cname.empty() && !std::count_if(d_addrs.begin(), d_addrs.end(), [qtype](const ComboAddress& a)
-                                       {
-                                         return (qtype == QType::ANY || ((a.sin4.sin_family == AF_INET && qtype == QType::A) ||
-                                                                         (a.sin4.sin_family == AF_INET6 && qtype == QType::AAAA)));
-                                       }))
+  if (d_cname.empty() &&
+      d_rawResponse.empty() &&
+      d_types.count(qtype) == 0) {
     return Action::None;
+  }
 
   vector<ComboAddress> addrs;
-  unsigned int totrdatalen=0;
+  unsigned int totrdatalen = 0;
+  uint16_t numberOfRecords = 0;
   if (!d_cname.empty()) {
     qtype = QType::CNAME;
     totrdatalen += d_cname.toDNSString().size();
-  } else {
+    numberOfRecords = 1;
+  } else if (!d_rawResponse.empty()) {
+    totrdatalen += d_rawResponse.size();
+    numberOfRecords = 1;
+  }
+  else {
     for(const auto& addr : d_addrs) {
       if(qtype != QType::ANY && ((addr.sin4.sin_family == AF_INET && qtype != QType::A) ||
                                  (addr.sin4.sin_family == AF_INET6 && qtype != QType::AAAA))) {
@@ -424,6 +429,7 @@ DNSAction::Action SpoofAction::operator()(DNSQuestion* dq, std::string* ruleresu
       }
       totrdatalen += addr.sin4.sin_family == AF_INET ? sizeof(addr.sin4.sin_addr.s_addr) : sizeof(addr.sin6.sin6_addr.s6_addr);
       addrs.push_back(addr);
+      ++numberOfRecords;
     }
   }
 
@@ -433,7 +439,7 @@ DNSAction::Action SpoofAction::operator()(DNSQuestion* dq, std::string* ruleresu
   unsigned int consumed=0;
   DNSName ignore((char*)dq->dh, dq->len, sizeof(dnsheader), false, 0, 0, &consumed);
 
-  if (dq->size < (sizeof(dnsheader) + consumed + 4 + ((d_cname.empty() ? 0 : 1) + addrs.size())*12 /* recordstart */ + totrdatalen)) {
+  if (dq->size < (sizeof(dnsheader) + consumed + 4 + numberOfRecords*12 /* recordstart */ + totrdatalen)) {
     return Action::None;
   }
 
@@ -452,14 +458,22 @@ DNSAction::Action SpoofAction::operator()(DNSQuestion* dq, std::string* ruleresu
   dq->dh->ancount = 0;
   dq->dh->arcount = 0; // for now, forget about your EDNS, we're marching over it
 
-  if(qtype == QType::CNAME) {
-    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
-                                       0, 0, 0, 60,   // TTL
-                                       0, (unsigned char)wireData.length()};
-    static_assert(sizeof(recordstart) == 12, "sizeof(recordstart) must be equal to 12, otherwise the above check is invalid");
+  uint32_t ttl = htonl(d_responseConfig.ttl);
+  unsigned char recordstart[] = {0xc0, 0x0c,    // compressed name
+                                 0, 0,          // QTYPE
+                                 0, QClass::IN,
+                                 0, 0, 0, 0,    // TTL
+                                 0, 0 };        // rdata length
+  static_assert(sizeof(recordstart) == 12, "sizeof(recordstart) must be equal to 12, otherwise the above check is invalid");
+  memcpy(&recordstart[6], &ttl, sizeof(ttl));
+  bool raw = false;
+
+  if (qtype == QType::CNAME) {
+    const std::string wireData = d_cname.toDNSString(); // Note! This doesn't do compression!
+    uint16_t rdataLen = htons(wireData.length());
+    qtype = htons(qtype);
+    memcpy(&recordstart[2], &qtype, sizeof(qtype));
+    memcpy(&recordstart[10], &rdataLen, sizeof(rdataLen));
 
     memcpy(dest, recordstart, sizeof(recordstart));
     dest += sizeof(recordstart);
@@ -467,31 +481,41 @@ DNSAction::Action SpoofAction::operator()(DNSQuestion* dq, std::string* ruleresu
     dq->len += wireData.length() + sizeof(recordstart);
     dq->dh->ancount++;
   }
+  else if (!d_rawResponse.empty()) {
+    uint16_t rdataLen = htons(d_rawResponse.size());
+    qtype = htons(qtype);
+    memcpy(&recordstart[2], &qtype, sizeof(qtype));
+    memcpy(&recordstart[10], &rdataLen, sizeof(rdataLen));
+
+    memcpy(dest, recordstart, sizeof(recordstart));
+    dest += sizeof(recordstart);
+    memcpy(dest, d_rawResponse.c_str(), d_rawResponse.size());
+    dq->len += d_rawResponse.size() + sizeof(recordstart);
+    dq->dh->ancount++;
+    raw = true;
+  }
   else {
     for(const auto& addr : addrs) {
-      unsigned char rdatalen = addr.sin4.sin_family == AF_INET ? sizeof(addr.sin4.sin_addr.s_addr) : sizeof(addr.sin6.sin6_addr.s6_addr);
-      const unsigned char recordstart[]={0xc0, 0x0c,    // compressed name
-                                         0, (unsigned char) (addr.sin4.sin_family == AF_INET ? QType::A : QType::AAAA),
-                                         0, QClass::IN, // IN
-                                         0, 0, 0, 60,   // TTL
-                                         0, rdatalen};
-      static_assert(sizeof(recordstart) == 12, "sizeof(recordstart) must be equal to 12, otherwise the above check is invalid");
+      uint16_t rdataLen = htons(addr.sin4.sin_family == AF_INET ? sizeof(addr.sin4.sin_addr.s_addr) : sizeof(addr.sin6.sin6_addr.s6_addr));
+      qtype = htons(addr.sin4.sin_family == AF_INET ? QType::A : QType::AAAA);
+      memcpy(&recordstart[2], &qtype, sizeof(qtype));
+      memcpy(&recordstart[10], &rdataLen, sizeof(rdataLen));
 
       memcpy(dest, recordstart, sizeof(recordstart));
       dest += sizeof(recordstart);
 
       memcpy(dest,
              addr.sin4.sin_family == AF_INET ? (void*)&addr.sin4.sin_addr.s_addr : (void*)&addr.sin6.sin6_addr.s6_addr,
-             rdatalen);
-      dest += rdatalen;
-      dq->len += rdatalen + sizeof(recordstart);
+             addr.sin4.sin_family == AF_INET ? sizeof(addr.sin4.sin_addr.s_addr) : sizeof(addr.sin6.sin6_addr.s6_addr));
+      dest += (addr.sin4.sin_family == AF_INET ? sizeof(addr.sin4.sin_addr.s_addr) : sizeof(addr.sin6.sin6_addr.s6_addr));
+      dq->len += (addr.sin4.sin_family == AF_INET ? sizeof(addr.sin4.sin_addr.s_addr) : sizeof(addr.sin6.sin6_addr.s6_addr)) + sizeof(recordstart);
       dq->dh->ancount++;
     }
   }
 
   dq->dh->ancount = htons(dq->dh->ancount);
 
-  if (hadEDNS) {
+  if (hadEDNS && raw == false) {
     addEDNS(dq->dh, dq->len, dq->size, dnssecOK, g_PayloadSizeSelfGenAnswers, 0);
   }
 
@@ -1236,6 +1260,44 @@ private:
   std::string d_tag;
 };
 
+class SetNegativeAndSOAAction: public DNSAction
+{
+public:
+  SetNegativeAndSOAAction(bool nxd, const DNSName& zone, uint32_t ttl, const DNSName& mname, const DNSName& rname, uint32_t serial, uint32_t refresh, uint32_t retry, uint32_t expire, uint32_t minimum): d_zone(zone), d_mname(mname), d_rname(rname), d_ttl(ttl), d_serial(serial), d_refresh(refresh), d_retry(retry), d_expire(expire), d_minimum(minimum), d_nxd(nxd)
+  {
+  }
+
+  DNSAction::Action operator()(DNSQuestion* dq, std::string* ruleresult) const override
+  {
+    if (!setNegativeAndAdditionalSOA(*dq, d_nxd, d_zone, d_ttl, d_mname, d_rname, d_serial, d_refresh, d_retry, d_expire, d_minimum)) {
+      return Action::None;
+    }
+
+    setResponseHeadersFromConfig(*dq->dh, d_responseConfig);
+
+    return Action::Allow;
+  }
+
+  std::string toString() const override
+  {
+    return std::string(d_nxd ? "NXD " : "NODATA") + " with SOA";
+  }
+
+  ResponseConfig d_responseConfig;
+
+private:
+  DNSName d_zone;
+  DNSName d_mname;
+  DNSName d_rname;
+  uint32_t d_ttl;
+  uint32_t d_serial;
+  uint32_t d_refresh;
+  uint32_t d_retry;
+  uint32_t d_expire;
+  uint32_t d_minimum;
+  bool d_nxd;
+};
+
 template<typename T, typename ActionT>
 static void addAction(GlobalStateHolder<vector<T> > *someRulActions, const luadnsrule_t& var, const std::shared_ptr<ActionT>& action, boost::optional<luaruleparams_t>& params) {
   setLuaSideEffect();
@@ -1250,11 +1312,14 @@ static void addAction(GlobalStateHolder<vector<T> > *someRulActions, const luadn
     });
 }
 
-typedef std::unordered_map<std::string, boost::variant<bool> > responseParams_t;
+typedef std::unordered_map<std::string, boost::variant<bool, uint32_t> > responseParams_t;
 
 static void parseResponseConfig(boost::optional<responseParams_t> vars, ResponseConfig& config)
 {
   if (vars) {
+    if (vars->count("ttl")) {
+      config.ttl = boost::get<uint32_t>((*vars)["ttl"]);
+    }
     if (vars->count("aa")) {
       config.setAA = boost::get<bool>((*vars)["aa"]);
     }
@@ -1378,7 +1443,7 @@ void setupLuaActions()
       return std::shared_ptr<DNSAction>(new QPSPoolAction(limit, a));
     });
 
-  g_lua.writeFunction("SpoofAction", [](boost::variant<std::string,vector<pair<int, std::string>>> inp, boost::optional<std::string> b, boost::optional<responseParams_t> vars ) {
+  g_lua.writeFunction("SpoofAction", [](boost::variant<std::string,vector<pair<int, std::string>>> inp, boost::optional<std::string> b, boost::optional<responseParams_t> vars) {
       vector<ComboAddress> addrs;
       if(auto s = boost::get<std::string>(&inp))
         addrs.push_back(ComboAddress(*s));
@@ -1398,11 +1463,16 @@ void setupLuaActions()
     });
 
   g_lua.writeFunction("SpoofCNAMEAction", [](const std::string& a, boost::optional<responseParams_t> vars) {
-      auto ret = std::shared_ptr<DNSAction>(new SpoofAction(a));
-      ResponseConfig responseConfig;
-      parseResponseConfig(vars, responseConfig);
+      auto ret = std::shared_ptr<DNSAction>(new SpoofAction(DNSName(a)));
+      auto sa = std::dynamic_pointer_cast<SpoofAction>(ret);
+      parseResponseConfig(vars, sa->d_responseConfig);
+      return ret;
+    });
+
+  g_lua.writeFunction("SpoofRawAction", [](const std::string& raw, boost::optional<responseParams_t> vars) {
+      auto ret = std::shared_ptr<DNSAction>(new SpoofAction(raw));
       auto sa = std::dynamic_pointer_cast<SpoofAction>(ret);
-      sa->d_responseConfig = responseConfig;
+      parseResponseConfig(vars, sa->d_responseConfig);
       return ret;
     });
 
@@ -1612,4 +1682,11 @@ void setupLuaActions()
   g_lua.writeFunction("KeyValueStoreLookupAction", [](std::shared_ptr<KeyValueStore>& kvs, std::shared_ptr<KeyValueLookupKey>& lookupKey, const std::string& destinationTag) {
       return std::shared_ptr<DNSAction>(new KeyValueStoreLookupAction(kvs, lookupKey, destinationTag));
     });
+
+  g_lua.writeFunction("SetNegativeAndSOAAction", [](bool nxd, const std::string& zone, uint32_t ttl, const std::string& mname, const std::string& rname, uint32_t serial, uint32_t refresh, uint32_t retry, uint32_t expire, uint32_t minimum, boost::optional<responseParams_t> vars) {
+      auto ret = std::shared_ptr<DNSAction>(new SetNegativeAndSOAAction(nxd, DNSName(zone), ttl, DNSName(mname), DNSName(rname), serial, refresh, retry, expire, minimum));
+      auto action = std::dynamic_pointer_cast<SetNegativeAndSOAAction>(ret);
+      parseResponseConfig(vars, action->d_responseConfig);
+      return ret;
+    });
 }
index fee1626cb89ff2171e2b404ae8c38c34a199ef67..eff46f3ab5aa7c835ab483a0492dbabea2305262 100644 (file)
@@ -96,6 +96,7 @@ void setupLuaBindingsDNSQuestion()
       }
 #endif /* HAVE_NET_SNMP */
     });
+
   g_lua.registerFunction<void(DNSQuestion::*)(std::string, std::string)>("setTag", [](DNSQuestion& dq, const std::string& strLabel, const std::string& strValue) {
       if(dq.qTag == nullptr) {
         dq.qTag = std::make_shared<QTag>();
@@ -169,6 +170,44 @@ void setupLuaBindingsDNSQuestion()
       }
       return true;
     });
+
+  g_lua.registerFunction<void(DNSResponse::*)(std::string, std::string)>("setTag", [](DNSResponse& dr, const std::string& strLabel, const std::string& strValue) {
+      if(dr.qTag == nullptr) {
+        dr.qTag = std::make_shared<QTag>();
+      }
+      dr.qTag->insert({strLabel, strValue});
+    });
+
+  g_lua.registerFunction<void(DNSResponse::*)(vector<pair<string, string>>)>("setTagArray", [](DNSResponse& dr, const vector<pair<string, string>>&tags) {
+      if (!dr.qTag) {
+        dr.qTag = std::make_shared<QTag>();
+      }
+
+      for (const auto& tag : tags) {
+        dr.qTag->insert({tag.first, tag.second});
+      }
+    });
+  g_lua.registerFunction<string(DNSResponse::*)(std::string)>("getTag", [](const DNSResponse& dr, const std::string& strLabel) {
+      if (!dr.qTag) {
+        return string();
+      }
+
+      std::string strValue;
+      const auto it = dr.qTag->find(strLabel);
+      if (it == dr.qTag->cend()) {
+        return string();
+      }
+      return it->second;
+    });
+  g_lua.registerFunction<QTag(DNSResponse::*)(void)>("getTagArray", [](const DNSResponse& dr) {
+      if (!dr.qTag) {
+        QTag empty;
+        return empty;
+      }
+
+      return *dr.qTag;
+    });
+
   g_lua.registerFunction<void(DNSResponse::*)(std::string)>("sendTrap", [](const DNSResponse& dr, boost::optional<std::string> reason) {
 #ifdef HAVE_NET_SNMP
       if (g_snmpAgent && g_snmpTrapsEnabled) {
@@ -220,4 +259,8 @@ void setupLuaBindingsDNSQuestion()
       dq.du->setHTTPResponse(statusCode, body, contentType ? *contentType : "");
     });
 #endif /* HAVE_DNS_OVER_HTTPS */
+
+  g_lua.registerFunction<bool(DNSQuestion::*)(bool nxd, const std::string& zone, uint32_t ttl, const std::string& mname, const std::string& rname, uint32_t serial, uint32_t refresh, uint32_t retry, uint32_t expire, uint32_t minimum)>("setNegativeAndAdditionalSOA", [](DNSQuestion& dq, bool nxd, const std::string& zone, uint32_t ttl, const std::string& mname, const std::string& rname, uint32_t serial, uint32_t refresh, uint32_t retry, uint32_t expire, uint32_t minimum) {
+      return setNegativeAndAdditionalSOA(dq, nxd, DNSName(zone), ttl, DNSName(mname), DNSName(rname), serial, refresh, retry, expire, minimum);
+    });
 }
index 2cfb6f61e800cd58800e220f69271dbb6cdca220..06cc42f9da88f566e31916730b729f93a40998cf 100644 (file)
@@ -147,7 +147,7 @@ void setupLuaBindings(bool client)
 
   g_lua.registerFunction<void(dnsheader::*)(bool)>("setAA", [](dnsheader& dh, bool v) {
       dh.aa=v;
-     });
+    });
 
   g_lua.registerFunction<bool(dnsheader::*)()>("getAA", [](dnsheader& dh) {
       return (bool)dh.aa;
index f10c6c3d37ba72890329202404e5bdcdb389494d..b76a05cfee59bb4f8b37788d1cca600416dfbc28 100644 (file)
@@ -31,6 +31,7 @@ void setupLuaVars()
       {"Nxdomain", (int)DNSAction::Action::Nxdomain},
       {"Refused", (int)DNSAction::Action::Refused},
       {"Spoof", (int)DNSAction::Action::Spoof},
+      {"SpoofRaw", (int)DNSAction::Action::SpoofRaw},
       {"Allow", (int)DNSAction::Action::Allow},
       {"HeaderModify", (int)DNSAction::Action::HeaderModify},
       {"Pool", (int)DNSAction::Action::Pool},
index 79557c6f96caa5098f796d8779aad5953e789488..a2979bac0b020f64cceed9f2c3e7434083a85009 100644 (file)
@@ -1858,6 +1858,10 @@ void setupLuaConfig(bool client, bool configCheck)
         }
       }
 
+      if (vars->count("sendCacheControlHeaders")) {
+        frontend->d_sendCacheControlHeaders = boost::get<bool>((*vars)["sendCacheControlHeaders"]);
+      }
+
       parseTLSConfig(frontend->d_tlsConfig, "addDOHLocal", vars);
     }
     g_dohlocals.push_back(frontend);
index aa33859c8001f47c956ab50137893c1f70af6f90..021e1248195a259ec67a585c48917eb7e6188a16 100644 (file)
@@ -26,6 +26,7 @@ struct ResponseConfig
   boost::optional<bool> setAA{boost::none};
   boost::optional<bool> setAD{boost::none};
   boost::optional<bool> setRA{boost::none};
+  uint32_t ttl{60};
 };
 void setResponseHeadersFromConfig(dnsheader& dh, const ResponseConfig& config);
 
@@ -64,17 +65,40 @@ class SpoofAction : public DNSAction
 public:
   SpoofAction(const vector<ComboAddress>& addrs): d_addrs(addrs)
   {
+    for (const auto& addr : d_addrs) {
+      if (addr.isIPv4()) {
+        d_types.insert(QType::A);
+      }
+      else if (addr.isIPv6()) {
+        d_types.insert(QType::AAAA);
+      }
+    }
+
+    if (!d_addrs.empty()) {
+      d_types.insert(QType::ANY);
+    }
   }
-  SpoofAction(const string& cname): d_cname(cname)
+
+  SpoofAction(const DNSName& cname): d_cname(cname)
   {
   }
+
+  SpoofAction(const std::string& raw): d_rawResponse(raw)
+  {
+  }
+
   DNSAction::Action operator()(DNSQuestion* dq, string* ruleresult) const override;
+
   string toString() const override
   {
     string ret = "spoof in ";
-    if(!d_cname.empty()) {
-      ret+=d_cname.toString()+ " ";
-    } else {
+    if (!d_cname.empty()) {
+      ret += d_cname.toString() + " ";
+    }
+    else if (!d_rawResponse.empty()) {
+      ret += "raw bytes ";
+    }
+    else {
       for(const auto& a : d_addrs)
         ret += a.toString()+" ";
     }
@@ -85,6 +109,8 @@ public:
   ResponseConfig d_responseConfig;
 private:
   std::vector<ComboAddress> d_addrs;
+  std::set<uint16_t> d_types;
+  std::string d_rawResponse;
   DNSName d_cname;
 };
 
index 421ce833a3a008489902ca2347f97715b4b6523d..a089a9f6c3f17359998627d0b63a3cd5df031c28 100644 (file)
@@ -21,6 +21,7 @@
  */
 #include "dnsdist.hh"
 #include "dnsdist-healthchecks.hh"
+#include "dnsdist-prometheus.hh"
 
 #include "sstuff.hh"
 #include "ext/json11/json11.hpp"
 bool g_apiReadWrite{false};
 WebserverConfig g_webserverConfig;
 std::string g_apiConfigDirectory;
+static const MetricDefinitionStorage s_metricDefinitions;
+
+const std::map<std::string, MetricDefinition> MetricDefinitionStorage::metrics{
+  { "responses",              MetricDefinition(PrometheusMetricType::counter, "Number of responses received from backends") },
+  { "servfail-responses",     MetricDefinition(PrometheusMetricType::counter, "Number of SERVFAIL answers received from backends") },
+  { "queries",                MetricDefinition(PrometheusMetricType::counter, "Number of received queries")},
+  { "frontend-nxdomain",      MetricDefinition(PrometheusMetricType::counter, "Number of NXDomain answers sent to clients")},
+  { "frontend-servfail",      MetricDefinition(PrometheusMetricType::counter, "Number of SERVFAIL answers sent to clients")},
+  { "frontend-noerror",       MetricDefinition(PrometheusMetricType::counter, "Number of NoError answers sent to clients")},
+  { "acl-drops",              MetricDefinition(PrometheusMetricType::counter, "Number of packets dropped because of the ACL")},
+  { "rule-drop",              MetricDefinition(PrometheusMetricType::counter, "Number of queries dropped because of a rule")},
+  { "rule-nxdomain",          MetricDefinition(PrometheusMetricType::counter, "Number of NXDomain answers returned because of a rule")},
+  { "rule-refused",           MetricDefinition(PrometheusMetricType::counter, "Number of Refused answers returned because of a rule")},
+  { "rule-servfail",          MetricDefinition(PrometheusMetricType::counter, "Number of SERVFAIL answers received because of a rule")},
+  { "self-answered",          MetricDefinition(PrometheusMetricType::counter, "Number of self-answered responses")},
+  { "downstream-timeouts",    MetricDefinition(PrometheusMetricType::counter, "Number of queries not answered in time by a backend")},
+  { "downstream-send-errors", MetricDefinition(PrometheusMetricType::counter, "Number of errors when sending a query to a backend")},
+  { "trunc-failures",         MetricDefinition(PrometheusMetricType::counter, "Number of errors encountered while truncating an answer")},
+  { "no-policy",              MetricDefinition(PrometheusMetricType::counter, "Number of queries dropped because no server was available")},
+  { "latency0-1",             MetricDefinition(PrometheusMetricType::counter, "Number of queries answered in less than 1ms")},
+  { "latency1-10",            MetricDefinition(PrometheusMetricType::counter, "Number of queries answered in 1-10 ms")},
+  { "latency10-50",           MetricDefinition(PrometheusMetricType::counter, "Number of queries answered in 10-50 ms")},
+  { "latency50-100",          MetricDefinition(PrometheusMetricType::counter, "Number of queries answered in 50-100 ms")},
+  { "latency100-1000",        MetricDefinition(PrometheusMetricType::counter, "Number of queries answered in 100-1000 ms")},
+  { "latency-slow",           MetricDefinition(PrometheusMetricType::counter, "Number of queries answered in more than 1 second")},
+  { "latency-avg100",         MetricDefinition(PrometheusMetricType::gauge,   "Average response latency in microseconds of the last 100 packets")},
+  { "latency-avg1000",        MetricDefinition(PrometheusMetricType::gauge,   "Average response latency in microseconds of the last 1000 packets")},
+  { "latency-avg10000",       MetricDefinition(PrometheusMetricType::gauge,   "Average response latency in microseconds of the last 10000 packets")},
+  { "latency-avg1000000",     MetricDefinition(PrometheusMetricType::gauge,   "Average response latency in microseconds of the last 1000000 packets")},
+  { "uptime",                 MetricDefinition(PrometheusMetricType::gauge,   "Uptime of the dnsdist process in seconds")},
+  { "real-memory-usage",      MetricDefinition(PrometheusMetricType::gauge,   "Current memory usage in bytes")},
+  { "noncompliant-queries",   MetricDefinition(PrometheusMetricType::counter, "Number of queries dropped as non-compliant")},
+  { "noncompliant-responses", MetricDefinition(PrometheusMetricType::counter, "Number of answers from a backend dropped as non-compliant")},
+  { "rdqueries",              MetricDefinition(PrometheusMetricType::counter, "Number of received queries with the recursion desired bit set")},
+  { "empty-queries",          MetricDefinition(PrometheusMetricType::counter, "Number of empty queries received from clients")},
+  { "cache-hits",             MetricDefinition(PrometheusMetricType::counter, "Number of times an answer was retrieved from cache")},
+  { "cache-misses",           MetricDefinition(PrometheusMetricType::counter, "Number of times an answer not found in the cache")},
+  { "cpu-iowait",             MetricDefinition(PrometheusMetricType::counter, "Time waiting for I/O to complete by the whole system, in units of USER_HZ")},
+  { "cpu-user-msec",          MetricDefinition(PrometheusMetricType::counter, "Milliseconds spent by dnsdist in the user state")},
+  { "cpu-steal",              MetricDefinition(PrometheusMetricType::counter, "Stolen time, which is the time spent by the whole system in other operating systems when running in a virtualized environment, in units of USER_HZ")},
+  { "cpu-sys-msec",           MetricDefinition(PrometheusMetricType::counter, "Milliseconds spent by dnsdist in the system state")},
+  { "fd-usage",               MetricDefinition(PrometheusMetricType::gauge,   "Number of currently used file descriptors")},
+  { "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") },
+  { "udp-in-errors",          MetricDefinition(PrometheusMetricType::counter, "From /proc/net/snmp InErrors") },
+  { "udp-noport-errors",      MetricDefinition(PrometheusMetricType::counter, "From /proc/net/snmp NoPorts") },
+  { "udp-recvbuf-errors",     MetricDefinition(PrometheusMetricType::counter, "From /proc/net/snmp RcvbufErrors") },
+  { "udp-sndbuf-errors",      MetricDefinition(PrometheusMetricType::counter, "From /proc/net/snmp SndbufErrors") },
+};
 
 static bool apiWriteConfigFile(const string& filebasename, const string& content)
 {
@@ -421,12 +472,12 @@ static void connectionThread(int sock, ComboAddress remote)
           }
 
           MetricDefinition metricDetails;
-          if (!g_metricDefinitions.getMetricDetails(metricName, metricDetails)) {
+          if (!s_metricDefinitions.getMetricDetails(metricName, metricDetails)) {
               vinfolog("Do not have metric details for %s", metricName);
               continue;
           }
 
-          std::string prometheusTypeName = g_metricDefinitions.getPrometheusStringMetricType(metricDetails.prometheusType);
+          std::string prometheusTypeName = s_metricDefinitions.getPrometheusStringMetricType(metricDetails.prometheusType);
 
           if (prometheusTypeName == "") {
               vinfolog("Unknown Prometheus type for %s", metricName);
@@ -469,6 +520,8 @@ static void connectionThread(int sock, ComboAddress remote)
         auto states = g_dstates.getLocal();
         const string statesbase = "dnsdist_server_";
 
+        output << "# HELP " << statesbase << "status "                 << "Whether this backend is up (1) or down (0)"                        << "\n";
+        output << "# TYPE " << statesbase << "status "                 << "gauge"                                                             << "\n";
         output << "# HELP " << statesbase << "queries "                << "Amount of queries relayed to server"                               << "\n";
         output << "# TYPE " << statesbase << "queries "                << "counter"                                                           << "\n";
         output << "# HELP " << statesbase << "responses "              << "Amount of responses received from this server"                     << "\n";
@@ -477,7 +530,7 @@ static void connectionThread(int sock, ComboAddress remote)
         output << "# TYPE " << statesbase << "drops "                  << "counter"                                                           << "\n";
         output << "# HELP " << statesbase << "latency "                << "Server's latency when answering questions in milliseconds"         << "\n";
         output << "# TYPE " << statesbase << "latency "                << "gauge"                                                             << "\n";
-        output << "# HELP " << statesbase << "senderrors "             << "Total number of OS send errors while relaying queries"              << "\n";
+        output << "# HELP " << statesbase << "senderrors "             << "Total number of OS send errors while relaying queries"             << "\n";
         output << "# TYPE " << statesbase << "senderrors "             << "counter"                                                           << "\n";
         output << "# HELP " << statesbase << "outstanding "            << "Current number of queries that are waiting for a backend response" << "\n";
         output << "# TYPE " << statesbase << "outstanding "            << "gauge"                                                             << "\n";
@@ -515,6 +568,7 @@ static void connectionThread(int sock, ComboAddress remote)
           const std::string label = boost::str(boost::format("{server=\"%1%\",address=\"%2%\"}")
             % serverName % state->remote.toStringWithPort());
 
+          output << statesbase << "status"                 << label << " " << (state->isUp() ? "1" : "0")       << "\n";
           output << statesbase << "queries"                << label << " " << state->queries.load()             << "\n";
           output << statesbase << "responses"              << label << " " << state->responses.load()           << "\n";
           output << statesbase << "drops"                  << label << " " << state->reuseds.load()             << "\n";
index 96af451cf7f52905ac9b89ef937312c923e6f37f..00cb2e3dfb89210b30e786410163f3ca9977d4e4 100644 (file)
@@ -37,6 +37,7 @@
 #include <editline/readline.h>
 #endif
 
+#include "dnsdist-systemd.hh"
 #ifdef HAVE_SYSTEMD
 #include <systemd/sd-daemon.h>
 #endif
@@ -81,7 +82,6 @@ using std::thread;
 bool g_verbose;
 
 struct DNSDistStats g_stats;
-MetricDefinitionStorage g_metricDefinitions;
 
 uint16_t g_maxOutstanding{std::numeric_limits<uint16_t>::max()};
 uint32_t g_staleCacheEntriesTTL{0};
@@ -1031,34 +1031,41 @@ NumberedServerVector getDownstreamCandidates(const pools_t& pools, const std::st
   return pool->getServers();
 }
 
-static void spoofResponseFromString(DNSQuestion& dq, const string& spoofContent)
+static void spoofResponseFromString(DNSQuestion& dq, const string& spoofContent, bool raw)
 {
   string result;
 
-  std::vector<std::string> addrs;
-  stringtok(addrs, spoofContent, " ,");
+  if (raw) {
+    SpoofAction sa(spoofContent);
+    sa(&dq, &result);
+  }
+  else {
+    std::vector<std::string> addrs;
+    stringtok(addrs, spoofContent, " ,");
 
-  if (addrs.size() == 1) {
-    try {
-      ComboAddress spoofAddr(spoofContent);
-      SpoofAction sa({spoofAddr});
-      sa(&dq, &result);
-    }
-    catch(const PDNSException &e) {
-      SpoofAction sa(spoofContent); // CNAME then
-      sa(&dq, &result);
-    }
-  } else {
-    std::vector<ComboAddress> cas;
-    for (const auto& addr : addrs) {
+    if (addrs.size() == 1) {
       try {
-        cas.push_back(ComboAddress(addr));
+        ComboAddress spoofAddr(spoofContent);
+        SpoofAction sa({spoofAddr});
+        sa(&dq, &result);
+      }
+      catch(const PDNSException &e) {
+        DNSName cname(spoofContent);
+        SpoofAction sa(cname); // CNAME then
+        sa(&dq, &result);
       }
-      catch (...) {
+    } else {
+      std::vector<ComboAddress> cas;
+      for (const auto& addr : addrs) {
+        try {
+          cas.push_back(ComboAddress(addr));
+        }
+        catch (...) {
+        }
       }
+      SpoofAction sa(cas);
+      sa(&dq, &result);
     }
-    SpoofAction sa(cas);
-    sa(&dq, &result);
   }
 }
 
@@ -1092,7 +1099,11 @@ bool processRulesResult(const DNSAction::Action& action, DNSQuestion& dq, std::s
     return true;
     break;
   case DNSAction::Action::Spoof:
-    spoofResponseFromString(dq, ruleresult);
+    spoofResponseFromString(dq, ruleresult, false);
+    return true;
+    break;
+  case DNSAction::Action::SpoofRaw:
+    spoofResponseFromString(dq, ruleresult, true);
     return true;
     break;
   case DNSAction::Action::Truncate:
@@ -1525,7 +1536,7 @@ ProcessQueryResult processQuery(DNSQuestion& dq, ClientState& cs, LocalHolders&
         }
       }
 
-      if (!handleEDNSClientSubnet(dq, &(dq.ednsAdded), &(dq.ecsAdded), g_preserveTrailingData)) {
+      if (!handleEDNSClientSubnet(dq, dq.ednsAdded, dq.ecsAdded, g_preserveTrailingData)) {
         vinfolog("Dropping query from %s because we couldn't insert the ECS value", dq.remote->toStringWithPort());
         return ProcessQueryResult::Drop;
       }
@@ -2579,8 +2590,8 @@ try
   }
 #endif
 
-  uid_t newgid=0;
-  gid_t newuid=0;
+  uid_t newgid=getegid();
+  gid_t newuid=geteuid();
 
   if(!g_cmdLine.gid.empty())
     newgid = strToGID(g_cmdLine.gid.c_str());
@@ -2588,8 +2599,22 @@ try
   if(!g_cmdLine.uid.empty())
     newuid = strToUID(g_cmdLine.uid.c_str());
 
-  dropGroupPrivs(newgid);
-  dropUserPrivs(newuid);
+  if (getegid() != newgid) {
+    if (running_in_service_mgr()) {
+      errlog("--gid/-g set on command-line, but dnsdist was started as a systemd service. Use the 'Group' setting in the systemd unit file to set the group to run as");
+      _exit(EXIT_FAILURE);
+    }
+    dropGroupPrivs(newgid);
+  }
+
+  if (geteuid() != newuid) {
+    if (running_in_service_mgr()) {
+      errlog("--uid/-u set on command-line, but dnsdist was started as a systemd service. Use the 'User' setting in the systemd unit file to set the user to run as");
+      _exit(EXIT_FAILURE);
+    }
+    dropUserPrivs(newuid);
+  }
+
   try {
     /* we might still have capabilities remaining,
        for example if we have been started as root
index 9c65edc8740b24c9abbb6118e483f9316d4df376..9443dc8ce943e6cea281feb07817312d4a97ecbe 100644 (file)
@@ -132,7 +132,7 @@ struct DNSResponse : DNSQuestion
 class DNSAction
 {
 public:
-  enum class Action { Drop, Nxdomain, Refused, Spoof, Allow, HeaderModify, Pool, Delay, Truncate, ServFail, None, NoOp, NoRecurse };
+  enum class Action { Drop, Nxdomain, Refused, Spoof, Allow, HeaderModify, Pool, Delay, Truncate, ServFail, None, NoOp, NoRecurse, SpoofRaw };
   static std::string typeToString(const Action& action)
   {
     switch(action) {
@@ -144,6 +144,8 @@ public:
       return "Send Refused";
     case Action::Spoof:
       return "Spoof an answer";
+    case Action::SpoofRaw:
+      return "Spoof an answer from raw bytes";
     case Action::Allow:
       return "Allow";
     case Action::HeaderModify:
@@ -290,14 +292,20 @@ struct DNSDistStats
     {"uptime", uptimeOfProcess},
     {"real-memory-usage", getRealMemoryUsage},
     {"special-memory-usage", getSpecialMemoryUsage},
+    {"udp-in-errors", boost::bind(udpErrorStats, "udp-in-errors")},
+    {"udp-noport-errors", boost::bind(udpErrorStats, "udp-noport-errors")},
+    {"udp-recvbuf-errors", boost::bind(udpErrorStats, "udp-recvbuf-errors")},
+    {"udp-sndbuf-errors", boost::bind(udpErrorStats, "udp-sndbuf-errors")},
     {"noncompliant-queries", &nonCompliantQueries},
     {"noncompliant-responses", &nonCompliantResponses},
     {"rdqueries", &rdQueries},
     {"empty-queries", &emptyQueries},
     {"cache-hits", &cacheHits},
     {"cache-misses", &cacheMisses},
-    {"cpu-user-msec", getCPUTimeUser},
+    {"cpu-iowait", getCPUIOWait},
+    {"cpu-steal", getCPUSteal},
     {"cpu-sys-msec", getCPUTimeSystem},
+    {"cpu-user-msec", getCPUTimeUser},
     {"fd-usage", getOpenFileDescriptors},
     {"dyn-blocked", &dynBlocked},
     {"dyn-block-nmg-size", [](const std::string&) { return g_dynblockNMG.getLocal()->size(); }},
@@ -308,98 +316,6 @@ struct DNSDistStats
   };
 };
 
-// Metric types for Prometheus
-enum class PrometheusMetricType: int {
-    counter = 1,
-    gauge = 2
-};
-
-// Keeps additional information about metrics
-struct MetricDefinition {
-  MetricDefinition(PrometheusMetricType _prometheusType, const std::string& _description): description(_description), prometheusType(_prometheusType) {
-  }
-  MetricDefinition() = default;
-
-  // Metric description
-  std::string description;
-  // Metric type for Prometheus
-  PrometheusMetricType prometheusType;
-};
-
-struct MetricDefinitionStorage {
-  // Return metric definition by name
-  bool getMetricDetails(std::string metricName, MetricDefinition& metric) {
-  auto metricDetailsIter = metrics.find(metricName);
-
-  if (metricDetailsIter == metrics.end()) {
-    return false;
-  }
-
-  metric = metricDetailsIter->second;
-    return true;
-  };
-
-  // Return string representation of Prometheus metric type
-  std::string getPrometheusStringMetricType(PrometheusMetricType metricType) {
-    switch (metricType) { 
-      case PrometheusMetricType::counter:
-        return "counter";
-        break;
-      case PrometheusMetricType::gauge:
-        return "gauge";
-        break;
-      default:
-        return "";
-        break;
-    }
-  };
-
-  std::map<std::string, MetricDefinition> metrics = {
-    { "responses",              MetricDefinition(PrometheusMetricType::counter, "Number of responses received from backends") },
-    { "servfail-responses",     MetricDefinition(PrometheusMetricType::counter, "Number of SERVFAIL answers received from backends") },
-    { "queries",                MetricDefinition(PrometheusMetricType::counter, "Number of received queries")},
-    { "frontend-nxdomain",      MetricDefinition(PrometheusMetricType::counter, "Number of NXDomain answers sent to clients")},
-    { "frontend-servfail",      MetricDefinition(PrometheusMetricType::counter, "Number of SERVFAIL answers sent to clients")},
-    { "frontend-noerror",       MetricDefinition(PrometheusMetricType::counter, "Number of NoError answers sent to clients")},
-    { "acl-drops",              MetricDefinition(PrometheusMetricType::counter, "Number of packets dropped because of the ACL")},
-    { "rule-drop",              MetricDefinition(PrometheusMetricType::counter, "Number of queries dropped because of a rule")},
-    { "rule-nxdomain",          MetricDefinition(PrometheusMetricType::counter, "Number of NXDomain answers returned because of a rule")},
-    { "rule-refused",           MetricDefinition(PrometheusMetricType::counter, "Number of Refused answers returned because of a rule")},
-    { "rule-servfail",          MetricDefinition(PrometheusMetricType::counter, "Number of SERVFAIL answers received because of a rule")},
-    { "self-answered",          MetricDefinition(PrometheusMetricType::counter, "Number of self-answered responses")},
-    { "downstream-timeouts",    MetricDefinition(PrometheusMetricType::counter, "Number of queries not answered in time by a backend")},
-    { "downstream-send-errors", MetricDefinition(PrometheusMetricType::counter, "Number of errors when sending a query to a backend")},
-    { "trunc-failures",         MetricDefinition(PrometheusMetricType::counter, "Number of errors encountered while truncating an answer")},
-    { "no-policy",              MetricDefinition(PrometheusMetricType::counter, "Number of queries dropped because no server was available")},
-    { "latency0-1",             MetricDefinition(PrometheusMetricType::counter, "Number of queries answered in less than 1ms")},
-    { "latency1-10",            MetricDefinition(PrometheusMetricType::counter, "Number of queries answered in 1-10 ms")},
-    { "latency10-50",           MetricDefinition(PrometheusMetricType::counter, "Number of queries answered in 10-50 ms")},
-    { "latency50-100",          MetricDefinition(PrometheusMetricType::counter, "Number of queries answered in 50-100 ms")},
-    { "latency100-1000",        MetricDefinition(PrometheusMetricType::counter, "Number of queries answered in 100-1000 ms")},
-    { "latency-slow",           MetricDefinition(PrometheusMetricType::counter, "Number of queries answered in more than 1 second")},
-    { "latency-avg100",         MetricDefinition(PrometheusMetricType::gauge,   "Average response latency in microseconds of the last 100 packets")},
-    { "latency-avg1000",        MetricDefinition(PrometheusMetricType::gauge,   "Average response latency in microseconds of the last 1000 packets")},
-    { "latency-avg10000",       MetricDefinition(PrometheusMetricType::gauge,   "Average response latency in microseconds of the last 10000 packets")},
-    { "latency-avg1000000",     MetricDefinition(PrometheusMetricType::gauge,   "Average response latency in microseconds of the last 1000000 packets")},
-    { "uptime",                 MetricDefinition(PrometheusMetricType::gauge,   "Uptime of the dnsdist process in seconds")},
-    { "real-memory-usage",      MetricDefinition(PrometheusMetricType::gauge,   "Current memory usage in bytes")},
-    { "noncompliant-queries",   MetricDefinition(PrometheusMetricType::counter, "Number of queries dropped as non-compliant")},
-    { "noncompliant-responses", MetricDefinition(PrometheusMetricType::counter, "Number of answers from a backend dropped as non-compliant")},
-    { "rdqueries",              MetricDefinition(PrometheusMetricType::counter, "Number of received queries with the recursion desired bit set")},
-    { "empty-queries",          MetricDefinition(PrometheusMetricType::counter, "Number of empty queries received from clients")},
-    { "cache-hits",             MetricDefinition(PrometheusMetricType::counter, "Number of times an answer was retrieved from cache")},
-    { "cache-misses",           MetricDefinition(PrometheusMetricType::counter, "Number of times an answer not found in the cache")},
-    { "cpu-user-msec",          MetricDefinition(PrometheusMetricType::counter, "Milliseconds spent by dnsdist in the user state")},
-    { "cpu-sys-msec",           MetricDefinition(PrometheusMetricType::counter, "Milliseconds spent by dnsdist in the system state")},
-    { "fd-usage",               MetricDefinition(PrometheusMetricType::gauge,   "Number of currently used file descriptors")},
-    { "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") },
-  };
-};
-
-extern MetricDefinitionStorage g_metricDefinitions;
 extern struct DNSDistStats g_stats;
 void doLatencyStats(double udiff);
 
index a4982f27bd79c7ed39974759074521c60cf0a7c1..78ae0624587d64688327a9cb027f730f87b05fb5 100644 (file)
@@ -139,11 +139,13 @@ dnsdist_SOURCES = \
        dnsdist-lua-inspection-ffi.cc dnsdist-lua-inspection-ffi.hh \
        dnsdist-lua-rules.cc \
        dnsdist-lua-vars.cc \
+       dnsdist-prometheus.hh \
        dnsdist-protobuf.cc dnsdist-protobuf.hh \
        dnsdist-rings.cc dnsdist-rings.hh \
        dnsdist-rules.hh \
        dnsdist-secpoll.cc dnsdist-secpoll.hh \
        dnsdist-snmp.cc dnsdist-snmp.hh \
+       dnsdist-systemd.cc dnsdist-systemd.hh \
        dnsdist-tcp.cc \
        dnsdist-web.cc \
        dnsdist-xpf.cc dnsdist-xpf.hh \
@@ -399,7 +401,7 @@ endif
 
 if HAVE_SYSTEMD
 dnsdist.service: dnsdist.service.in
-       $(AM_V_GEN)sed -e 's![@]bindir[@]!$(bindir)!' < $< > $@
+       $(AM_V_GEN)sed -e 's![@]bindir[@]!$(bindir)!' -e 's![@]service_user[@]!$(service_user)!' -e 's![@]service_group[@]!$(service_group)!' < $< > $@
 if !HAVE_SYSTEMD_LOCK_PERSONALITY
        $(AM_V_GEN)perl -ni -e 'print unless /^LockPersonality/' $@
 endif
index e0109dc2843772162d6e92a1ea583852460ffdc9..89bb865e19c3ad0126eb4d1a4addd1b7275fae98 100644 (file)
@@ -47,6 +47,7 @@ PDNS_WITH_LIBCAP
 AX_AVAILABLE_SYSTEMD
 AX_CHECK_SYSTEMD_FEATURES
 AM_CONDITIONAL([HAVE_SYSTEMD], [ test x"$systemd" = "xy" ])
+PDNS_WITH_SERVICE_USER([dnsdist])
 
 dnl the *_r functions are in posix so we can use them unconditionally, but the ext/yahttp code is
 dnl using the defines.
diff --git a/pdns/dnsdistdist/dnsdist-prometheus.hh b/pdns/dnsdistdist/dnsdist-prometheus.hh
new file mode 100644 (file)
index 0000000..a64b904
--- /dev/null
@@ -0,0 +1,72 @@
+/*
+ * 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
+
+// Metric types for Prometheus
+enum class PrometheusMetricType: int {
+    counter = 1,
+    gauge = 2
+};
+
+// Keeps additional information about metrics
+struct MetricDefinition {
+  MetricDefinition(PrometheusMetricType _prometheusType, const std::string& _description): description(_description), prometheusType(_prometheusType) {
+  }
+
+  MetricDefinition() = default;
+
+  // Metric description
+  std::string description;
+  // Metric type for Prometheus
+  PrometheusMetricType prometheusType;
+};
+
+struct MetricDefinitionStorage {
+  // Return metric definition by name
+  bool getMetricDetails(const std::string& metricName, MetricDefinition& metric) const {
+    const auto& metricDetailsIter = metrics.find(metricName);
+
+    if (metricDetailsIter == metrics.end()) {
+      return false;
+    }
+
+    metric = metricDetailsIter->second;
+    return true;
+  };
+
+  // Return string representation of Prometheus metric type
+  std::string getPrometheusStringMetricType(PrometheusMetricType metricType) const {
+    switch (metricType) {
+      case PrometheusMetricType::counter:
+        return "counter";
+        break;
+      case PrometheusMetricType::gauge:
+        return "gauge";
+        break;
+      default:
+        return "";
+        break;
+    }
+  };
+
+  static const std::map<std::string, MetricDefinition> metrics;
+};
diff --git a/pdns/dnsdistdist/dnsdist-systemd.cc b/pdns/dnsdistdist/dnsdist-systemd.cc
new file mode 100644 (file)
index 0000000..6f9f890
--- /dev/null
@@ -0,0 +1,35 @@
+/*
+ * This file is part of PowerDNS or dnsdist.
+ * Copyright -- PowerDNS.COM B.V. and its contributors
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ *
+ * In addition, for the avoidance of any doubt, permission is granted to
+ * link this program with OpenSSL and to (re)distribute the binaries
+ * produced as the result of such linking.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+#include "config.h"
+#include "dnsdist-systemd.hh"
+#include <cstdlib>
+
+bool running_in_service_mgr() {
+#ifdef HAVE_SYSTEMD
+  char *c;
+  c = getenv("NOTIFY_SOCKET"); // XXX Ideally we'd check for INVOCATION_ID (systemd.exec(5)), but that was introduced in systemd 232, and Debian Jessie has 215
+  if (c != nullptr) {
+    return true;
+  }
+#endif
+  return false;
+}
diff --git a/pdns/dnsdistdist/dnsdist-systemd.hh b/pdns/dnsdistdist/dnsdist-systemd.hh
new file mode 100644 (file)
index 0000000..046905c
--- /dev/null
@@ -0,0 +1,24 @@
+/*
+ * 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
+
+bool running_in_service_mgr();
index 85de6f951e60afefcc73c455152ff44c64dc79e3..c96c37c168ee21283dccb3f34af0dec2fcff33bf 100644 (file)
@@ -9,6 +9,8 @@ After=network-online.target
 ExecStartPre=@bindir@/dnsdist --check-config
 # Note: when editing the ExecStart command, keep --supervised and --disable-syslog
 ExecStart=@bindir@/dnsdist --supervised --disable-syslog
+User=@service_user@
+Group=@service_group@
 Type=notify
 Restart=on-failure
 RestartSec=2
@@ -20,7 +22,8 @@ LimitNOFILE=16384
 TasksMax=8192
 
 # Sandboxing
-CapabilityBoundingSet=CAP_NET_BIND_SERVICE CAP_SETGID CAP_SETUID
+CapabilityBoundingSet=CAP_NET_BIND_SERVICE
+AmbientCapabilities=CAP_NET_BIND_SERVICE
 LockPersonality=true
 NoNewPrivileges=true
 PrivateDevices=true
index a478c8eacee057b4d3073f665360454048d705ea..26c1761cc61fbfdcecbd74a3cd7218b49bec436c 100644 (file)
@@ -52,7 +52,7 @@ The following example sets the CD flag to true and change the QName to "powerdns
     function myHealthCheck(qname, qtype, qclass, dh)
       dh:setCD(true)
 
-      return newDNSName("powerdns.com."), dnsdist.AAAA, qclass
+      return newDNSName("powerdns.com."), DNSQType.AAAA, qclass
     end
 
     newServer({address="2620:0:0ccd::2", checkFunction=myHealthCheck})
index e22d6e4e5caed0092623b82d97976501cc3cb0ed..ff0ad6fdc3885a93300e2826bdef0e5519d57629 100644 (file)
@@ -42,7 +42,7 @@ For example, instead of having something like:
     addDynBlocks(exceedQRate(30, 10), "Exceeded query rate", 60)
     addDynBlocks(exceedNXDOMAINs(20, 10), "Exceeded NXD rate", 60)
     addDynBlocks(exceedServFails(20, 10), "Exceeded ServFail rate", 60)
-    addDynBlocks(exceedQTypeRate(dnsdist.ANY, 5, 10), "Exceeded ANY rate", 60)
+    addDynBlocks(exceedQTypeRate(DNSQType.ANY, 5, 10), "Exceeded ANY rate", 60)
     addDynBlocks(exceedRespByterate(1000000, 10), "Exceeded resp BW rate", 60)
   end
 
@@ -52,9 +52,9 @@ The new syntax would be:
 
   local dbr = dynBlockRulesGroup()
   dbr:setQueryRate(30, 10, "Exceeded query rate", 60)
-  dbr:setRCodeRate(dnsdist.NXDOMAIN, 20, 10, "Exceeded NXD rate", 60)
-  dbr:setRCodeRate(dnsdist.SERVFAIL, 20, 10, "Exceeded ServFail rate", 60)
-  dbr:setQTypeRate(dnsdist.ANY, 5, 10, "Exceeded ANY rate", 60)
+  dbr:setRCodeRate(DNSRCode.NXDOMAIN, 20, 10, "Exceeded NXD rate", 60)
+  dbr:setRCodeRate(DNSRCode.SERVFAIL, 20, 10, "Exceeded ServFail rate", 60)
+  dbr:setQTypeRate(DNSQType.ANY, 5, 10, "Exceeded ANY rate", 60)
   dbr:setResponseByteRate(10000, 10, "Exceeded resp BW rate", 60)
 
   function maintenance()
index 9aa47383f8fecc464b9e16ffa6098a35cba6e4cd..a0cdeb9baba262c84b1c2b72c4d8df0cc76f4ec0 100644 (file)
@@ -107,6 +107,9 @@ Listen Sockets
 
   .. versionadded:: 1.4.0
 
+  .. versionchanged:: 1.5.0
+    ``sendCacheControlHeaders`` option added.
+
   Listen on the specified address and TCP port for incoming DNS over HTTPS connections, presenting the specified X.509 certificate.
   If no certificate (or key) files are specified, listen for incoming DNS over HTTP connections instead.
 
@@ -137,6 +140,7 @@ Listen Sockets
   * ``numberOfStoredSessions``: int - The maximum number of sessions kept in memory at the same time. Default is 20480. Setting this value to 0 disables stored session entirely.
   * ``preferServerCiphers``: bool - Whether to prefer the order of ciphers set by the server instead of the one set by the client. Default is true, meaning that the order of the server is used.
   * ``keyLogFile``: str - Write the TLS keys in the specified file so that an external program can decrypt TLS exchanges, in the format described in https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSS/Key_Log_Format.
+  * ``sendCacheControlHeaders``: bool - Whether to parse the response to find the lowest TTL and set a HTTP Cache-Control header accordingly. Default is true.
 
 .. function:: addTLSLocal(address, certFile(s), keyFile(s) [, options])
 
@@ -308,7 +312,7 @@ Webserver configuration
   Options:
 
   * ``password=newPassword``: string - Changes the API password
-  * ``apikey=newKey``: string - Changes the API Key (set to an empty string do disable it)
+  * ``apiKey=newKey``: string - Changes the API Key (set to an empty string do disable it)
   * ``custom_headers={[str]=str,...}``: map of string - Allows setting custom headers and removing the defaults.
                  
 Access Control Lists
index 1ead1ec82e0a3235239443ece6da9dd2c9f50c9a..aba032b706af5eafc63d83ad647b6cdb1f7a4ba4 100644 (file)
@@ -102,6 +102,9 @@ These constants represent the section in the DNS Packet.
 DNSAction
 ---------
 
+.. versionchanged:: 1.5.0
+  ``DNSAction.SpoofRaw`` has been added.
+
 These constants represent an Action that can be returned from :func:`LuaAction` functions.
 
  * ``DNSAction.Allow``: let the query pass, skipping other rules
@@ -115,6 +118,7 @@ These constants represent an Action that can be returned from :func:`LuaAction`
  * ``DNSAction.Refused``: return a response with a Refused rcode
  * ``DNSAction.ServFail``: return a response with a ServFail rcode
  * ``DNSAction.Spoof``: spoof the response using the supplied IPv4 (A), IPv6 (AAAA) or string (CNAME) value
+ * ``DNSAction.SpoofRaw``: spoof the response using the supplied raw value as record data
  * ``DNSAction.Truncate``: truncate the response
  * ``DNSAction.NoRecurse``: set rd=0 on the query
 
index 53e7eb053c5041659088961b029a4069df544f32..da2672f7534ca04c8a94ba5054a1b96f54e33830 100644 (file)
@@ -187,6 +187,23 @@ This state can be modified from the various hooks.
     :param string body: The body of the HTTP response, or a URL if the status code is a redirect (3xx)
     :param string contentType: The HTTP Content-Type header to return for a 200 response, ignored otherwise. Default is ''application/dns-message''.
 
+  .. method:: DNSQuestion:setNegativeAndAdditionalSOA(nxd, zone, ttl, mname, rname, serial, refresh, retry, expire, minimum)
+
+    .. versionadded:: 1.5.0
+
+    Turn a question into a response, either a NXDOMAIN or a NODATA one based on ''nxd'', setting the QR bit to 1 and adding a SOA record in the additional section.
+
+    :param bool nxd: Whether the answer is a NXDOMAIN (true) or a NODATA (false)
+    :param string zone: The owner name for the SOA record
+    :param int ttl: The TTL of the SOA record
+    :param string mname: The mname of the SOA record
+    :param string rname: The rname of the SOA record
+    :param int serial: The value of the serial field in the SOA record
+    :param int refresh: The value of the refresh field in the SOA record
+    :param int retry: The value of the retry field in the SOA record
+    :param int expire: The value of the expire field in the SOA record
+    :param int minimum: The value of the minimum field in the SOA record
+
   .. method:: DNSQuestion:setTag(key, value)
 
     .. versionadded:: 1.2.0
index 1d698b69d5fc59466b646ebcc9fd8d1c0abc5658..9ff60543c8993cabce57cacca0d018e660abec18 100644 (file)
@@ -174,7 +174,7 @@ Rule Generators
   ::
 
     function luarule(dq)
-      if(dq.qtype==dnsdist.NAPTR)
+      if(dq.qtype==DNSQType.NAPTR)
       then
         return DNSAction.Pool, "abuse" -- send to abuse pool
       else
@@ -712,7 +712,7 @@ These ``DNSRule``\ s be one of the following items:
 
   Matches queries with the specified ``qtype``
   ``qtype`` may be specified as an integer or as one of the built-in QTypes.
-  For instance ``dnsdist.A``, ``dnsdist.TXT`` and ``dnsdist.ANY``.
+  For instance ``DNSQType.A``, ``DNSQType.TXT`` and ``DNSQType.ANY``.
 
   :param int qtype: The QType to match on
 
@@ -1187,6 +1187,30 @@ The following actions exist.
   :param string v4: The IPv4 netmask, for example "192.0.2.1/32"
   :param string v6: The IPv6 netmask, if any
 
+.. function:: SetNegativeAndSOAAction(nxd, zone, ttl, mname, rname, serial, refresh, retry, expire, minimum [, options])
+
+  .. versionadded:: 1.5.0
+
+  Turn a question into a response, either a NXDOMAIN or a NODATA one based on ''nxd'', setting the QR bit to 1 and adding a SOA record in the additional section.
+
+  :param bool nxd: Whether the answer is a NXDOMAIN (true) or a NODATA (false)
+  :param string zone: The owner name for the SOA record
+  :param int ttl: The TTL of the SOA record
+  :param string mname: The mname of the SOA record
+  :param string rname: The rname of the SOA record
+  :param int serial: The value of the serial field in the SOA record
+  :param int refresh: The value of the refresh field in the SOA record
+  :param int retry: The value of the retry field in the SOA record
+  :param int expire: The value of the expire field in the SOA record
+  :param int minimum: The value of the minimum field in the SOA record
+  :param table options: A table with key: value pairs with options
+
+  Options:
+
+  * ``aa``: bool - Set the AA bit to this value (true means the bit is set, false means it's cleared). Default is to clear it.
+  * ``ad``: bool - Set the AD bit to this value (true means the bit is set, false means it's cleared). Default is to clear it.
+  * ``ra``: bool - Set the RA bit to this value (true means the bit is set, false means it's cleared). Default is to copy the value of the RD bit from the incoming query.
+
 .. function:: SkipCacheAction()
 
   Don't lookup the cache for this query, don't store the answer.
@@ -1223,6 +1247,7 @@ The following actions exist.
   * ``aa``: bool - Set the AA bit to this value (true means the bit is set, false means it's cleared). Default is to clear it.
   * ``ad``: bool - Set the AD bit to this value (true means the bit is set, false means it's cleared). Default is to clear it.
   * ``ra``: bool - Set the RA bit to this value (true means the bit is set, false means it's cleared). Default is to copy the value of the RD bit from the incoming query.
+  * ``ttl``: int - The TTL of the record.
 
 .. function:: SpoofCNAMEAction(cname [, options])
 
@@ -1239,6 +1264,24 @@ The following actions exist.
   * ``aa``: bool - Set the AA bit to this value (true means the bit is set, false means it's cleared). Default is to clear it.
   * ``ad``: bool - Set the AD bit to this value (true means the bit is set, false means it's cleared). Default is to clear it.
   * ``ra``: bool - Set the RA bit to this value (true means the bit is set, false means it's cleared). Default is to copy the value of the RD bit from the incoming query.
+  * ``ttl``: int - The TTL of the record.
+
+.. function:: SpoofRawAction(rawAnswer [, options])
+
+  .. versionadded:: 1.5.0
+
+  Forge a response with the specified raw bytes as record data.
+  For example, for a TXT record of "aaa" "bbbb": SpoofRawAction("\003aaa\004bbbb")
+
+  :param string rawAnswer: The raw record data
+  :param table options: A table with key: value pairs with options.
+
+  Options:
+
+  * ``aa``: bool - Set the AA bit to this value (true means the bit is set, false means it's cleared). Default is to clear it.
+  * ``ad``: bool - Set the AD bit to this value (true means the bit is set, false means it's cleared). Default is to clear it.
+  * ``ra``: bool - Set the RA bit to this value (true means the bit is set, false means it's cleared). Default is to copy the value of the RD bit from the incoming query.
+  * ``ttl``: int - The TTL of the record.
 
 .. function:: TagAction(name, value)
 
index ee806bb425c2390d558452ecf04011d2dcfbe9a4..c6456ee5c83dc2d65500248d6d2034c4bc4333c7 100644 (file)
@@ -34,6 +34,18 @@ cache-misses
 ------------
 Number of times an answer was not found in the :doc:`packet cache <guides/cache>`. Only counted if a packet cache was setup for the selected pool.
 
+cpu-iowait
+----------
+.. versionadded:: 1.5.0
+
+Time spent waiting for I/O to complete by the whole system, in units of USER_HZ.
+
+cpu-steal
+---------
+.. versionadded:: 1.5.0
+
+Stolen time, which is the time spent by the whole system in other operating systems when running in a virtualized environment, in units of USER_HZ.
+
 cpu-sys-msec
 ------------
 Milliseconds spent by :program:`dnsdist` in the "system" state.
@@ -201,7 +213,30 @@ trunc-failures
 --------------
 Number of errors encountered while truncating an answer.
 
+udp-in-errors
+-------------
+.. versionadded:: 1.5.0
+
+From /proc/net/snmp InErrors.
+
+udp-noport-errors
+-----------------
+.. versionadded:: 1.5.0
+
+From /proc/net/snmp NoPorts.
+
+udp-recvbuf-errors
+------------------
+.. versionadded:: 1.5.0
+
+From /proc/net/snmp RcvbufErrors.
+
+udp-sndbuf-errors
+-----------------
+.. versionadded:: 1.5.0
+
+From /proc/net/snmp SndbufErrors.
+
 uptime
 ------
 Uptime of the dnsdist process, in seconds.
-
index 9b29e650b810bc5179e35964bbfa475ea1cdc483..6f5d1b8fe6eaea9959326aed125cfe1659914475 100644 (file)
@@ -307,6 +307,16 @@ static void handleResponse(DOHFrontend& df, st_h2o_req_t* req, uint16_t statusCo
       }
     }
 
+    if (df.d_sendCacheControlHeaders && !response.empty()) {
+      uint32_t minTTL = getDNSPacketMinTTL(response.data(), response.size());
+      if (minTTL != std::numeric_limits<uint32_t>::max()) {
+        std::string cacheControlValue = "max-age=" + std::to_string(minTTL);
+        /* we need to duplicate the header content because h2o keeps a pointer and we will be deleted before the response has been sent */
+        h2o_iovec_t ccv = h2o_strdup(&req->pool, cacheControlValue.c_str(), cacheControlValue.size());
+        h2o_add_header(&req->pool, &req->res.headers, H2O_TOKEN_CACHE_CONTROL, nullptr, ccv.base, ccv.len);
+      }
+    }
+
     req->res.content_length = response.size();
     h2o_send_inline(req, response.c_str(), response.size());
   }
index 5da395c37c4064f9f0aaba8a47482ae86f22d104..6aa91bf0381769325920c0e4d90e4c453de801b0 100644 (file)
@@ -744,6 +744,7 @@ static void libssl_key_log_file_callback(const SSL* ssl, const char* line)
   }
 
   fprintf(fp, "%s\n", line);
+  fflush(fp);
 }
 #endif /* HAVE_SSL_CTX_SET_KEYLOG_CALLBACK */
 
diff --git a/pdns/dnsdistdist/m4/pdns_with_service_user.m4 b/pdns/dnsdistdist/m4/pdns_with_service_user.m4
new file mode 120000 (symlink)
index 0000000..bc72a6e
--- /dev/null
@@ -0,0 +1 @@
+../../../m4/pdns_with_service_user.m4
\ No newline at end of file
index 59017d8f81cb268bf18bd5b483519d4688fa9ea3..71ca39f516c0e5682eddf0a0908130601c80c9d6 100644 (file)
@@ -42,6 +42,7 @@ public:
   OpenSSLTLSTicketKeysRing d_ticketKeys;
   std::map<int, std::string> d_ocspResponses;
   std::unique_ptr<SSL_CTX, void(*)(SSL_CTX*)> d_tlsCtx{nullptr, SSL_CTX_free};
+  std::unique_ptr<FILE, int(*)(FILE*)> d_keyLogFile{nullptr, fclose};
 };
 
 class OpenSSLTLSConnection: public TLSConnection
@@ -303,6 +304,10 @@ public:
 
     libssl_set_error_counters_callback(d_feContext->d_tlsCtx, &fe.d_tlsCounters);
 
+    if (!fe.d_tlsConfig.d_keyLogFile.empty()) {
+      d_feContext->d_keyLogFile = libssl_set_key_log_file(d_feContext->d_tlsCtx, fe.d_tlsConfig.d_keyLogFile);
+    }
+
     try {
       if (fe.d_tlsConfig.d_ticketKeyFile.empty()) {
         handleTicketsKeyRotation(time(nullptr));
index fe64966fd0cacf8f67a83dbfc3f680d112df850c..826f666845bf3efcbdac60b737757686ce268b0c 100644 (file)
@@ -83,7 +83,7 @@ vector<string> segmentDNSText(const string& input )
 };
 
 
-DNSName::string_t segmentDNSNameRaw(const char* realinput)
+DNSName::string_t segmentDNSNameRaw(const char* realinput, size_t inputlen)
 {
 %%{
         machine dnsnameraw;
@@ -100,7 +100,6 @@ DNSName::string_t segmentDNSNameRaw(const char* realinput)
           return ret;
         }
 
-        unsigned int inputlen=strlen(realinput);
         ret.reserve(inputlen+1);
 
         const char *p = realinput, *pe = realinput + inputlen;
index b2936ec757ea68834e33a287c5a5cb3387b70276..c262d282023b6362f19bc86f8bcfe517b38ee1b7 100644 (file)
@@ -41,7 +41,7 @@ std::ostream & operator<<(std::ostream &os, const DNSName& d)
   return os <<d.toLogString();
 }
 
-DNSName::DNSName(const char* p)
+DNSName::DNSName(const char* p, size_t length)
 {
   if(p[0]==0 || (p[0]=='.' && p[1]==0)) {
     d_storage.assign(1, (char)0);
@@ -49,7 +49,7 @@ DNSName::DNSName(const char* p)
     if(!strchr(p, '\\')) {
       unsigned char lenpos=0;
       unsigned char labellen=0;
-      size_t plen=strlen(p);
+      size_t plen=length;
       const char* const pbegin=p, *pend=p+plen;
       d_storage.reserve(plen+1);
       for(auto iter = pbegin; iter != pend; ) {
@@ -76,7 +76,7 @@ DNSName::DNSName(const char* p)
       d_storage.append(1, (char)0);
     }
     else {
-      d_storage=segmentDNSNameRaw(p); 
+      d_storage=segmentDNSNameRaw(p, length);
       if(d_storage.size() > 255) {
         throw std::range_error("name too long");
       }
index da6c4de46471d27f02af5e0a2dbd6506a1d981b6..1f8ddf49a107f066060d4afa005b045c11496f25 100644 (file)
@@ -20,6 +20,7 @@
  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
  */
 #pragma once
+#include <cstring>
 #include <string>
 #include <vector>
 #include <set>
@@ -62,9 +63,10 @@ class DNSName
 {
 public:
   DNSName()  {}          //!< Constructs an *empty* DNSName, NOT the root!
-  explicit DNSName(const char* p);      //!< Constructs from a human formatted, escaped presentation
-  explicit DNSName(const std::string& str) : DNSName(str.c_str()) {}; //!< Constructs from a human formatted, escaped presentation
-  DNSName(const char* p, int len, int offset, bool uncompress, uint16_t* qtype=0, uint16_t* qclass=0, unsigned int* consumed=0, uint16_t minOffset=0); //!< Construct from a DNS Packet, taking the first question if offset=12
+  explicit DNSName(const char* p): DNSName(p, std::strlen(p)) {} //!< Constructs from a human formatted, escaped presentation
+  explicit DNSName(const char* p, size_t len);      //!< Constructs from a human formatted, escaped presentation
+  explicit DNSName(const std::string& str) : DNSName(str.c_str(), str.length()) {}; //!< Constructs from a human formatted, escaped presentation
+  DNSName(const char* p, int len, int offset, bool uncompress, uint16_t* qtype=nullptr, uint16_t* qclass=nullptr, unsigned int* consumed=nullptr, uint16_t minOffset=0); //!< Construct from a DNS Packet, taking the first question if offset=12. If supplied, consumed is set to the number of bytes consumed from the packet, which will not be equal to the wire length of the resulting name in case of compression.
   
   bool isPartOf(const DNSName& rhs) const;   //!< Are we part of the rhs name?
   inline bool operator==(const DNSName& rhs) const; //!< DNS-native comparison (case insensitive) - empty compares to empty
@@ -468,7 +470,7 @@ namespace std {
     };
 }
 
-DNSName::string_t segmentDNSNameRaw(const char* input); // from ragel
+DNSName::string_t segmentDNSNameRaw(const char* input, size_t inputlen); // from ragel
 bool DNSName::operator==(const DNSName& rhs) const
 {
   if(rhs.empty() != empty() || rhs.d_storage.size() != d_storage.size())
index 0fd335edba431d4ddd08b8607655e0b267a5ddc3..f4b5e816f42421a56f75e8ecb83e78220f7393a9 100644 (file)
@@ -575,122 +575,6 @@ string simpleCompress(const string& elabel, const string& root)
   return ret;
 }
 
-
-/** Simple DNSPacketMangler. Ritual is: get a pointer into the packet and moveOffset() to beyond your needs
- *  If you survive that, feel free to read from the pointer */
-class DNSPacketMangler
-{
-public:
-  explicit DNSPacketMangler(std::string& packet)
-    : d_packet((char*) packet.c_str()), d_length(packet.length()), d_notyouroffset(12), d_offset(d_notyouroffset)
-  {}
-  DNSPacketMangler(char* packet, size_t length)
-    : d_packet(packet), d_length(length), d_notyouroffset(12), d_offset(d_notyouroffset)
-  {}
-  
-  /*! Advances past a wire-format domain name
-   * The name is not checked for adherence to length restrictions.
-   * Compression pointers are not followed.
-   */
-  void skipDomainName()
-  {
-    uint8_t len; 
-    while((len=get8BitInt())) { 
-      if(len >= 0xc0) { // extended label
-        get8BitInt();
-        return;
-      }
-      skipBytes(len);
-    }
-  }
-
-  void skipBytes(uint16_t bytes)
-  {
-    moveOffset(bytes);
-  }
-  void rewindBytes(uint16_t by)
-  {
-    rewindOffset(by);
-  }
-  uint32_t get32BitInt()
-  {
-    const char* p = d_packet + d_offset;
-    moveOffset(4);
-    uint32_t ret;
-    memcpy(&ret, (void*)p, sizeof(ret));
-    return ntohl(ret);
-  }
-  uint16_t get16BitInt()
-  {
-    const char* p = d_packet + d_offset;
-    moveOffset(2);
-    uint16_t ret;
-    memcpy(&ret, (void*)p, sizeof(ret));
-    return ntohs(ret);
-  }
-  
-  uint8_t get8BitInt()
-  {
-    const char* p = d_packet + d_offset;
-    moveOffset(1);
-    return *p;
-  }
-  
-  void skipRData()
-  {
-    int toskip = get16BitInt();
-    moveOffset(toskip);
-  }
-
-  void decreaseAndSkip32BitInt(uint32_t decrease)
-  {
-    const char *p = d_packet + d_offset;
-    moveOffset(4);
-
-    uint32_t tmp;
-    memcpy(&tmp, (void*) p, sizeof(tmp));
-    tmp = ntohl(tmp);
-    tmp-=decrease;
-    tmp = htonl(tmp);
-    memcpy(d_packet + d_offset-4, (const char*)&tmp, sizeof(tmp));
-  }
-  void setAndSkip32BitInt(uint32_t value)
-  {
-    moveOffset(4);
-
-    value = htonl(value);
-    memcpy(d_packet + d_offset-4, (const char*)&value, sizeof(value));
-  }
-  uint32_t getOffset() const
-  {
-    return d_offset;
-  }
-private:
-  void moveOffset(uint16_t by)
-  {
-    d_notyouroffset += by;
-    if(d_notyouroffset > d_length)
-      throw std::out_of_range("dns packet out of range: "+std::to_string(d_notyouroffset) +" > " 
-      + std::to_string(d_length) );
-  }
-  void rewindOffset(uint16_t by)
-  {
-    if(d_notyouroffset < by)
-      throw std::out_of_range("Rewinding dns packet out of range: "+std::to_string(d_notyouroffset) +" < "
-                              + std::to_string(by));
-    d_notyouroffset -= by;
-    if(d_notyouroffset < 12)
-      throw std::out_of_range("Rewinding dns packet out of range: "+std::to_string(d_notyouroffset) +" < "
-                              + std::to_string(12));
-  }
-  char* d_packet;
-  size_t d_length;
-  
-  uint32_t d_notyouroffset;  // only 'moveOffset' can touch this
-  const uint32_t&  d_offset; // look.. but don't touch
-  
-};
-
 // method of operation: silently fail if it doesn't work - we're only trying to be nice, don't fall over on it
 void editDNSPacketTTL(char* packet, size_t length, std::function<uint32_t(uint8_t, uint16_t, uint16_t, uint32_t)> visitor)
 {
index 33228bdac2344dd4a3f9b509449c3b7002c8ab15..3bb22f3b7ef631f84aaa54a722eee2228f3740b0 100644 (file)
@@ -408,4 +408,123 @@ std::shared_ptr<T> getRR(const DNSRecord& dr)
   return std::dynamic_pointer_cast<T>(dr.d_content);
 }
 
+/** Simple DNSPacketMangler. Ritual is: get a pointer into the packet and moveOffset() to beyond your needs
+ *  If you survive that, feel free to read from the pointer */
+class DNSPacketMangler
+{
+public:
+  explicit DNSPacketMangler(std::string& packet)
+    : d_packet((char*) packet.c_str()), d_length(packet.length()), d_notyouroffset(12), d_offset(d_notyouroffset)
+  {}
+  DNSPacketMangler(char* packet, size_t length)
+    : d_packet(packet), d_length(length), d_notyouroffset(12), d_offset(d_notyouroffset)
+  {}
+
+  /*! Advances past a wire-format domain name
+   * The name is not checked for adherence to length restrictions.
+   * Compression pointers are not followed.
+   */
+  void skipDomainName()
+  {
+    uint8_t len;
+    while((len=get8BitInt())) {
+      if(len >= 0xc0) { // extended label
+        get8BitInt();
+        return;
+      }
+      skipBytes(len);
+    }
+  }
+
+  void skipBytes(uint16_t bytes)
+  {
+    moveOffset(bytes);
+  }
+  void rewindBytes(uint16_t by)
+  {
+    rewindOffset(by);
+  }
+  uint32_t get32BitInt()
+  {
+    const char* p = d_packet + d_offset;
+    moveOffset(4);
+    uint32_t ret;
+    memcpy(&ret, (void*)p, sizeof(ret));
+    return ntohl(ret);
+  }
+  uint16_t get16BitInt()
+  {
+    const char* p = d_packet + d_offset;
+    moveOffset(2);
+    uint16_t ret;
+    memcpy(&ret, (void*)p, sizeof(ret));
+    return ntohs(ret);
+  }
+
+  uint8_t get8BitInt()
+  {
+    const char* p = d_packet + d_offset;
+    moveOffset(1);
+    return *p;
+  }
+
+  void skipRData()
+  {
+    int toskip = get16BitInt();
+    moveOffset(toskip);
+  }
+
+  void decreaseAndSkip32BitInt(uint32_t decrease)
+  {
+    const char *p = d_packet + d_offset;
+    moveOffset(4);
+
+    uint32_t tmp;
+    memcpy(&tmp, (void*) p, sizeof(tmp));
+    tmp = ntohl(tmp);
+    tmp-=decrease;
+    tmp = htonl(tmp);
+    memcpy(d_packet + d_offset-4, (const char*)&tmp, sizeof(tmp));
+  }
+
+  void setAndSkip32BitInt(uint32_t value)
+  {
+    moveOffset(4);
+
+    value = htonl(value);
+    memcpy(d_packet + d_offset-4, (const char*)&value, sizeof(value));
+  }
+
+  uint32_t getOffset() const
+  {
+    return d_offset;
+  }
+
+private:
+  void moveOffset(uint16_t by)
+  {
+    d_notyouroffset += by;
+    if(d_notyouroffset > d_length)
+      throw std::out_of_range("dns packet out of range: "+std::to_string(d_notyouroffset) +" > "
+      + std::to_string(d_length) );
+  }
+
+  void rewindOffset(uint16_t by)
+  {
+    if(d_notyouroffset < by)
+      throw std::out_of_range("Rewinding dns packet out of range: "+std::to_string(d_notyouroffset) +" < "
+                              + std::to_string(by));
+    d_notyouroffset -= by;
+    if(d_notyouroffset < 12)
+      throw std::out_of_range("Rewinding dns packet out of range: "+std::to_string(d_notyouroffset) +" < "
+                              + std::to_string(12));
+  }
+
+  char* d_packet;
+  size_t d_length;
+
+  uint32_t d_notyouroffset;  // only 'moveOffset' can touch this
+  const uint32_t&  d_offset; // look.. but don't touch
+};
+
 #endif
index 16048f16e7dca16fc52031338198f1f9b793afa7..fbfcd4739ad35a8f0f2bfaf9887b9c581e04a48d 100644 (file)
@@ -173,5 +173,4 @@ private:
 typedef vector<pair<string::size_type, string::size_type> > labelparts_t;
 // bool labeltokUnescape(labelparts_t& parts, const DNSName& label);
 std::vector<string> segmentDNSText(const string& text); // from dnslabeltext.rl
-std::deque<string> segmentDNSName(const string& input ); // from dnslabeltext.rl
 #endif
index ba0499f7e048ef08b12b47ef33a1db04481c31d2..dbcfbb31ab55a60bc8f92dada7deea11f471dc34 100644 (file)
@@ -76,6 +76,7 @@ struct DOHFrontend
 
   HTTPVersionStats d_http1Stats;
   HTTPVersionStats d_http2Stats;
+  bool d_sendCacheControlHeaders{true};
 
   time_t getTicketsKeyRotationDelay() const
   {
index 6b4ec1098ff2c8ac7d0607992abde92616177770..d20755be732723363bcbd1dfe4f61cabe08f03ef 100644 (file)
@@ -105,6 +105,29 @@ int getEDNSOptions(const char* optRR, const size_t len, EDNSOptionViewMap& optio
   return 0;
 }
 
+bool getEDNSOptionsFromContent(const std::string& content, std::vector<std::pair<uint16_t, std::string>>& options)
+{
+  size_t pos = 0;
+  uint16_t code, len;
+  const size_t contentLength = content.size();
+
+  while (pos < contentLength && (contentLength - pos) >= (EDNS_OPTION_CODE_SIZE + EDNS_OPTION_LENGTH_SIZE)) {
+    code = (static_cast<unsigned char>(content.at(pos)) * 256) + static_cast<unsigned char>(content.at(pos+1));
+    pos += EDNS_OPTION_CODE_SIZE;
+    len = (static_cast<unsigned char>(content.at(pos)) * 256) + static_cast<unsigned char>(content.at(pos+1));
+    pos += EDNS_OPTION_LENGTH_SIZE;
+
+    if (pos > contentLength || len > (contentLength - pos)) {
+      return false;
+    }
+
+    options.emplace_back(code, std::string(&content.at(pos), len));
+    pos += len;
+  }
+
+  return true;
+}
+
 void generateEDNSOption(uint16_t optionCode, const std::string& payload, std::string& res)
 {
   const uint16_t ednsOptionCode = htons(optionCode);
index 019ac9bb9b01d343d7f8fdf62b00dd7ac19f2f42..4c8a330cebc311a879d8eacef6761f476755d722 100644 (file)
@@ -47,6 +47,8 @@ typedef std::map<uint16_t, EDNSOptionView> EDNSOptionViewMap;
 
 /* extract all EDNS0 options from a pointer on the beginning rdLen of the OPT RR */
 int getEDNSOptions(const char* optRR, size_t len, EDNSOptionViewMap& options);
+/* extract all EDNS0 options from the content (so after rdLen) of the OPT RR */
+bool getEDNSOptionsFromContent(const std::string& content, std::vector<std::pair<uint16_t, std::string>>& options);
 
 void generateEDNSOption(uint16_t optionCode, const std::string& payload, std::string& res);
 
index f9248af42a1193d8f28af6aa2bcdcdcf11dcd8a1..279151116a9ee6cf2d85cbd7c1d7c1f4473cc2da 100644 (file)
@@ -1236,6 +1236,56 @@ uint64_t udpErrorStats(const std::string& str)
   return 0;
 }
 
+uint64_t getCPUIOWait(const std::string& str)
+{
+#ifdef __linux__
+  ifstream ifs("/proc/stat");
+  if (!ifs) {
+    return 0;
+  }
+
+  string line;
+  vector<string> parts;
+  while (getline(ifs, line)) {
+    if (boost::starts_with(line, "cpu ")) {
+      stringtok(parts, line, " \n\t\r");
+
+      if (parts.size() < 6) {
+        break;
+      }
+
+      return std::stoull(parts[5]);
+    }
+  }
+#endif
+  return 0;
+}
+
+uint64_t getCPUSteal(const std::string& str)
+{
+#ifdef __linux__
+  ifstream ifs("/proc/stat");
+  if (!ifs) {
+    return 0;
+  }
+
+  string line;
+  vector<string> parts;
+  while (getline(ifs, line)) {
+    if (boost::starts_with(line, "cpu ")) {
+      stringtok(parts, line, " \n\t\r");
+
+      if (parts.size() < 9) {
+        break;
+      }
+
+      return std::stoull(parts[8]);
+    }
+  }
+#endif
+  return 0;
+}
+
 bool getTSIGHashEnum(const DNSName& algoName, TSIGHashEnum& algoEnum)
 {
   if (algoName == DNSName("hmac-md5.sig-alg.reg.int") || algoName == DNSName("hmac-md5"))
index 4bd9439a87deab30b7511402d391fe65b36c320c..b4924c427468f4130f73956ccb58802ebc25f919 100644 (file)
@@ -551,6 +551,8 @@ uint64_t getSpecialMemoryUsage(const std::string&);
 uint64_t getOpenFileDescriptors(const std::string&);
 uint64_t getCPUTimeUser(const std::string&);
 uint64_t getCPUTimeSystem(const std::string&);
+uint64_t getCPUIOWait(const std::string&);
+uint64_t getCPUSteal(const std::string&);
 std::string getMACAddress(const ComboAddress& ca);
 template<typename T, typename... Args>
 std::unique_ptr<T> make_unique(Args&&... args)
index 339ea604f542de999945d9eea0a74a268428aa7f..046f1d485ba1700cdd03372cb1f45cdbb8ad50ac 100644 (file)
@@ -300,6 +300,9 @@ void loadRecursorLuaConfig(const std::string& fname, luaConfigDelayedThreads& de
 
           if(have.count("refresh")) {
             refresh = boost::get<uint32_t>(have["refresh"]);
+            if (refresh == 0) {
+              g_log<<Logger::Warning<<"rpzMaster refresh value of 0 ignored"<<endl;
+            }
           }
 
           if(have.count("maxReceivedMBytes")) {
@@ -335,7 +338,6 @@ void loadRecursorLuaConfig(const std::string& fname, luaConfigDelayedThreads& de
         DNSName domain(zoneName);
         zone->setDomain(domain);
         zone->setName(polName);
-        zone->setRefresh(refresh);
         zoneIdx = lci.dfe.addZone(zone);
 
         if (!seedFile.empty()) {
@@ -365,7 +367,7 @@ void loadRecursorLuaConfig(const std::string& fname, luaConfigDelayedThreads& de
         exit(1);  // FIXME proper exit code?
       }
 
-      delayedThreads.rpzMasterThreads.push_back(std::make_tuple(masters, defpol, defpolOverrideLocal, maxTTL, zoneIdx, tt, maxReceivedXFRMBytes, localAddress, axfrTimeout, sr, dumpFile));
+      delayedThreads.rpzMasterThreads.push_back(std::make_tuple(masters, defpol, defpolOverrideLocal, maxTTL, zoneIdx, tt, maxReceivedXFRMBytes, localAddress, axfrTimeout, refresh, sr, dumpFile));
     });
 
   typedef vector<pair<int,boost::variant<string, vector<pair<int, string> > > > > argvec_t;
@@ -598,7 +600,7 @@ void startLuaConfigDelayedThreads(const luaConfigDelayedThreads& delayedThreads,
 {
   for (const auto& rpzMaster : delayedThreads.rpzMasterThreads) {
     try {
-      std::thread t(RPZIXFRTracker, std::get<0>(rpzMaster), std::get<1>(rpzMaster), std::get<2>(rpzMaster), std::get<3>(rpzMaster), std::get<4>(rpzMaster), std::get<5>(rpzMaster), std::get<6>(rpzMaster) * 1024 * 1024, std::get<7>(rpzMaster), std::get<8>(rpzMaster), std::get<9>(rpzMaster), std::get<10>(rpzMaster), generation);
+      std::thread t(RPZIXFRTracker, std::get<0>(rpzMaster), std::get<1>(rpzMaster), std::get<2>(rpzMaster), std::get<3>(rpzMaster), std::get<4>(rpzMaster), std::get<5>(rpzMaster), std::get<6>(rpzMaster) * 1024 * 1024, std::get<7>(rpzMaster), std::get<8>(rpzMaster), std::get<9>(rpzMaster), std::get<10>(rpzMaster), std::get<11>(rpzMaster), generation);
       t.detach();
     }
     catch(const std::exception& e) {
index 1cd2e62046cd9a9ee7d9c27c39df4eb38719b7dd..0ffff69c019137180e1398a684cd66e06890469b 100644 (file)
@@ -85,7 +85,7 @@ extern GlobalStateHolder<LuaConfigItems> g_luaconfs;
 
 struct luaConfigDelayedThreads
 {
-  std::vector<std::tuple<std::vector<ComboAddress>, boost::optional<DNSFilterEngine::Policy>, bool, uint32_t, size_t, TSIGTriplet, size_t, ComboAddress, uint16_t, std::shared_ptr<SOARecordContent>, std::string> > rpzMasterThreads;
+  std::vector<std::tuple<std::vector<ComboAddress>, boost::optional<DNSFilterEngine::Policy>, bool, uint32_t, size_t, TSIGTriplet, size_t, ComboAddress, uint16_t, uint32_t, std::shared_ptr<SOARecordContent>, std::string> > rpzMasterThreads;
 };
 
 void loadRecursorLuaConfig(const std::string& fname, luaConfigDelayedThreads& delayedThreads);
index a37d4cbc89ba2c50216bcd72998ddc9b4df7c45f..5eccb7583bd81bbca48c040b57f49475f927c419 100644 (file)
@@ -1162,6 +1162,11 @@ void registerAllStats()
   addGetStat("user-msec", getUserTimeMsec);
   addGetStat("sys-msec", getSysTimeMsec);
 
+#ifdef __linux__
+  addGetStat("cpu-iowait", boost::bind(getCPUIOWait, string()));
+  addGetStat("cpu-steal", boost::bind(getCPUSteal, string()));
+#endif
+
   for(unsigned int n=0; n < g_numThreads; ++n)
     addGetStat("cpu-msec-thread-"+std::to_string(n), boost::bind(&doGetThreadCPUMsec, n));
 
index 962d25032c73151669f6a77cf47dc3b7cbc40d43..320f1484d06b0780358b24d9ab62a302008887e1 100644 (file)
@@ -1,5 +1,44 @@
 Changelogs for 4.3.x
 ====================
+.. changelog::
+  :version: 4.3.0-rc1
+  :released: 3rd of February 2020
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 8751
+
+    Update boost.m4.
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 8738
+
+    Explicitly enable dnstap for debian-stretch and buster.
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 8730
+
+    Make ``ComboAddress::setPort()`` update the current object.
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 8728
+
+    EPEL 8 now has libfstrm-devel.
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 8727
+
+    Fix the evaluation order for filtering policies (RPZ).
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 8726
+
+    Give an explicit messsage if something is wrong with socket-dir.
 
 .. changelog::
   :version: 4.3.0-beta2
index d1b675e3291427852a898ca752b245c831f5cd8f..704c44149df78d370a7901928b9512dba28ad177 100644 (file)
@@ -168,10 +168,20 @@ The DNSQuestion object contains at least the following fields:
 
   .. method:: DNSQuestion:addPolicyTag(tag)
 
-     Add a policy tag.
+     Add policyTag ``tag`` to the list of policyTags.
 
      :param str tag: The tag to add
 
+  .. method:: DNSQuestion:getPolicyTags() -> {str}
+
+      Get the current policy tags as a table of strings.
+
+  .. method:: DNSQuestion:setPolicyTags(tags)
+
+      Set the policy tags to ``tags``, overwriting any existing policy tags.
+
+      :param {str} tags: The policy tags
+
   .. method:: DNSQuestion:discardPolicy(policyname)
 
      Skip the filtering policy (for example RPZ) named ``policyname`` for this query.
@@ -183,20 +193,10 @@ The DNSQuestion object contains at least the following fields:
 
       Returns the :class:`DNSHeader` of the query or nil.
 
-  .. method:: DNSQuestion:getPolicyTags() -> {str}
-
-      Get the current policy tags as a table of strings.
-
   .. method:: DNSQuestion:getRecords() -> {DNSRecord}
 
       Get a table of DNS Records in this DNS Question (or answer by now).
 
-  .. method:: DNSQuestion:setPolicyTags(tags)
-
-      Set the policy tags to ``tags``, overwriting any existing policy tags.
-
-      :param {str} tags: The policy tags
-
   .. method:: DNSQuestion:setRecords(records)
 
       After your edits, update the answers of this question
@@ -225,16 +225,6 @@ The DNSQuestion object contains at least the following fields:
 
       Returns the :class:`Netmask` specified in the EDNSSubnet option, or empty if there was none.
 
-  .. method:: DNSQuestion:addPolicyTag(tag)
-
-      Add policyTag ``tag`` to the list of policyTags
-
-      :param str tag: The tag to add
-
-  .. method:: DNSQuestion:getPolicyTags() -> {str}
-
-      Get a list the policyTags for this message.
-
 DNSHeader Object
 ================
 
index de0e6eddd1095723ab7b4ad4ac2060a8da863bb3..e6aedde774bafcf7df86caeef83358244b6d3657 100644 (file)
@@ -189,6 +189,18 @@ cpu-msec-thread-n
 ^^^^^^^^^^^^^^^^^
 shows the number of milliseconds spent in thread n. Available since 4.1.12.
 
+cpu-iowait
+^^^^^^^^^^
+.. versionadded:: 4.4
+
+Time spent waiting for I/O to complete by the whole system, in units of USER_HZ.
+
+cpu-steal
+^^^^^^^^^
+.. versionadded:: 4.4
+
+Stolen time, which is the time spent by the whole system in other operating systems when running in a virtualized environment, in units of USER_HZ.
+
 dlg-only-drops
 ^^^^^^^^^^^^^^
 number of records dropped because of :ref:`setting-delegation-only` setting
index 5ad436aa312b4301d26fd1e0237fc192116084e4..413b758c0f194d2883f77c1f5c40bfe5148c8b35 100644 (file)
@@ -441,6 +441,13 @@ private:
     {"udp-sndbuf-errors",
       MetricDefinition(PrometheusMetricType::counter,
         "From /proc/net/snmp SndbufErrors")},
+
+    {"cpu-iowait",
+      MetricDefinition(PrometheusMetricType::counter,
+        "Time spent waiting for I/O to complete by the whole system, in units of USER_HZ")},
+    {"cpu-steal",
+      MetricDefinition(PrometheusMetricType::counter,
+        "Stolen time, which is the time spent by the whole system in other operating systems when running in a virtualized environment, in units of USER_HZ")},
   };
 };
 
index e25642b6168bf8f3b2f8a808c30357f1be68837e..53cad7286056dbec5129967a2bbea051e72bb3c1 100644 (file)
@@ -262,6 +262,9 @@ std::shared_ptr<SOARecordContent> loadRPZFromFile(const std::string& fname, std:
     }
   }
 
+  if (sr != nullptr) {
+    zone->setRefresh(sr->d_st.refresh);
+  }
   return sr;
 }
 
@@ -346,18 +349,20 @@ static bool dumpZoneToDisk(const DNSName& zoneName, const std::shared_ptr<DNSFil
   return true;
 }
 
-void RPZIXFRTracker(const std::vector<ComboAddress>& masters, boost::optional<DNSFilterEngine::Policy> defpol, bool defpolOverrideLocal, uint32_t maxTTL, size_t zoneIdx, const TSIGTriplet& tt, size_t maxReceivedBytes, const ComboAddress& localAddress, const uint16_t axfrTimeout, std::shared_ptr<SOARecordContent> sr, std::string dumpZoneFileName, uint64_t configGeneration)
+void RPZIXFRTracker(const std::vector<ComboAddress>& masters, boost::optional<DNSFilterEngine::Policy> defpol, bool defpolOverrideLocal, uint32_t maxTTL, size_t zoneIdx, const TSIGTriplet& tt, size_t maxReceivedBytes, const ComboAddress& localAddress, const uint16_t axfrTimeout, const uint32_t refreshFromConf, std::shared_ptr<SOARecordContent> sr, std::string dumpZoneFileName, uint64_t configGeneration)
 {
   setThreadName("pdns-r/RPZIXFR");
   bool isPreloaded = sr != nullptr;
   auto luaconfsLocal = g_luaconfs.getLocal();
+
   /* we can _never_ modify this zone directly, we need to do a full copy then replace the existing zone */
   std::shared_ptr<DNSFilterEngine::Zone> oldZone = luaconfsLocal->dfe.getZone(zoneIdx);
   if (!oldZone) {
     g_log<<Logger::Error<<"Unable to retrieve RPZ zone with index "<<zoneIdx<<" from the configuration, exiting"<<endl;
     return;
   }
-  uint32_t refresh = oldZone->getRefresh();
+
+  time_t refresh;
   DNSName zoneName = oldZone->getDomain();
   std::string polName = oldZone->getName() ? *(oldZone->getName()) : zoneName.toString();
 
@@ -368,11 +373,10 @@ void RPZIXFRTracker(const std::vector<ComboAddress>& masters, boost::optional<DN
     std::shared_ptr<DNSFilterEngine::Zone> newZone = std::make_shared<DNSFilterEngine::Zone>(*oldZone);
     for (const auto& master : masters) {
       try {
+        refresh = refreshFromConf ? refreshFromConf : 10U;
         sr = loadRPZFromServer(master, zoneName, newZone, defpol, defpolOverrideLocal, maxTTL, tt, maxReceivedBytes, localAddress, axfrTimeout);
-        if(refresh == 0) {
-          refresh = sr->d_st.refresh;
-        }
         newZone->setSerial(sr->d_st.serial);
+        newZone->setRefresh(sr->d_st.refresh);
         setRPZZoneNewState(polName, sr->d_st.serial, newZone->size(), true);
 
         g_luaconfs.modify([zoneIdx, &newZone](LuaConfigItems& lci) {
@@ -387,24 +391,21 @@ void RPZIXFRTracker(const std::vector<ComboAddress>& masters, boost::optional<DN
         break;
       }
       catch(const std::exception& e) {
-        g_log<<Logger::Warning<<"Unable to load RPZ zone '"<<zoneName<<"' from '"<<master<<"': '"<<e.what()<<"'. (Will try again in "<<(refresh > 0 ? refresh : 10)<<" seconds...)"<<endl;
+        g_log<<Logger::Warning<<"Unable to load RPZ zone '"<<zoneName<<"' from '"<<master<<"': '"<<e.what()<<"'. (Will try again in "<<refresh<<" seconds...)"<<endl;
         incRPZFailedTransfers(polName);
       }
       catch(const PDNSException& e) {
-        g_log<<Logger::Warning<<"Unable to load RPZ zone '"<<zoneName<<"' from '"<<master<<"': '"<<e.reason<<"'. (Will try again in "<<(refresh > 0 ? refresh : 10)<<" seconds...)"<<endl;
+        g_log<<Logger::Warning<<"Unable to load RPZ zone '"<<zoneName<<"' from '"<<master<<"': '"<<e.reason<<"'. (Will try again in "<<refresh<<" seconds...)"<<endl;
         incRPZFailedTransfers(polName);
       }
     }
 
     if (!sr) {
-      if (refresh == 0) {
-        sleep(10);
-      } else {
-        sleep(refresh);
-      }
+      sleep(refresh);
     }
   }
 
+  refresh = std::max(refreshFromConf ? refreshFromConf :  oldZone->getRefresh(), 1U);
   bool skipRefreshDelay = isPreloaded;
 
   for(;;) {
@@ -504,6 +505,7 @@ void RPZIXFRTracker(const std::vector<ComboAddress>& masters, boost::optional<DN
     }
     g_log<<Logger::Info<<"Had "<<totremove<<" RPZ removal"<<addS(totremove)<<", "<<totadd<<" addition"<<addS(totadd)<<" for "<<zoneName<<" New serial: "<<sr->d_st.serial<<endl;
     newZone->setSerial(sr->d_st.serial);
+    newZone->setRefresh(sr->d_st.refresh);
     setRPZZoneNewState(polName, sr->d_st.serial, newZone->size(), fullUpdate);
 
     /* we need to replace the existing zone with the new one,
@@ -517,5 +519,6 @@ void RPZIXFRTracker(const std::vector<ComboAddress>& masters, boost::optional<DN
     if (!dumpZoneFileName.empty()) {
       dumpZoneToDisk(zoneName, newZone, dumpZoneFileName);
     }
+    refresh = std::max(refreshFromConf ? refreshFromConf :  newZone->getRefresh(), 1U);
   }
 }
index 345a1e31ee7f81b77672757c17039ef5a3159156..c11156f6dd9ca78e0b8b7726865f31951c89b1f9 100644 (file)
@@ -27,7 +27,7 @@
 extern bool g_logRPZChanges;
 
 std::shared_ptr<SOARecordContent> loadRPZFromFile(const std::string& fname, std::shared_ptr<DNSFilterEngine::Zone> zone, boost::optional<DNSFilterEngine::Policy> defpol, bool defpolOverrideLocal, uint32_t maxTTL);
-void RPZIXFRTracker(const std::vector<ComboAddress>& masters, boost::optional<DNSFilterEngine::Policy> defpol, bool defpolOverrideLocal, uint32_t maxTTL, size_t zoneIdx, const TSIGTriplet& tt, size_t maxReceivedBytes, const ComboAddress& localAddress, const uint16_t axfrTimeout, shared_ptr<SOARecordContent> sr, std::string dumpZoneFileName, uint64_t configGeneration);
+void RPZIXFRTracker(const std::vector<ComboAddress>& masters, boost::optional<DNSFilterEngine::Policy> defpol, bool defpolOverrideLocal, uint32_t maxTTL, size_t zoneIdx, const TSIGTriplet& tt, size_t maxReceivedBytes, const ComboAddress& localAddress, const uint16_t axfrTimeout, const uint32_t reloadFromConf, shared_ptr<SOARecordContent> sr, std::string dumpZoneFileName, uint64_t configGeneration);
 
 struct rpzStats
 {
index 18739a92bef399877396a37990137c0ee720a818..94843022aeac434327a835adf9ea14bd6cbbb550 100644 (file)
@@ -679,8 +679,8 @@ int TCPNameserver::doAXFR(const DNSName &target, std::unique_ptr<DNSPacket>& q,
   zrr.auth = 1; // please sign!
 
   string publishCDNSKEY, publishCDS;
-  dk.getFromMeta(q->qdomain, "PUBLISH-CDNSKEY", publishCDNSKEY);
-  dk.getFromMeta(q->qdomain, "PUBLISH-CDS", publishCDS);
+  dk.getPublishCDNSKEY(q->qdomain, publishCDNSKEY);
+  dk.getPublishCDS(q->qdomain, publishCDS);
   vector<DNSZoneRecord> cds, cdnskey;
   DNSSECKeeper::keyset_t entryPoints = dk.getEntryPoints(q->qdomain);
   set<uint32_t> entryPointIds;
index 14d463db31b4928ec5f71cd9c5004d6e7a3185e2..bc06bebf8299ca6b72508529109b868f1c1ae94d 100644 (file)
@@ -42,16 +42,16 @@ BOOST_AUTO_TEST_SUITE(test_dnsdist_cc)
 static const uint16_t ECSSourcePrefixV4 = 24;
 static const uint16_t ECSSourcePrefixV6 = 56;
 
-static void validateQuery(const char * packet, size_t packetSize, bool hasEdns=true, bool hasXPF=false)
+static void validateQuery(const char * packet, size_t packetSize, bool hasEdns=true, bool hasXPF=false, uint16_t additionals=0, uint16_t answers=0, uint16_t authorities=0)
 {
   MOADNSParser mdp(true, packet, packetSize);
 
   BOOST_CHECK_EQUAL(mdp.d_qname.toString(), "www.powerdns.com.");
 
   BOOST_CHECK_EQUAL(mdp.d_header.qdcount, 1U);
-  BOOST_CHECK_EQUAL(mdp.d_header.ancount, 0U);
-  BOOST_CHECK_EQUAL(mdp.d_header.nscount, 0U);
-  uint16_t expectedARCount = 0 + (hasEdns ? 1 : 0) + (hasXPF ? 1 : 0);
+  BOOST_CHECK_EQUAL(mdp.d_header.ancount, answers);
+  BOOST_CHECK_EQUAL(mdp.d_header.nscount, authorities);
+  uint16_t expectedARCount = additionals + (hasEdns ? 1U : 0U) + (hasXPF ? 1U : 0U);
   BOOST_CHECK_EQUAL(mdp.d_header.arcount, expectedARCount);
 }
 
@@ -231,10 +231,10 @@ BOOST_AUTO_TEST_CASE(addECSWithoutEDNS)
   BOOST_CHECK_EQUAL(qname, name);
   BOOST_CHECK(qtype == QType::A);
 
-  BOOST_CHECK(handleEDNSClientSubnet(packet, sizeof packet, consumed, &len, &ednsAdded, &ecsAdded, false, newECSOption, false));
+  BOOST_CHECK(handleEDNSClientSubnet(packet, sizeof packet, consumed, &len, ednsAdded, ecsAdded, false, newECSOption, false));
   BOOST_CHECK(static_cast<size_t>(len) > query.size());
   BOOST_CHECK_EQUAL(ednsAdded, true);
-  BOOST_CHECK_EQUAL(ecsAdded, false);
+  BOOST_CHECK_EQUAL(ecsAdded, true);
   validateQuery(packet, len);
   validateECS(packet, len, remote);
   vector<uint8_t> queryWithEDNS;
@@ -250,7 +250,7 @@ BOOST_AUTO_TEST_CASE(addECSWithoutEDNS)
   BOOST_CHECK_EQUAL(qname, name);
   BOOST_CHECK(qtype == QType::A);
 
-  BOOST_CHECK(!handleEDNSClientSubnet(reinterpret_cast<char*>(query.data()), query.size(), consumed, &len, &ednsAdded, &ecsAdded, false, newECSOption, false));
+  BOOST_CHECK(!handleEDNSClientSubnet(reinterpret_cast<char*>(query.data()), query.size(), consumed, &len, ednsAdded, ecsAdded, false, newECSOption, false));
   BOOST_CHECK_EQUAL(static_cast<size_t>(len), query.size());
   BOOST_CHECK_EQUAL(ednsAdded, false);
   BOOST_CHECK_EQUAL(ecsAdded, false);
@@ -273,11 +273,11 @@ BOOST_AUTO_TEST_CASE(addECSWithoutEDNS)
     packet[len + idx] = 'A';
   }
   len += trailingDataSize;
-  BOOST_CHECK(handleEDNSClientSubnet(packet, sizeof packet, consumed, &len, &ednsAdded, &ecsAdded, false, newECSOption, false));
+  BOOST_CHECK(handleEDNSClientSubnet(packet, sizeof packet, consumed, &len, ednsAdded, ecsAdded, false, newECSOption, false));
   BOOST_REQUIRE_EQUAL(static_cast<size_t>(len), queryWithEDNS.size());
   BOOST_CHECK_EQUAL(memcmp(queryWithEDNS.data(), packet, queryWithEDNS.size()), 0);
   BOOST_CHECK_EQUAL(ednsAdded, true);
-  BOOST_CHECK_EQUAL(ecsAdded, false);
+  BOOST_CHECK_EQUAL(ecsAdded, true);
   validateQuery(packet, len);
 
   /* packet with trailing data (preserving trailing data) */
@@ -296,14 +296,14 @@ BOOST_AUTO_TEST_CASE(addECSWithoutEDNS)
     packet[len + idx] = 'A';
   }
   len += trailingDataSize;
-  BOOST_CHECK(handleEDNSClientSubnet(packet, sizeof packet, consumed, &len, &ednsAdded, &ecsAdded, false, newECSOption, true));
+  BOOST_CHECK(handleEDNSClientSubnet(packet, sizeof packet, consumed, &len, ednsAdded, ecsAdded, false, newECSOption, true));
   BOOST_REQUIRE_EQUAL(static_cast<size_t>(len), queryWithEDNS.size() + trailingDataSize);
   BOOST_CHECK_EQUAL(memcmp(queryWithEDNS.data(), packet, queryWithEDNS.size()), 0);
   for (size_t idx = 0; idx < trailingDataSize; idx++) {
     BOOST_CHECK_EQUAL(packet[queryWithEDNS.size() + idx], 'A');
   }
   BOOST_CHECK_EQUAL(ednsAdded, true);
-  BOOST_CHECK_EQUAL(ecsAdded, false);
+  BOOST_CHECK_EQUAL(ecsAdded, true);
   validateQuery(packet, len);
 }
 
@@ -335,10 +335,10 @@ BOOST_AUTO_TEST_CASE(addECSWithoutEDNSAlreadyParsed)
   BOOST_CHECK(!parseEDNSOptions(dq));
 
   /* And now we add our own ECS */
-  BOOST_CHECK(handleEDNSClientSubnet(dq, &ednsAdded, &ecsAdded, false));
+  BOOST_CHECK(handleEDNSClientSubnet(dq, ednsAdded, ecsAdded, false));
   BOOST_CHECK_GT(static_cast<size_t>(dq.len), query.size());
   BOOST_CHECK_EQUAL(ednsAdded, true);
-  BOOST_CHECK_EQUAL(ecsAdded, false);
+  BOOST_CHECK_EQUAL(ecsAdded, true);
   validateQuery(packet, dq.len);
   validateECS(packet, dq.len, remote);
 
@@ -352,7 +352,7 @@ BOOST_AUTO_TEST_CASE(addECSWithoutEDNSAlreadyParsed)
   BOOST_CHECK(qclass == QClass::IN);
   DNSQuestion dq2(&qname, qtype, qclass, consumed, nullptr, &remote, reinterpret_cast<dnsheader*>(query.data()), query.size(), query.size(), false, nullptr);
 
-  BOOST_CHECK(!handleEDNSClientSubnet(dq2, &ednsAdded, &ecsAdded, false));
+  BOOST_CHECK(!handleEDNSClientSubnet(dq2, ednsAdded, ecsAdded, false));
   BOOST_CHECK_EQUAL(static_cast<size_t>(dq2.len), query.size());
   BOOST_CHECK_EQUAL(ednsAdded, false);
   BOOST_CHECK_EQUAL(ecsAdded, false);
@@ -384,7 +384,7 @@ BOOST_AUTO_TEST_CASE(addECSWithEDNSNoECS) {
   BOOST_CHECK_EQUAL(qname, name);
   BOOST_CHECK(qtype == QType::A);
 
-  BOOST_CHECK(handleEDNSClientSubnet(packet, sizeof packet, consumed, &len, &ednsAdded, &ecsAdded, false, newECSOption, false));
+  BOOST_CHECK(handleEDNSClientSubnet(packet, sizeof packet, consumed, &len, ednsAdded, ecsAdded, false, newECSOption, false));
   BOOST_CHECK((size_t) len > query.size());
   BOOST_CHECK_EQUAL(ednsAdded, false);
   BOOST_CHECK_EQUAL(ecsAdded, true);
@@ -400,7 +400,7 @@ BOOST_AUTO_TEST_CASE(addECSWithEDNSNoECS) {
   BOOST_CHECK_EQUAL(qname, name);
   BOOST_CHECK(qtype == QType::A);
 
-  BOOST_CHECK(!handleEDNSClientSubnet(reinterpret_cast<char*>(query.data()), query.size(), consumed, &len, &ednsAdded, &ecsAdded, false, newECSOption, false));
+  BOOST_CHECK(!handleEDNSClientSubnet(reinterpret_cast<char*>(query.data()), query.size(), consumed, &len, ednsAdded, ecsAdded, false, newECSOption, false));
   BOOST_CHECK_EQUAL((size_t) len, query.size());
   BOOST_CHECK_EQUAL(ednsAdded, false);
   BOOST_CHECK_EQUAL(ecsAdded, false);
@@ -436,7 +436,7 @@ BOOST_AUTO_TEST_CASE(addECSWithEDNSNoECSAlreadyParsed) {
   BOOST_CHECK(parseEDNSOptions(dq));
 
   /* And now we add our own ECS */
-  BOOST_CHECK(handleEDNSClientSubnet(dq, &ednsAdded, &ecsAdded, false));
+  BOOST_CHECK(handleEDNSClientSubnet(dq, ednsAdded, ecsAdded, false));
   BOOST_CHECK_GT(static_cast<size_t>(dq.len), query.size());
   BOOST_CHECK_EQUAL(ednsAdded, false);
   BOOST_CHECK_EQUAL(ecsAdded, true);
@@ -453,7 +453,7 @@ BOOST_AUTO_TEST_CASE(addECSWithEDNSNoECSAlreadyParsed) {
   BOOST_CHECK(qclass == QClass::IN);
   DNSQuestion dq2(&qname, qtype, qclass, consumed, nullptr, &remote, reinterpret_cast<dnsheader*>(query.data()), query.size(), query.size(), false, nullptr);
 
-  BOOST_CHECK(!handleEDNSClientSubnet(dq2, &ednsAdded, &ecsAdded, false));
+  BOOST_CHECK(!handleEDNSClientSubnet(dq2, ednsAdded, ecsAdded, false));
   BOOST_CHECK_EQUAL(static_cast<size_t>(dq2.len), query.size());
   BOOST_CHECK_EQUAL(ednsAdded, false);
   BOOST_CHECK_EQUAL(ecsAdded, false);
@@ -491,7 +491,7 @@ BOOST_AUTO_TEST_CASE(replaceECSWithSameSize) {
   BOOST_CHECK_EQUAL(qname, name);
   BOOST_CHECK(qtype == QType::A);
 
-  BOOST_CHECK(handleEDNSClientSubnet(packet, sizeof packet, consumed, &len, &ednsAdded, &ecsAdded, true, newECSOption, false));
+  BOOST_CHECK(handleEDNSClientSubnet(packet, sizeof packet, consumed, &len, ednsAdded, ecsAdded, true, newECSOption, false));
   BOOST_CHECK_EQUAL((size_t) len, query.size());
   BOOST_CHECK_EQUAL(ednsAdded, false);
   BOOST_CHECK_EQUAL(ecsAdded, false);
@@ -536,7 +536,7 @@ BOOST_AUTO_TEST_CASE(replaceECSWithSameSizeAlreadyParsed) {
   BOOST_CHECK(parseEDNSOptions(dq));
 
   /* And now we add our own ECS */
-  BOOST_CHECK(handleEDNSClientSubnet(dq, &ednsAdded, &ecsAdded, false));
+  BOOST_CHECK(handleEDNSClientSubnet(dq, ednsAdded, ecsAdded, false));
   BOOST_CHECK_EQUAL(static_cast<size_t>(dq.len), query.size());
   BOOST_CHECK_EQUAL(ednsAdded, false);
   BOOST_CHECK_EQUAL(ecsAdded, false);
@@ -575,7 +575,7 @@ BOOST_AUTO_TEST_CASE(replaceECSWithSmaller) {
   BOOST_CHECK_EQUAL(qname, name);
   BOOST_CHECK(qtype == QType::A);
 
-  BOOST_CHECK(handleEDNSClientSubnet(packet, sizeof packet, consumed, &len, &ednsAdded, &ecsAdded, true, newECSOption, false));
+  BOOST_CHECK(handleEDNSClientSubnet(packet, sizeof packet, consumed, &len, ednsAdded, ecsAdded, true, newECSOption, false));
   BOOST_CHECK((size_t) len < query.size());
   BOOST_CHECK_EQUAL(ednsAdded, false);
   BOOST_CHECK_EQUAL(ecsAdded, false);
@@ -614,7 +614,7 @@ BOOST_AUTO_TEST_CASE(replaceECSWithLarger) {
   BOOST_CHECK_EQUAL(qname, name);
   BOOST_CHECK(qtype == QType::A);
 
-  BOOST_CHECK(handleEDNSClientSubnet(packet, sizeof packet, consumed, &len, &ednsAdded, &ecsAdded, true, newECSOption, false));
+  BOOST_CHECK(handleEDNSClientSubnet(packet, sizeof packet, consumed, &len, ednsAdded, ecsAdded, true, newECSOption, false));
   BOOST_CHECK((size_t) len > query.size());
   BOOST_CHECK_EQUAL(ednsAdded, false);
   BOOST_CHECK_EQUAL(ecsAdded, false);
@@ -630,13 +630,340 @@ BOOST_AUTO_TEST_CASE(replaceECSWithLarger) {
   BOOST_CHECK_EQUAL(qname, name);
   BOOST_CHECK(qtype == QType::A);
 
-  BOOST_CHECK(!handleEDNSClientSubnet(reinterpret_cast<char*>(query.data()), query.size(), consumed, &len, &ednsAdded, &ecsAdded, true, newECSOption, false));
+  BOOST_CHECK(!handleEDNSClientSubnet(reinterpret_cast<char*>(query.data()), query.size(), consumed, &len, ednsAdded, ecsAdded, true, newECSOption, false));
   BOOST_CHECK_EQUAL((size_t) len, query.size());
   BOOST_CHECK_EQUAL(ednsAdded, false);
   BOOST_CHECK_EQUAL(ecsAdded, false);
   validateQuery(reinterpret_cast<char*>(query.data()), len);
 }
 
+BOOST_AUTO_TEST_CASE(replaceECSFollowedByTSIG) {
+  bool ednsAdded = false;
+  bool ecsAdded = false;
+  ComboAddress remote("192.168.1.25");
+  DNSName name("www.powerdns.com.");
+  ComboAddress origRemote("127.0.0.1");
+  string newECSOption;
+  generateECSOption(remote, newECSOption, remote.sin4.sin_family == AF_INET ? ECSSourcePrefixV4 : ECSSourcePrefixV6);
+
+  vector<uint8_t> query;
+  DNSPacketWriter pw(query, name, QType::A, QClass::IN, 0);
+  pw.getHeader()->rd = 1;
+  EDNSSubnetOpts ecsOpts;
+  ecsOpts.source = Netmask(origRemote, 8);
+  string origECSOption = makeEDNSSubnetOptsString(ecsOpts);
+  DNSPacketWriter::optvect_t opts;
+  opts.push_back(make_pair(EDNSOptionCode::ECS, origECSOption));
+  pw.addOpt(512, 0, 0, opts);
+  pw.startRecord(DNSName("tsigname."), QType::TSIG, 0, QClass::ANY, DNSResourceRecord::ADDITIONAL, false);
+  pw.commit();
+  uint16_t len = query.size();
+
+  /* large enough packet */
+  char packet[1500];
+  memcpy(packet, query.data(), query.size());
+
+  unsigned int consumed = 0;
+  uint16_t qtype;
+  DNSName qname(packet, len, sizeof(dnsheader), false, &qtype, NULL, &consumed);
+  BOOST_CHECK_EQUAL(qname, name);
+  BOOST_CHECK(qtype == QType::A);
+
+  BOOST_CHECK(handleEDNSClientSubnet(packet, sizeof packet, consumed, &len, ednsAdded, ecsAdded, true, newECSOption, false));
+  BOOST_CHECK((size_t) len > query.size());
+  BOOST_CHECK_EQUAL(ednsAdded, false);
+  BOOST_CHECK_EQUAL(ecsAdded, false);
+  validateQuery(packet, len, true, false, 1);
+  validateECS(packet, len, remote);
+
+  /* not large enough packet */
+  ednsAdded = false;
+  ecsAdded = false;
+  consumed = 0;
+  len = query.size();
+  qname = DNSName(reinterpret_cast<char*>(query.data()), len, sizeof(dnsheader), false, &qtype, NULL, &consumed);
+  BOOST_CHECK_EQUAL(qname, name);
+  BOOST_CHECK(qtype == QType::A);
+
+  BOOST_CHECK(!handleEDNSClientSubnet(reinterpret_cast<char*>(query.data()), query.size(), consumed, &len, ednsAdded, ecsAdded, true, newECSOption, false));
+  BOOST_CHECK_EQUAL((size_t) len, query.size());
+  BOOST_CHECK_EQUAL(ednsAdded, false);
+  BOOST_CHECK_EQUAL(ecsAdded, false);
+  validateQuery(reinterpret_cast<char*>(query.data()), len, true, false, 1);
+}
+
+BOOST_AUTO_TEST_CASE(replaceECSAfterAN) {
+  bool ednsAdded = false;
+  bool ecsAdded = false;
+  ComboAddress remote("192.168.1.25");
+  DNSName name("www.powerdns.com.");
+  ComboAddress origRemote("127.0.0.1");
+  string newECSOption;
+  generateECSOption(remote, newECSOption, remote.sin4.sin_family == AF_INET ? ECSSourcePrefixV4 : ECSSourcePrefixV6);
+
+  vector<uint8_t> query;
+  DNSPacketWriter pw(query, name, QType::A, QClass::IN, 0);
+  pw.getHeader()->rd = 1;
+  pw.startRecord(DNSName("powerdns.com."), QType::A, 0, QClass::IN, DNSResourceRecord::ANSWER, true);
+  pw.commit();
+  EDNSSubnetOpts ecsOpts;
+  ecsOpts.source = Netmask(origRemote, 8);
+  string origECSOption = makeEDNSSubnetOptsString(ecsOpts);
+  DNSPacketWriter::optvect_t opts;
+  opts.push_back(make_pair(EDNSOptionCode::ECS, origECSOption));
+  pw.addOpt(512, 0, 0, opts);
+  pw.commit();
+  uint16_t len = query.size();
+
+  /* large enough packet */
+  char packet[1500];
+  memcpy(packet, query.data(), query.size());
+
+  unsigned int consumed = 0;
+  uint16_t qtype;
+  DNSName qname(packet, len, sizeof(dnsheader), false, &qtype, NULL, &consumed);
+  BOOST_CHECK_EQUAL(qname, name);
+  BOOST_CHECK(qtype == QType::A);
+
+  BOOST_CHECK(handleEDNSClientSubnet(packet, sizeof packet, consumed, &len, ednsAdded, ecsAdded, true, newECSOption, false));
+  BOOST_CHECK((size_t) len > query.size());
+  BOOST_CHECK_EQUAL(ednsAdded, false);
+  BOOST_CHECK_EQUAL(ecsAdded, false);
+  validateQuery(packet, len, true, false, 0, 1, 0);
+  validateECS(packet, len, remote);
+
+  /* not large enough packet */
+  ednsAdded = false;
+  ecsAdded = false;
+  consumed = 0;
+  len = query.size();
+  qname = DNSName(reinterpret_cast<char*>(query.data()), len, sizeof(dnsheader), false, &qtype, NULL, &consumed);
+  BOOST_CHECK_EQUAL(qname, name);
+  BOOST_CHECK(qtype == QType::A);
+
+  BOOST_CHECK(!handleEDNSClientSubnet(reinterpret_cast<char*>(query.data()), query.size(), consumed, &len, ednsAdded, ecsAdded, true, newECSOption, false));
+  BOOST_CHECK_EQUAL((size_t) len, query.size());
+  BOOST_CHECK_EQUAL(ednsAdded, false);
+  BOOST_CHECK_EQUAL(ecsAdded, false);
+  validateQuery(reinterpret_cast<char*>(query.data()), len, true, false, 0, 1, 0);
+}
+
+BOOST_AUTO_TEST_CASE(replaceECSAfterAuth) {
+  bool ednsAdded = false;
+  bool ecsAdded = false;
+  ComboAddress remote("192.168.1.25");
+  DNSName name("www.powerdns.com.");
+  ComboAddress origRemote("127.0.0.1");
+  string newECSOption;
+  generateECSOption(remote, newECSOption, remote.sin4.sin_family == AF_INET ? ECSSourcePrefixV4 : ECSSourcePrefixV6);
+
+  vector<uint8_t> query;
+  DNSPacketWriter pw(query, name, QType::A, QClass::IN, 0);
+  pw.getHeader()->rd = 1;
+  pw.startRecord(DNSName("powerdns.com."), QType::A, 0, QClass::IN, DNSResourceRecord::AUTHORITY, true);
+  pw.commit();
+  EDNSSubnetOpts ecsOpts;
+  ecsOpts.source = Netmask(origRemote, 8);
+  string origECSOption = makeEDNSSubnetOptsString(ecsOpts);
+  DNSPacketWriter::optvect_t opts;
+  opts.push_back(make_pair(EDNSOptionCode::ECS, origECSOption));
+  pw.addOpt(512, 0, 0, opts);
+  pw.commit();
+  uint16_t len = query.size();
+
+  /* large enough packet */
+  char packet[1500];
+  memcpy(packet, query.data(), query.size());
+
+  unsigned int consumed = 0;
+  uint16_t qtype;
+  DNSName qname(packet, len, sizeof(dnsheader), false, &qtype, NULL, &consumed);
+  BOOST_CHECK_EQUAL(qname, name);
+  BOOST_CHECK(qtype == QType::A);
+
+  BOOST_CHECK(handleEDNSClientSubnet(packet, sizeof packet, consumed, &len, ednsAdded, ecsAdded, true, newECSOption, false));
+  BOOST_CHECK((size_t) len > query.size());
+  BOOST_CHECK_EQUAL(ednsAdded, false);
+  BOOST_CHECK_EQUAL(ecsAdded, false);
+  validateQuery(packet, len, true, false, 0, 0, 1);
+  validateECS(packet, len, remote);
+
+  /* not large enough packet */
+  ednsAdded = false;
+  ecsAdded = false;
+  consumed = 0;
+  len = query.size();
+  qname = DNSName(reinterpret_cast<char*>(query.data()), len, sizeof(dnsheader), false, &qtype, NULL, &consumed);
+  BOOST_CHECK_EQUAL(qname, name);
+  BOOST_CHECK(qtype == QType::A);
+
+  BOOST_CHECK(!handleEDNSClientSubnet(reinterpret_cast<char*>(query.data()), query.size(), consumed, &len, ednsAdded, ecsAdded, true, newECSOption, false));
+  BOOST_CHECK_EQUAL((size_t) len, query.size());
+  BOOST_CHECK_EQUAL(ednsAdded, false);
+  BOOST_CHECK_EQUAL(ecsAdded, false);
+  validateQuery(reinterpret_cast<char*>(query.data()), len, true, false, 0, 0, 1);
+}
+
+BOOST_AUTO_TEST_CASE(replaceECSBetweenTwoRecords) {
+  bool ednsAdded = false;
+  bool ecsAdded = false;
+  ComboAddress remote("192.168.1.25");
+  DNSName name("www.powerdns.com.");
+  ComboAddress origRemote("127.0.0.1");
+  string newECSOption;
+  generateECSOption(remote, newECSOption, remote.sin4.sin_family == AF_INET ? ECSSourcePrefixV4 : ECSSourcePrefixV6);
+
+  vector<uint8_t> query;
+  DNSPacketWriter pw(query, name, QType::A, QClass::IN, 0);
+  pw.getHeader()->rd = 1;
+  EDNSSubnetOpts ecsOpts;
+  ecsOpts.source = Netmask(origRemote, 8);
+  string origECSOption = makeEDNSSubnetOptsString(ecsOpts);
+  DNSPacketWriter::optvect_t opts;
+  opts.push_back(make_pair(EDNSOptionCode::ECS, origECSOption));
+  pw.startRecord(DNSName("additional"), QType::A, 0, QClass::IN, DNSResourceRecord::ADDITIONAL, false);
+  pw.xfr32BitInt(0x01020304);
+  pw.addOpt(512, 0, 0, opts);
+  pw.startRecord(DNSName("tsigname."), QType::TSIG, 0, QClass::ANY, DNSResourceRecord::ADDITIONAL, false);
+  pw.commit();
+  uint16_t len = query.size();
+
+  /* large enough packet */
+  char packet[1500];
+  memcpy(packet, query.data(), query.size());
+
+  unsigned int consumed = 0;
+  uint16_t qtype;
+  DNSName qname(packet, len, sizeof(dnsheader), false, &qtype, NULL, &consumed);
+  BOOST_CHECK_EQUAL(qname, name);
+  BOOST_CHECK(qtype == QType::A);
+
+  BOOST_CHECK(handleEDNSClientSubnet(packet, sizeof packet, consumed, &len, ednsAdded, ecsAdded, true, newECSOption, false));
+  BOOST_CHECK((size_t) len > query.size());
+  BOOST_CHECK_EQUAL(ednsAdded, false);
+  BOOST_CHECK_EQUAL(ecsAdded, false);
+  validateQuery(packet, len, true, false, 2);
+  validateECS(packet, len, remote);
+
+  /* not large enough packet */
+  ednsAdded = false;
+  ecsAdded = false;
+  consumed = 0;
+  len = query.size();
+  qname = DNSName(reinterpret_cast<char*>(query.data()), len, sizeof(dnsheader), false, &qtype, NULL, &consumed);
+  BOOST_CHECK_EQUAL(qname, name);
+  BOOST_CHECK(qtype == QType::A);
+
+  BOOST_CHECK(!handleEDNSClientSubnet(reinterpret_cast<char*>(query.data()), query.size(), consumed, &len, ednsAdded, ecsAdded, true, newECSOption, false));
+  BOOST_CHECK_EQUAL((size_t) len, query.size());
+  BOOST_CHECK_EQUAL(ednsAdded, false);
+  BOOST_CHECK_EQUAL(ecsAdded, false);
+  validateQuery(reinterpret_cast<char*>(query.data()), len, true, false, 2);
+}
+
+BOOST_AUTO_TEST_CASE(insertECSInEDNSBetweenTwoRecords) {
+  bool ednsAdded = false;
+  bool ecsAdded = false;
+  ComboAddress remote("192.168.1.25");
+  DNSName name("www.powerdns.com.");
+  ComboAddress origRemote("127.0.0.1");
+  string newECSOption;
+  generateECSOption(remote, newECSOption, remote.sin4.sin_family == AF_INET ? ECSSourcePrefixV4 : ECSSourcePrefixV6);
+
+  vector<uint8_t> query;
+  DNSPacketWriter pw(query, name, QType::A, QClass::IN, 0);
+  pw.getHeader()->rd = 1;
+  pw.startRecord(DNSName("additional"), QType::A, 0, QClass::IN, DNSResourceRecord::ADDITIONAL, false);
+  pw.xfr32BitInt(0x01020304);
+  pw.addOpt(512, 0, 0);
+  pw.startRecord(DNSName("tsigname."), QType::TSIG, 0, QClass::ANY, DNSResourceRecord::ADDITIONAL, false);
+  pw.commit();
+  uint16_t len = query.size();
+
+  /* large enough packet */
+  char packet[1500];
+  memcpy(packet, query.data(), query.size());
+
+  unsigned int consumed = 0;
+  uint16_t qtype;
+  DNSName qname(packet, len, sizeof(dnsheader), false, &qtype, NULL, &consumed);
+  BOOST_CHECK_EQUAL(qname, name);
+  BOOST_CHECK(qtype == QType::A);
+
+  BOOST_CHECK(handleEDNSClientSubnet(packet, sizeof packet, consumed, &len, ednsAdded, ecsAdded, true, newECSOption, false));
+  BOOST_CHECK((size_t) len > query.size());
+  BOOST_CHECK_EQUAL(ednsAdded, false);
+  BOOST_CHECK_EQUAL(ecsAdded, true);
+  validateQuery(packet, len, true, false, 2);
+  validateECS(packet, len, remote);
+
+  /* not large enough packet */
+  ednsAdded = false;
+  ecsAdded = false;
+  consumed = 0;
+  len = query.size();
+  qname = DNSName(reinterpret_cast<char*>(query.data()), len, sizeof(dnsheader), false, &qtype, NULL, &consumed);
+  BOOST_CHECK_EQUAL(qname, name);
+  BOOST_CHECK(qtype == QType::A);
+
+  BOOST_CHECK(!handleEDNSClientSubnet(reinterpret_cast<char*>(query.data()), query.size(), consumed, &len, ednsAdded, ecsAdded, true, newECSOption, false));
+  BOOST_CHECK_EQUAL((size_t) len, query.size());
+  BOOST_CHECK_EQUAL(ednsAdded, false);
+  BOOST_CHECK_EQUAL(ecsAdded, false);
+  validateQuery(reinterpret_cast<char*>(query.data()), len, true, false, 2);
+}
+
+BOOST_AUTO_TEST_CASE(insertECSAfterTSIG) {
+  bool ednsAdded = false;
+  bool ecsAdded = false;
+  ComboAddress remote("192.168.1.25");
+  DNSName name("www.powerdns.com.");
+  ComboAddress origRemote("127.0.0.1");
+  string newECSOption;
+  generateECSOption(remote, newECSOption, remote.sin4.sin_family == AF_INET ? ECSSourcePrefixV4 : ECSSourcePrefixV6);
+
+  vector<uint8_t> query;
+  DNSPacketWriter pw(query, name, QType::A, QClass::IN, 0);
+  pw.getHeader()->rd = 1;
+  pw.startRecord(DNSName("tsigname."), QType::TSIG, 0, QClass::ANY, DNSResourceRecord::ADDITIONAL, false);
+  pw.commit();
+  uint16_t len = query.size();
+
+  /* large enough packet */
+  char packet[1500];
+  memcpy(packet, query.data(), query.size());
+
+  unsigned int consumed = 0;
+  uint16_t qtype;
+  DNSName qname(packet, len, sizeof(dnsheader), false, &qtype, NULL, &consumed);
+  BOOST_CHECK_EQUAL(qname, name);
+  BOOST_CHECK(qtype == QType::A);
+
+  BOOST_CHECK(handleEDNSClientSubnet(packet, sizeof packet, consumed, &len, ednsAdded, ecsAdded, true, newECSOption, false));
+  BOOST_CHECK((size_t) len > query.size());
+  BOOST_CHECK_EQUAL(ednsAdded, true);
+  BOOST_CHECK_EQUAL(ecsAdded, true);
+  /* the MOADNSParser does not allow anything except XPF after a TSIG */
+  BOOST_CHECK_THROW(validateQuery(packet, len, true, false, 1), MOADNSException);
+  validateECS(packet, len, remote);
+
+  /* not large enough packet */
+  ednsAdded = false;
+  ecsAdded = false;
+  consumed = 0;
+  len = query.size();
+  qname = DNSName(reinterpret_cast<char*>(query.data()), len, sizeof(dnsheader), false, &qtype, NULL, &consumed);
+  BOOST_CHECK_EQUAL(qname, name);
+  BOOST_CHECK(qtype == QType::A);
+
+  BOOST_CHECK(!handleEDNSClientSubnet(reinterpret_cast<char*>(query.data()), query.size(), consumed, &len, ednsAdded, ecsAdded, true, newECSOption, false));
+  BOOST_CHECK_EQUAL((size_t) len, query.size());
+  BOOST_CHECK_EQUAL(ednsAdded, false);
+  BOOST_CHECK_EQUAL(ecsAdded, false);
+  validateQuery(reinterpret_cast<char*>(query.data()), len, true, false);
+}
+
+
 BOOST_AUTO_TEST_CASE(removeEDNSWhenFirst) {
   DNSName name("www.powerdns.com.");
 
@@ -1588,4 +1915,74 @@ BOOST_AUTO_TEST_CASE(test_isEDNSOptionInOpt) {
   }
 }
 
+BOOST_AUTO_TEST_CASE(test_setNegativeAndAdditionalSOA) {
+  struct timespec queryTime;
+  gettime(&queryTime);  // does not have to be accurate ("realTime") in tests
+  ComboAddress remote;
+  DNSName name("www.powerdns.com.");
+
+  vector<uint8_t> query;
+  DNSPacketWriter pw(query, name, QType::A, QClass::IN, 0);
+  pw.getHeader()->rd = 1;
+  const uint16_t len = query.size();
+
+  /* test NXD */
+  {
+    char packet[1500];
+    memcpy(packet, query.data(), query.size());
+
+    unsigned int consumed = 0;
+    uint16_t qtype;
+    DNSName qname(packet, len, sizeof(dnsheader), false, &qtype, nullptr, &consumed);
+    auto dh = reinterpret_cast<dnsheader*>(packet);
+    DNSQuestion dq(&qname, qtype, QClass::IN, qname.wirelength(), &remote, &remote, dh, sizeof(packet), query.size(), false, &queryTime);
+
+    BOOST_CHECK(setNegativeAndAdditionalSOA(dq, true, DNSName("zone."), 42, DNSName("mname."), DNSName("rname."), 1, 2, 3, 4 , 5));
+    BOOST_CHECK(static_cast<size_t>(dq.len) > query.size());
+    MOADNSParser mdp(true, packet, dq.len);
+
+    BOOST_CHECK_EQUAL(mdp.d_qname.toString(), "www.powerdns.com.");
+    BOOST_CHECK_EQUAL(mdp.d_header.rcode, RCode::NXDomain);
+    BOOST_CHECK_EQUAL(mdp.d_header.qdcount, 1);
+    BOOST_CHECK_EQUAL(mdp.d_header.ancount, 0);
+    BOOST_CHECK_EQUAL(mdp.d_header.nscount, 0);
+    BOOST_CHECK_EQUAL(mdp.d_header.arcount, 2);
+    BOOST_REQUIRE_EQUAL(mdp.d_answers.size(), 2);
+    BOOST_CHECK_EQUAL(mdp.d_answers.at(0).first.d_type, static_cast<uint16_t>(QType::SOA));
+    BOOST_CHECK_EQUAL(mdp.d_answers.at(0).first.d_class, QClass::IN);
+    BOOST_CHECK_EQUAL(mdp.d_answers.at(0).first.d_name, DNSName("zone."));
+    BOOST_CHECK_EQUAL(mdp.d_answers.at(1).first.d_type, static_cast<uint16_t>(QType::OPT));
+    BOOST_CHECK_EQUAL(mdp.d_answers.at(1).first.d_name, g_rootdnsname);
+  }
+
+  /* test No Data */
+  {
+    char packet[1500];
+    memcpy(packet, query.data(), query.size());
+
+    unsigned int consumed = 0;
+    uint16_t qtype;
+    DNSName qname(packet, len, sizeof(dnsheader), false, &qtype, nullptr, &consumed);
+    auto dh = reinterpret_cast<dnsheader*>(packet);
+    DNSQuestion dq(&qname, qtype, QClass::IN, qname.wirelength(), &remote, &remote, dh, sizeof(packet), query.size(), false, &queryTime);
+
+    BOOST_CHECK(setNegativeAndAdditionalSOA(dq, false, DNSName("zone."), 42, DNSName("mname."), DNSName("rname."), 1, 2, 3, 4 , 5));
+    BOOST_CHECK(static_cast<size_t>(dq.len) > query.size());
+    MOADNSParser mdp(true, packet, dq.len);
+
+    BOOST_CHECK_EQUAL(mdp.d_qname.toString(), "www.powerdns.com.");
+    BOOST_CHECK_EQUAL(mdp.d_header.rcode, RCode::NoError);
+    BOOST_CHECK_EQUAL(mdp.d_header.qdcount, 1);
+    BOOST_CHECK_EQUAL(mdp.d_header.ancount, 0);
+    BOOST_CHECK_EQUAL(mdp.d_header.nscount, 0);
+    BOOST_CHECK_EQUAL(mdp.d_header.arcount, 2);
+    BOOST_REQUIRE_EQUAL(mdp.d_answers.size(), 2);
+    BOOST_CHECK_EQUAL(mdp.d_answers.at(0).first.d_type, static_cast<uint16_t>(QType::SOA));
+    BOOST_CHECK_EQUAL(mdp.d_answers.at(0).first.d_class, QClass::IN);
+    BOOST_CHECK_EQUAL(mdp.d_answers.at(0).first.d_name, DNSName("zone."));
+    BOOST_CHECK_EQUAL(mdp.d_answers.at(1).first.d_type, static_cast<uint16_t>(QType::OPT));
+    BOOST_CHECK_EQUAL(mdp.d_answers.at(1).first.d_name, g_rootdnsname);
+  }
+}
+
 BOOST_AUTO_TEST_SUITE_END();
index 54dde00791283a1525a55aec8e4e94d5f73db71d..64c87efa33a189e48a521689d62773a72c07be45 100644 (file)
@@ -248,25 +248,28 @@ void apiServerStatistics(HttpRequest* req, HttpResponse* resp) {
   }
 
 #ifndef RECURSOR
-  for(const auto& ringName : S.listRings()) {
-    Json::array values;
-    const auto& ring = S.getRing(ringName);
-    for(const auto& item : ring) {
-      if (item.second == 0)
-        continue;
-
-      values.push_back(Json::object {
-        { "name", item.first },
-        { "value", std::to_string(item.second) },
+  if (!req->getvars.count("includerings") ||
+       req->getvars["includerings"] != "false") {
+    for(const auto& ringName : S.listRings()) {
+      Json::array values;
+      const auto& ring = S.getRing(ringName);
+      for(const auto& item : ring) {
+        if (item.second == 0)
+          continue;
+
+        values.push_back(Json::object {
+          { "name", item.first },
+          { "value", std::to_string(item.second) },
+        });
+      }
+
+      doc.push_back(Json::object {
+        { "type", "RingStatisticItem" },
+        { "name", ringName },
+        { "size", std::to_string(S.getRingSize(ringName)) },
+        { "value", values },
       });
     }
-
-    doc.push_back(Json::object {
-      { "type", "RingStatisticItem" },
-      { "name", ringName },
-      { "size", std::to_string(S.getRingSize(ringName)) },
-      { "value", values },
-    });
   }
 #endif
 
index 01eb5332edab98a2dc12818aba7df39f7b7a6ce4..8765ea58445658ea0ac95288b08f19d6933c98f8 100644 (file)
@@ -552,6 +552,9 @@ class DNSDistTest(AssertEqualDNSMessageMixin, unittest.TestCase):
         if withCookies:
             for option in received.options:
                 self.assertEquals(option.otype, 10)
+        else:
+            for option in received.options:
+                self.assertNotEquals(option.otype, 10)
 
     def checkMessageEDNSWithECS(self, expected, received, additionalOptions=0):
         self.assertEquals(expected, received)
index 178e79083d8652b273520c509d1018122c0fa25d..0928c28c168a489262848d00376cf0283c571c3e 100644 (file)
@@ -234,8 +234,9 @@ class TestAPIBasics(DNSDistTest):
                     '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']
+                    'cache-misses', 'cpu-iowait', 'cpu-steal', 'cpu-sys-msec', 'cpu-user-msec', 'fd-usage', 'dyn-blocked',
+                    'dyn-block-nmg-size', 'rule-servfail', 'security-status',
+                    'udp-in-errors', 'udp-noport-errors', 'udp-recvbuf-errors', 'udp-sndbuf-errors']
 
         for key in expected:
             self.assertIn(key, values)
index 6be7d4f3c0c55488df0740ab62c51ff94a5abc6e..ba1a67ac09a17ee83145236c3f30e15ce8afc775 100644 (file)
@@ -1842,3 +1842,53 @@ class TestAdvancedContinueAction(DNSDistTest):
             print(receivedResponse)
             print(expectedResponse)
             self.assertEquals(receivedResponse, expectedResponse)
+
+class TestAdvancedSetNegativeAndSOA(DNSDistTest):
+
+    _config_template = """
+    addAction("nxd.setnegativeandsoa.advanced.tests.powerdns.com.", SetNegativeAndSOAAction(true, "auth.", 42, "mname", "rname", 5, 4, 3, 2, 1))
+    addAction("nodata.setnegativeandsoa.advanced.tests.powerdns.com.", SetNegativeAndSOAAction(false, "another-auth.", 42, "mname", "rname", 1, 2, 3, 4, 5))
+    newServer{address="127.0.0.1:%s"}
+    """
+
+    def testAdvancedNegativeAndSOANXD(self):
+        """
+        Advanced: SetNegativeAndSOAAction NXD
+        """
+        name = 'nxd.setnegativeandsoa.advanced.tests.powerdns.com.'
+        query = dns.message.make_query(name, 'A', 'IN')
+        query.flags &= ~dns.flags.RD
+        expectedResponse = dns.message.make_response(query)
+        expectedResponse.set_rcode(dns.rcode.NXDOMAIN)
+        soa = dns.rrset.from_text("auth",
+                                  42,
+                                  dns.rdataclass.IN,
+                                  dns.rdatatype.SOA,
+                                  'mname. rname. 5 4 3 2 1')
+        expectedResponse.additional.append(soa)
+
+        for method in ("sendUDPQuery", "sendTCPQuery"):
+            sender = getattr(self, method)
+            (_, receivedResponse) = sender(query, response=None, useQueue=False)
+            self.assertEquals(receivedResponse, expectedResponse)
+
+    def testAdvancedNegativeAndSOANoData(self):
+        """
+        Advanced: SetNegativeAndSOAAction NoData
+        """
+        name = 'nodata.setnegativeandsoa.advanced.tests.powerdns.com.'
+        query = dns.message.make_query(name, 'A', 'IN')
+        query.flags &= ~dns.flags.RD
+        expectedResponse = dns.message.make_response(query)
+        expectedResponse.set_rcode(dns.rcode.NOERROR)
+        soa = dns.rrset.from_text("another-auth",
+                                  42,
+                                  dns.rdataclass.IN,
+                                  dns.rdatatype.SOA,
+                                  'mname. rname. 1 2 3 4 5')
+        expectedResponse.additional.append(soa)
+
+        for method in ("sendUDPQuery", "sendTCPQuery"):
+            sender = getattr(self, method)
+            (_, receivedResponse) = sender(query, response=None, useQueue=False)
+            self.assertEquals(receivedResponse, expectedResponse)
index bb27fde2861e192435a9b3eecaf2de0f24278c0b..330382c91727c597939c851d7931087ff7226b21 100644 (file)
@@ -2,6 +2,8 @@
 import base64
 import dns
 import os
+import re
+import time
 import unittest
 import clientsubnetoption
 from dnsdisttests import DNSDistTest
@@ -110,6 +112,21 @@ class DNSDistDOHTest(DNSDistTest):
         cls._response_headers = response_headers.getvalue()
         return (receivedQuery, message)
 
+    def getHeaderValue(self, name):
+        for header in self._response_headers.decode().splitlines(False):
+            values = header.split(':')
+            key = values[0]
+            if key.lower() == name.lower():
+                return values[1].strip()
+        return None
+
+    def checkHasHeader(self, name, value):
+        got = self.getHeaderValue(name)
+        self.assertEquals(got, value)
+
+    def checkNoHeader(self, name):
+        self.checkHasHeader(name, None)
+
     @classmethod
     def setUpClass(cls):
 
@@ -226,8 +243,10 @@ class TestDOH(DNSDistDOHTest):
         self.assertTrue((self._customResponseHeader2) in self._response_headers.decode())
         self.assertFalse(('UPPERCASE: VaLuE' in self._response_headers.decode()))
         self.assertTrue(('uppercase: VaLuE' in self._response_headers.decode()))
+        self.assertTrue(('cache-control: max-age=3600' in self._response_headers.decode()))
         self.checkQueryEDNSWithoutECS(expectedQuery, receivedQuery)
         self.assertEquals(response, receivedResponse)
+        self.checkHasHeader('cache-control', 'max-age=3600')
 
     def testDOHSimplePOST(self):
         """
@@ -842,7 +861,56 @@ class TestDOHWithCache(DNSDistDOHTest):
         self.assertEquals(expectedQuery, receivedQuery)
         self.checkQueryEDNSWithoutECS(expectedQuery, receivedQuery)
         self.assertEquals(response, receivedResponse)
+        self.checkHasHeader('cache-control', 'max-age=3600')
 
         for _ in range(numberOfQueries):
             (_, receivedResponse) = self.sendDOHQuery(self._dohServerPort, self._serverName, self._dohBaseURL, query, caFile=self._caCert, useQueue=False)
             self.assertEquals(receivedResponse, response)
+            self.checkHasHeader('cache-control', 'max-age=' + str(receivedResponse.answer[0].ttl))
+
+        time.sleep(1)
+
+        (_, receivedResponse) = self.sendDOHQuery(self._dohServerPort, self._serverName, self._dohBaseURL, query, caFile=self._caCert, useQueue=False)
+        self.assertEquals(receivedResponse, response)
+        self.checkHasHeader('cache-control', 'max-age=' + str(receivedResponse.answer[0].ttl))
+
+class TestDOHWithoutCacheControl(DNSDistDOHTest):
+
+    _serverKey = 'server.key'
+    _serverCert = 'server.chain'
+    _serverName = 'tls.tests.dnsdist.org'
+    _caCert = 'ca.pem'
+    _dohServerPort = 8443
+    _dohBaseURL = ("https://%s:%d/" % (_serverName, _dohServerPort))
+    _config_template = """
+    newServer{address="127.0.0.1:%s"}
+
+    addDOHLocal("127.0.0.1:%s", "%s", "%s", { "/" }, {sendCacheControlHeaders=false})
+    """
+    _config_params = ['_testServerPort', '_dohServerPort', '_serverCert', '_serverKey']
+
+    def testDOHSimple(self):
+        """
+        DOH without cache-control
+        """
+        name = 'simple.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.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.checkNoHeader('cache-control')
+        self.checkQueryEDNSWithoutECS(expectedQuery, receivedQuery)
+        self.assertEquals(response, receivedResponse)
index 241783fba29efbed8ebf1f6e0645a026bd261765..efa909df5c3f85c42cfe2dfbe2c52eb1459e6428 100644 (file)
@@ -204,6 +204,7 @@ class TestEdnsClientSubnetNoOverride(DNSDistTest):
         ecsoResponse = clientsubnetoption.ClientSubnetOption('127.0.0.1', 24, scope=24)
         response.use_edns(edns=True, payload=4096, options=[ecoResponse, ecsoResponse])
         expectedResponse = dns.message.make_response(query)
+        expectedResponse.use_edns(edns=True, payload=4096, options=[ecoResponse])
         rrset = dns.rrset.from_text(name,
                                     3600,
                                     dns.rdataclass.IN,
@@ -242,6 +243,7 @@ class TestEdnsClientSubnetNoOverride(DNSDistTest):
         ecsoResponse = clientsubnetoption.ClientSubnetOption('127.0.0.1', 24, scope=24)
         response.use_edns(edns=True, payload=4096, options=[ecsoResponse, ecoResponse])
         expectedResponse = dns.message.make_response(query, our_payload=4096)
+        expectedResponse.use_edns(edns=True, payload=4096, options=[ecoResponse])
         rrset = dns.rrset.from_text(name,
                                     3600,
                                     dns.rdataclass.IN,
@@ -280,6 +282,7 @@ class TestEdnsClientSubnetNoOverride(DNSDistTest):
         ecsoResponse = clientsubnetoption.ClientSubnetOption('127.0.0.1', 24, scope=24)
         response.use_edns(edns=True, payload=4096, options=[ecoResponse, ecsoResponse, ecoResponse])
         expectedResponse = dns.message.make_response(query, our_payload=4096)
+        expectedResponse.use_edns(edns=True, payload=4096, options=[ecoResponse, ecoResponse])
         rrset = dns.rrset.from_text(name,
                                     3600,
                                     dns.rdataclass.IN,
@@ -482,6 +485,181 @@ class TestEdnsClientSubnetOverride(DNSDistTest):
             self.checkQueryEDNSWithECS(expectedQuery, receivedQuery)
             self.checkResponseEDNSWithECS(response, receivedResponse)
 
+    def testWithECSFollowedByAnother(self):
+        """
+        ECS: Existing EDNS with ECS, followed by another record
+
+        Send a query with EDNS and an existing ECS value.
+        The OPT record is not the last one in the query
+        and is followed by another one.
+        Check that the query received by the responder
+        has a valid ECS value and that the response
+        received from dnsdist contains an EDNS pseudo-RR.
+        """
+        name = 'withecs-followedbyanother.ecs.tests.powerdns.com.'
+        ecso = clientsubnetoption.ClientSubnetOption('192.0.2.1', 24)
+        eco = cookiesoption.CookiesOption(b'deadbeef', b'deadbeef')
+        rewrittenEcso = clientsubnetoption.ClientSubnetOption('127.0.0.1', 24)
+        rrset = dns.rrset.from_text(name,
+                                    3600,
+                                    dns.rdataclass.IN,
+                                    dns.rdatatype.A,
+                                    '127.0.0.1')
+
+        query = dns.message.make_query(name, 'A', 'IN', use_edns=True, payload=4096, options=[eco,ecso,eco])
+        # I would have loved to use a TSIG here but I can't find how to make dnspython ignore
+        # it while parsing the message in the receiver :-/
+        query.additional.append(rrset)
+        expectedQuery = dns.message.make_query(name, 'A', 'IN', use_edns=True, payload=4096, options=[eco,eco,rewrittenEcso])
+        expectedQuery.additional.append(rrset)
+
+        response = dns.message.make_response(expectedQuery)
+        response.use_edns(edns=True, payload=4096, options=[eco, ecso, eco])
+        expectedResponse = dns.message.make_response(query)
+        expectedResponse.use_edns(edns=True, payload=4096, options=[eco, ecso, eco])
+        response.answer.append(rrset)
+        response.additional.append(rrset)
+        expectedResponse.answer.append(rrset)
+        expectedResponse.additional.append(rrset)
+
+        for method in ("sendUDPQuery", "sendTCPQuery"):
+            sender = getattr(self, method)
+            (receivedQuery, receivedResponse) = sender(query, response)
+            self.assertTrue(receivedQuery)
+            self.assertTrue(receivedResponse)
+            receivedQuery.id = expectedQuery.id
+            self.checkQueryEDNSWithECS(expectedQuery, receivedQuery, 2)
+            self.checkResponseEDNSWithECS(expectedResponse, receivedResponse, 2)
+
+    def testWithAnswerThenECS(self):
+        """
+        ECS: Record in answer followed by an existing EDNS with ECS
+
+        Send a query with a record in the answer section, EDNS and an existing ECS value.
+        Check that the query received by the responder
+        has a valid ECS value and that the response
+        received from dnsdist contains an EDNS pseudo-RR.
+        """
+        name = 'record-in-an-withecs.ecs.tests.powerdns.com.'
+        ecso = clientsubnetoption.ClientSubnetOption('192.0.2.1', 24)
+        eco = cookiesoption.CookiesOption(b'deadbeef', b'deadbeef')
+        rewrittenEcso = clientsubnetoption.ClientSubnetOption('127.0.0.1', 24)
+        rrset = dns.rrset.from_text(name,
+                                    3600,
+                                    dns.rdataclass.IN,
+                                    dns.rdatatype.A,
+                                    '127.0.0.1')
+
+        query = dns.message.make_query(name, 'A', 'IN', use_edns=True, payload=4096, options=[eco,ecso,eco])
+        query.answer.append(rrset)
+        expectedQuery = dns.message.make_query(name, 'A', 'IN', use_edns=True, payload=4096, options=[eco,eco,rewrittenEcso])
+        expectedQuery.answer.append(rrset)
+
+        response = dns.message.make_response(expectedQuery)
+        response.use_edns(edns=True, payload=4096, options=[eco, ecso, eco])
+        expectedResponse = dns.message.make_response(query)
+        expectedResponse.use_edns(edns=True, payload=4096, options=[eco, ecso, eco])
+        response.answer.append(rrset)
+        response.additional.append(rrset)
+        expectedResponse.answer.append(rrset)
+        expectedResponse.additional.append(rrset)
+
+        for method in ("sendUDPQuery", "sendTCPQuery"):
+            sender = getattr(self, method)
+            (receivedQuery, receivedResponse) = sender(query, response)
+            self.assertTrue(receivedQuery)
+            self.assertTrue(receivedResponse)
+            receivedQuery.id = expectedQuery.id
+            self.checkQueryEDNSWithECS(expectedQuery, receivedQuery, 2)
+            self.checkResponseEDNSWithECS(expectedResponse, receivedResponse, 2)
+
+    def testWithAuthThenECS(self):
+        """
+        ECS: Record in authority followed by an existing EDNS with ECS
+
+        Send a query with a record in the authority section, EDNS and an existing ECS value.
+        Check that the query received by the responder
+        has a valid ECS value and that the response
+        received from dnsdist contains an EDNS pseudo-RR.
+        """
+        name = 'record-in-an-withecs.ecs.tests.powerdns.com.'
+        ecso = clientsubnetoption.ClientSubnetOption('192.0.2.1', 24)
+        eco = cookiesoption.CookiesOption(b'deadbeef', b'deadbeef')
+        rewrittenEcso = clientsubnetoption.ClientSubnetOption('127.0.0.1', 24)
+        rrset = dns.rrset.from_text(name,
+                                    3600,
+                                    dns.rdataclass.IN,
+                                    dns.rdatatype.A,
+                                    '127.0.0.1')
+
+        query = dns.message.make_query(name, 'A', 'IN', use_edns=True, payload=4096, options=[eco,ecso,eco])
+        query.authority.append(rrset)
+        expectedQuery = dns.message.make_query(name, 'A', 'IN', use_edns=True, payload=4096, options=[eco,eco,rewrittenEcso])
+        expectedQuery.authority.append(rrset)
+
+        response = dns.message.make_response(expectedQuery)
+        response.use_edns(edns=True, payload=4096, options=[eco, ecso, eco])
+        expectedResponse = dns.message.make_response(query)
+        expectedResponse.use_edns(edns=True, payload=4096, options=[eco, ecso, eco])
+        response.answer.append(rrset)
+        response.additional.append(rrset)
+        expectedResponse.answer.append(rrset)
+        expectedResponse.additional.append(rrset)
+
+        for method in ("sendUDPQuery", "sendTCPQuery"):
+            sender = getattr(self, method)
+            (receivedQuery, receivedResponse) = sender(query, response)
+            self.assertTrue(receivedQuery)
+            self.assertTrue(receivedResponse)
+            receivedQuery.id = expectedQuery.id
+            self.checkQueryEDNSWithECS(expectedQuery, receivedQuery, 2)
+            self.checkResponseEDNSWithECS(expectedResponse, receivedResponse, 2)
+
+    def testWithEDNSNoECSFollowedByAnother(self):
+        """
+        ECS: Existing EDNS without ECS, followed by another record
+
+        Send a query with EDNS but no ECS value.
+        The OPT record is not the last one in the query
+        and is followed by another one.
+        Check that the query received by the responder
+        has a valid ECS value and that the response
+        received from dnsdist contains an EDNS pseudo-RR.
+        """
+        name = 'withedns-no-ecs-followedbyanother.ecs.tests.powerdns.com.'
+        eco = cookiesoption.CookiesOption(b'deadbeef', b'deadbeef')
+        rewrittenEcso = clientsubnetoption.ClientSubnetOption('127.0.0.1', 24)
+        rrset = dns.rrset.from_text(name,
+                                    3600,
+                                    dns.rdataclass.IN,
+                                    dns.rdatatype.A,
+                                    '127.0.0.1')
+
+        query = dns.message.make_query(name, 'A', 'IN', use_edns=True, payload=4096, options=[eco])
+        # I would have loved to use a TSIG here but I can't find how to make dnspython ignore
+        # it while parsing the message in the receiver :-/
+        query.additional.append(rrset)
+        expectedQuery = dns.message.make_query(name, 'A', 'IN', use_edns=True, payload=4096, options=[eco,rewrittenEcso])
+        expectedQuery.additional.append(rrset)
+
+        response = dns.message.make_response(expectedQuery)
+        response.use_edns(edns=True, payload=4096, options=[eco, rewrittenEcso, eco])
+        expectedResponse = dns.message.make_response(query)
+        expectedResponse.use_edns(edns=True, payload=4096, options=[eco, eco])
+        response.answer.append(rrset)
+        response.additional.append(rrset)
+        expectedResponse.answer.append(rrset)
+        expectedResponse.additional.append(rrset)
+
+        for method in ("sendUDPQuery", "sendTCPQuery"):
+            sender = getattr(self, method)
+            (receivedQuery, receivedResponse) = sender(query, response)
+            self.assertTrue(receivedQuery)
+            self.assertTrue(receivedResponse)
+            receivedQuery.id = expectedQuery.id
+            self.checkQueryEDNSWithECS(expectedQuery, receivedQuery, 1)
+            self.checkResponseEDNSWithoutECS(expectedResponse, receivedResponse, 2)
+
 class TestECSDisabledByRuleOrLua(DNSDistTest):
     """
     dnsdist is configured to add the EDNS0 Client Subnet
index 2ae03bf8668a178a0b846bdd8c9d5ab3b6102567..fa81ccf263a8c1edb2196914e2fa9a0dfcf7a3c1 100644 (file)
@@ -12,6 +12,9 @@ class TestSpoofingSpoof(DNSDistTest):
     addAction(makeRule("spoofaction-nora.spoofing.tests.powerdns.com."), SpoofAction("192.0.2.1", "2001:DB8::1", {ra=false}))
     addAction(makeRule("cnamespoofaction.spoofing.tests.powerdns.com."), SpoofCNAMEAction("cnameaction.spoofing.tests.powerdns.com."))
     addAction("multispoof.spoofing.tests.powerdns.com", SpoofAction({"192.0.2.1", "192.0.2.2", "2001:DB8::1", "2001:DB8::2"}))
+    addAction(AndRule{makeRule("raw.spoofing.tests.powerdns.com"), QTypeRule(DNSQType.A)}, SpoofRawAction("\\192\\000\\002\\001"))
+    addAction(AndRule{makeRule("raw.spoofing.tests.powerdns.com"), QTypeRule(DNSQType.TXT)}, SpoofRawAction("\\003aaa\\004bbbb\\011ccccccccccc"))
+    addAction(AndRule{makeRule("raw.spoofing.tests.powerdns.com"), QTypeRule(DNSQType.SRV)}, SpoofRawAction("\\000\\000\\000\\000\\255\\255\\003srv\\008powerdns\\003com\\000", { aa=true, ttl=3600 }))
     newServer{address="127.0.0.1:%s"}
     """
 
@@ -195,6 +198,7 @@ class TestSpoofingSpoof(DNSDistTest):
             (_, receivedResponse) = sender(query, response=None, useQueue=False)
             self.assertTrue(receivedResponse)
             self.assertEquals(expectedResponse, receivedResponse)
+            self.assertEquals(receivedResponse.answer[0].ttl, 60)
 
     def testSpoofActionSetAD(self):
         """
@@ -218,6 +222,7 @@ class TestSpoofingSpoof(DNSDistTest):
             (_, receivedResponse) = sender(query, response=None, useQueue=False)
             self.assertTrue(receivedResponse)
             self.assertEquals(expectedResponse, receivedResponse)
+            self.assertEquals(receivedResponse.answer[0].ttl, 60)
 
     def testSpoofActionSetRA(self):
         """
@@ -241,6 +246,7 @@ class TestSpoofingSpoof(DNSDistTest):
             (_, receivedResponse) = sender(query, response=None, useQueue=False)
             self.assertTrue(receivedResponse)
             self.assertEquals(expectedResponse, receivedResponse)
+            self.assertEquals(receivedResponse.answer[0].ttl, 60)
 
     def testSpoofActionSetNoRA(self):
         """
@@ -262,6 +268,71 @@ class TestSpoofingSpoof(DNSDistTest):
             (_, receivedResponse) = sender(query, response=None, useQueue=False)
             self.assertTrue(receivedResponse)
             self.assertEquals(expectedResponse, receivedResponse)
+            self.assertEquals(receivedResponse.answer[0].ttl, 60)
+
+    def testSpoofRawAction(self):
+        """
+        Spoofing: Spoof a response from raw bytes
+        """
+        name = 'raw.spoofing.tests.powerdns.com.'
+
+        # A
+        query = dns.message.make_query(name, 'A', 'IN')
+        query.flags &= ~dns.flags.RD
+        expectedResponse = dns.message.make_response(query)
+        expectedResponse.flags &= ~dns.flags.AA
+        rrset = dns.rrset.from_text(name,
+                                    60,
+                                    dns.rdataclass.IN,
+                                    dns.rdatatype.A,
+                                    '192.0.2.1')
+        expectedResponse.answer.append(rrset)
+
+        for method in ("sendUDPQuery", "sendTCPQuery"):
+            sender = getattr(self, method)
+            (_, receivedResponse) = sender(query, response=None, useQueue=False)
+            self.assertTrue(receivedResponse)
+            self.assertEquals(expectedResponse, receivedResponse)
+            self.assertEquals(receivedResponse.answer[0].ttl, 60)
+
+        # TXT
+        query = dns.message.make_query(name, 'TXT', 'IN')
+        query.flags &= ~dns.flags.RD
+        expectedResponse = dns.message.make_response(query)
+        expectedResponse.flags &= ~dns.flags.AA
+        rrset = dns.rrset.from_text(name,
+                                    60,
+                                    dns.rdataclass.IN,
+                                    dns.rdatatype.TXT,
+                                    '"aaa" "bbbb" "ccccccccccc"')
+        expectedResponse.answer.append(rrset)
+
+        for method in ("sendUDPQuery", "sendTCPQuery"):
+            sender = getattr(self, method)
+            (_, receivedResponse) = sender(query, response=None, useQueue=False)
+            self.assertTrue(receivedResponse)
+            self.assertEquals(expectedResponse, receivedResponse)
+            self.assertEquals(receivedResponse.answer[0].ttl, 60)
+
+        # SRV
+        query = dns.message.make_query(name, 'SRV', 'IN')
+        query.flags &= ~dns.flags.RD
+        expectedResponse = dns.message.make_response(query)
+        # this one should have the AA flag set
+        expectedResponse.flags |= dns.flags.AA
+        rrset = dns.rrset.from_text(name,
+                                    3600,
+                                    dns.rdataclass.IN,
+                                    dns.rdatatype.SRV,
+                                    '0 0 65535 srv.powerdns.com.')
+        expectedResponse.answer.append(rrset)
+
+        for method in ("sendUDPQuery", "sendTCPQuery"):
+            sender = getattr(self, method)
+            (_, receivedResponse) = sender(query, response=None, useQueue=False)
+            self.assertTrue(receivedResponse)
+            self.assertEquals(expectedResponse, receivedResponse)
+            self.assertEquals(receivedResponse.answer[0].ttl, 3600)
 
 class TestSpoofingLuaSpoof(DNSDistTest):
 
@@ -277,11 +348,29 @@ class TestSpoofingLuaSpoof(DNSDistTest):
                 return DNSAction.None, ""
         end
     end
+
     function spoof2rule(dq)
         return DNSAction.Spoof, "spoofedcname.spoofing.tests.powerdns.com."
     end
+
+    addAction(AndRule{makeRule("raw.spoofing.tests.powerdns.com"), QTypeRule(DNSQType.TXT)}, SpoofRawAction("\\003aaa\\004bbbb\\011ccccccccccc"))
+    addAction(AndRule{makeRule("raw.spoofing.tests.powerdns.com"), QTypeRule(DNSQType.SRV)}, SpoofRawAction("\\000\\000\\000\\000\\255\\255\\003srv\\008powerdns\\003com\\000", { aa=true, ttl=3600 }))
+
+    function spoofrawrule(dq)
+        if dq.qtype == DNSQType.A then
+             return DNSAction.SpoofRaw, "\\192\\000\\002\\001"
+        elseif dq.qtype == DNSQType.TXT then
+             return DNSAction.SpoofRaw, "\\003aaa\\004bbbb\\011ccccccccccc"
+        elseif dq.qtype == DNSQType.SRV then
+            dq.dh:setAA(true)
+            return DNSAction.SpoofRaw, "\\000\\000\\000\\000\\255\\255\\003srv\\008powerdns\\003com\\000"
+        end
+        return DNSAction.None, ""
+    end
+
     addAction("luaspoof1.spoofing.tests.powerdns.com.", LuaAction(spoof1rule))
     addAction("luaspoof2.spoofing.tests.powerdns.com.", LuaAction(spoof2rule))
+    addAction("lua-raw.spoofing.tests.powerdns.com.", LuaAction(spoofrawrule))
     newServer{address="127.0.0.1:%s"}
     """
 
@@ -385,6 +474,71 @@ class TestSpoofingLuaSpoof(DNSDistTest):
             self.assertTrue(receivedResponse)
             self.assertEquals(expectedResponse, receivedResponse)
 
+    def testLuaSpoofRawAction(self):
+        """
+        Spoofing: Spoof a response from raw bytes via Lua
+        """
+        name = 'lua-raw.spoofing.tests.powerdns.com.'
+
+        # A
+        query = dns.message.make_query(name, 'A', 'IN')
+        query.flags &= ~dns.flags.RD
+        expectedResponse = dns.message.make_response(query)
+        expectedResponse.flags &= ~dns.flags.AA
+        rrset = dns.rrset.from_text(name,
+                                    60,
+                                    dns.rdataclass.IN,
+                                    dns.rdatatype.A,
+                                    '192.0.2.1')
+        expectedResponse.answer.append(rrset)
+
+        for method in ("sendUDPQuery", "sendTCPQuery"):
+            sender = getattr(self, method)
+            (_, receivedResponse) = sender(query, response=None, useQueue=False)
+            self.assertTrue(receivedResponse)
+            self.assertEquals(expectedResponse, receivedResponse)
+            self.assertEquals(receivedResponse.answer[0].ttl, 60)
+
+        # TXT
+        query = dns.message.make_query(name, 'TXT', 'IN')
+        query.flags &= ~dns.flags.RD
+        expectedResponse = dns.message.make_response(query)
+        expectedResponse.flags &= ~dns.flags.AA
+        rrset = dns.rrset.from_text(name,
+                                    60,
+                                    dns.rdataclass.IN,
+                                    dns.rdatatype.TXT,
+                                    '"aaa" "bbbb" "ccccccccccc"')
+        expectedResponse.answer.append(rrset)
+
+        for method in ("sendUDPQuery", "sendTCPQuery"):
+            sender = getattr(self, method)
+            (_, receivedResponse) = sender(query, response=None, useQueue=False)
+            self.assertTrue(receivedResponse)
+            self.assertEquals(expectedResponse, receivedResponse)
+            self.assertEquals(receivedResponse.answer[0].ttl, 60)
+
+        # SRV
+        query = dns.message.make_query(name, 'SRV', 'IN')
+        query.flags &= ~dns.flags.RD
+        expectedResponse = dns.message.make_response(query)
+        # this one should have the AA flag set
+        expectedResponse.flags |= dns.flags.AA
+        rrset = dns.rrset.from_text(name,
+                                    3600,
+                                    dns.rdataclass.IN,
+                                    dns.rdatatype.SRV,
+                                    '0 0 65535 srv.powerdns.com.')
+        expectedResponse.answer.append(rrset)
+
+        for method in ("sendUDPQuery", "sendTCPQuery"):
+            sender = getattr(self, method)
+            (_, receivedResponse) = sender(query, response=None, useQueue=False)
+            self.assertTrue(receivedResponse)
+            self.assertEquals(expectedResponse, receivedResponse)
+            # sorry, we can't set the TTL from the Lua API right now
+            #self.assertEquals(receivedResponse.answer[0].ttl, 3600)
+
 class TestSpoofingLuaWithStatistics(DNSDistTest):
 
     _config_template = """
index bea9c047c37933f9474a0eb9f13cdd25f820792c..9ff54727d7c93d5dd4b2d6d70d09371cb63e49d7 100755 (executable)
@@ -27,7 +27,7 @@ $SDIG ::1 $port example.com SOA >&2 >/dev/null
 $SDIG ::1 $port example.com SOA tcp >&2 >/dev/null
 
 $PDNSCONTROL --config-name= --no-config --socket-dir=./ 'show *' | \
-  tr ',' '\n'| grep -v -E '(user-msec|sys-msec|uptime|udp-noport-errors|udp-in-errors|real-memory-usage|special-memory-usage|udp-recvbuf-errors|udp-sndbuf-errors|-hit|-miss|fd-usage|latency)' | LC_ALL=C sort
+  tr ',' '\n'| grep -v -E '(user-msec|sys-msec|cpu-iowait|cpu-steal|uptime|udp-noport-errors|udp-in-errors|real-memory-usage|special-memory-usage|udp-recvbuf-errors|udp-sndbuf-errors|-hit|-miss|fd-usage|latency)' | LC_ALL=C sort
 
 kill $(cat pdns*.pid)
 rm pdns*.pid
index 48a855dca1507757c86f92ebad69b4ef48ddfe8b..61f487320825623de2ae039b57addc9dba704251 100755 (executable)
@@ -35,12 +35,16 @@ $PDNS --config-dir=default-publish-cds &
 bindwait
 
 $SDIG 127.0.0.1 $port minimal.com CDS dnssec | LC_ALL=C sort
+$SAXFR 127.0.0.1 $port minimal.com dnssec | LC_ALL=C sort
 $PDNSUTIL --config-dir=default-publish-cds set-publish-cds minimal.com 2
 $SDIG 127.0.0.1 $port minimal.com CDS dnssec | LC_ALL=C sort
+$SAXFR 127.0.0.1 $port minimal.com dnssec | LC_ALL=C sort
 $PDNSUTIL --config-dir=default-publish-cds set-publish-cds minimal.com ''
 $SDIG 127.0.0.1 $port minimal.com CDS dnssec | LC_ALL=C sort
+$SAXFR 127.0.0.1 $port minimal.com dnssec | LC_ALL=C sort
 $PDNSUTIL --config-dir=default-publish-cds unset-publish-cds minimal.com
 $SDIG 127.0.0.1 $port minimal.com CDS dnssec | LC_ALL=C sort
+$SAXFR 127.0.0.1 $port minimal.com dnssec | LC_ALL=C sort
 
 kill $(cat pdns*.pid)
 rm pdns*.pid
index b19155390b73a08670433d61d497168978266dc3..25d3c383ef50051b023d2f25ef23990672681f64 100644 (file)
@@ -4,11 +4,35 @@
 2      .       IN      OPT     32768   
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='minimal.com.', qtype=CDS
+minimal.com.   120     IN      NS      ns1.example.com.
+minimal.com.   120     IN      NS      ns2.example.com.
+minimal.com.   120     IN      RRSIG   NS 8 2 120 [expiry] [inception] [keytag] minimal.com. ...
+minimal.com.   120     IN      RRSIG   SOA 8 2 120 [expiry] [inception] [keytag] minimal.com. ...
+minimal.com.   120     IN      SOA     ns1.example.com. ahu.example.com. 2000081501 28800 7200 604800 86400
+minimal.com.   120     IN      SOA     ns1.example.com. ahu.example.com. 2000081501 28800 7200 604800 86400
+minimal.com.   86400   IN      CDS     54319 8 4 ff159f2cc251c9850b24bedb9158f33b292137d228a2a8686c2a178e29e1097f80210813beba035bb065bbe1ffbb2229
+minimal.com.   86400   IN      DNSKEY  257 3 8 ...
+minimal.com.   86400   IN      NSEC    minimal.com. NS SOA RRSIG NSEC DNSKEY CDS
+minimal.com.   86400   IN      RRSIG   CDS 8 2 86400 [expiry] [inception] [keytag] minimal.com. ...
+minimal.com.   86400   IN      RRSIG   DNSKEY 8 2 86400 [expiry] [inception] [keytag] minimal.com. ...
+minimal.com.   86400   IN      RRSIG   NSEC 8 2 86400 [expiry] [inception] [keytag] minimal.com. ...
 0      minimal.com.    IN      CDS     86400   54319 8 2 c5359d2a312ff6c28883b5d6404c76666262c26bd3dadfed63afb366e6f09c24
 0      minimal.com.    IN      RRSIG   86400   CDS 8 2 86400 [expiry] [inception] [keytag] minimal.com. ...
 2      .       IN      OPT     32768   
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='minimal.com.', qtype=CDS
+minimal.com.   120     IN      NS      ns1.example.com.
+minimal.com.   120     IN      NS      ns2.example.com.
+minimal.com.   120     IN      RRSIG   NS 8 2 120 [expiry] [inception] [keytag] minimal.com. ...
+minimal.com.   120     IN      RRSIG   SOA 8 2 120 [expiry] [inception] [keytag] minimal.com. ...
+minimal.com.   120     IN      SOA     ns1.example.com. ahu.example.com. 2000081501 28800 7200 604800 86400
+minimal.com.   120     IN      SOA     ns1.example.com. ahu.example.com. 2000081501 28800 7200 604800 86400
+minimal.com.   86400   IN      CDS     54319 8 2 c5359d2a312ff6c28883b5d6404c76666262c26bd3dadfed63afb366e6f09c24
+minimal.com.   86400   IN      DNSKEY  257 3 8 ...
+minimal.com.   86400   IN      NSEC    minimal.com. NS SOA RRSIG NSEC DNSKEY CDS
+minimal.com.   86400   IN      RRSIG   CDS 8 2 86400 [expiry] [inception] [keytag] minimal.com. ...
+minimal.com.   86400   IN      RRSIG   DNSKEY 8 2 86400 [expiry] [inception] [keytag] minimal.com. ...
+minimal.com.   86400   IN      RRSIG   NSEC 8 2 86400 [expiry] [inception] [keytag] minimal.com. ...
 1      minimal.com.    IN      NSEC    86400   minimal.com. NS SOA RRSIG NSEC DNSKEY
 1      minimal.com.    IN      RRSIG   120     SOA 8 2 120 [expiry] [inception] [keytag] minimal.com. ...
 1      minimal.com.    IN      RRSIG   86400   NSEC 8 2 86400 [expiry] [inception] [keytag] minimal.com. ...
@@ -16,8 +40,30 @@ Reply to question for qname='minimal.com.', qtype=CDS
 2      .       IN      OPT     32768   
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='minimal.com.', qtype=CDS
+minimal.com.   120     IN      NS      ns1.example.com.
+minimal.com.   120     IN      NS      ns2.example.com.
+minimal.com.   120     IN      RRSIG   NS 8 2 120 [expiry] [inception] [keytag] minimal.com. ...
+minimal.com.   120     IN      RRSIG   SOA 8 2 120 [expiry] [inception] [keytag] minimal.com. ...
+minimal.com.   120     IN      SOA     ns1.example.com. ahu.example.com. 2000081501 28800 7200 604800 86400
+minimal.com.   120     IN      SOA     ns1.example.com. ahu.example.com. 2000081501 28800 7200 604800 86400
+minimal.com.   86400   IN      DNSKEY  257 3 8 ...
+minimal.com.   86400   IN      NSEC    minimal.com. NS SOA RRSIG NSEC DNSKEY
+minimal.com.   86400   IN      RRSIG   DNSKEY 8 2 86400 [expiry] [inception] [keytag] minimal.com. ...
+minimal.com.   86400   IN      RRSIG   NSEC 8 2 86400 [expiry] [inception] [keytag] minimal.com. ...
 0      minimal.com.    IN      CDS     86400   54319 8 4 ff159f2cc251c9850b24bedb9158f33b292137d228a2a8686c2a178e29e1097f80210813beba035bb065bbe1ffbb2229
 0      minimal.com.    IN      RRSIG   86400   CDS 8 2 86400 [expiry] [inception] [keytag] minimal.com. ...
 2      .       IN      OPT     32768   
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='minimal.com.', qtype=CDS
+minimal.com.   120     IN      NS      ns1.example.com.
+minimal.com.   120     IN      NS      ns2.example.com.
+minimal.com.   120     IN      RRSIG   NS 8 2 120 [expiry] [inception] [keytag] minimal.com. ...
+minimal.com.   120     IN      RRSIG   SOA 8 2 120 [expiry] [inception] [keytag] minimal.com. ...
+minimal.com.   120     IN      SOA     ns1.example.com. ahu.example.com. 2000081501 28800 7200 604800 86400
+minimal.com.   120     IN      SOA     ns1.example.com. ahu.example.com. 2000081501 28800 7200 604800 86400
+minimal.com.   86400   IN      CDS     54319 8 4 ff159f2cc251c9850b24bedb9158f33b292137d228a2a8686c2a178e29e1097f80210813beba035bb065bbe1ffbb2229
+minimal.com.   86400   IN      DNSKEY  257 3 8 ...
+minimal.com.   86400   IN      NSEC    minimal.com. NS SOA RRSIG NSEC DNSKEY CDS
+minimal.com.   86400   IN      RRSIG   CDS 8 2 86400 [expiry] [inception] [keytag] minimal.com. ...
+minimal.com.   86400   IN      RRSIG   DNSKEY 8 2 86400 [expiry] [inception] [keytag] minimal.com. ...
+minimal.com.   86400   IN      RRSIG   NSEC 8 2 86400 [expiry] [inception] [keytag] minimal.com. ...