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"
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
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
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
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
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)
--with-ebpf \
--with-lua=luajit \
--with-protobuf \
+ --with-service-user='_dnsdist' \
+ --with-service-group='_dnsdist' \
$(CONFIGURE_ARGS)
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
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
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)
--with-ebpf \
--with-lua=luajit \
--with-protobuf \
+ --with-service-user='_dnsdist' \
+ --with-service-group='_dnsdist' \
$(CONFIGURE_ARGS)
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
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
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)
--with-ebpf \
--with-lua=luajit \
--with-protobuf \
+ --with-service-user='_dnsdist' \
+ --with-service-group='_dnsdist' \
$(CONFIGURE_ARGS)
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
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
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
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; \
@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; \
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; \
# 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
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
# 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
# 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
.. 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:
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
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
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
------------------------
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
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.
--------------------------
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
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
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*...]
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-@ 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.
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/"
$ 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
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);
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;
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>");
}
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;
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
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);
+++ /dev/null
-/*
- * This file is part of PowerDNS or dnsdist.
- * Copyright -- PowerDNS.COM B.V. and its contributors
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of version 2 of the GNU General Public License as
- * published by the Free Software Foundation.
- *
- * In addition, for the avoidance of any doubt, permission is granted to
- * link this program with OpenSSL and to (re)distribute the binaries
- * produced as the result of such linking.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-#ifdef HAVE_CONFIG_H
-#include "config.h"
-#endif
-#include "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;
-}
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);
{ "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'" },
{ "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" },
pr.xfrBlob(blob);
pw.xfrBlob(blob);
} else {
+
pr.skip(ah.d_clen);
}
}
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);
}
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;
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,
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;
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;
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;
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);
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;
}
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);
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;
}
{
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))) {
}
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;
}
}
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;
}
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);
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);
}
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();
});
}
-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"]);
}
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));
});
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;
});
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;
+ });
}
}
#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>();
}
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) {
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);
+ });
}
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;
{"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},
}
}
+ if (vars->count("sendCacheControlHeaders")) {
+ frontend->d_sendCacheControlHeaders = boost::get<bool>((*vars)["sendCacheControlHeaders"]);
+ }
+
parseTLSConfig(frontend->d_tlsConfig, "addDOHLocal", vars);
}
g_dohlocals.push_back(frontend);
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);
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()+" ";
}
ResponseConfig d_responseConfig;
private:
std::vector<ComboAddress> d_addrs;
+ std::set<uint16_t> d_types;
+ std::string d_rawResponse;
DNSName d_cname;
};
*/
#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)
{
}
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);
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";
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";
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";
#include <editline/readline.h>
#endif
+#include "dnsdist-systemd.hh"
#ifdef HAVE_SYSTEMD
#include <systemd/sd-daemon.h>
#endif
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};
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);
}
}
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:
}
}
- 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;
}
}
#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());
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
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) {
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:
{"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(); }},
};
};
-// 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);
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 \
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
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.
--- /dev/null
+/*
+ * This file is part of PowerDNS or dnsdist.
+ * Copyright -- PowerDNS.COM B.V. and its contributors
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ *
+ * In addition, for the avoidance of any doubt, permission is granted to
+ * link this program with OpenSSL and to (re)distribute the binaries
+ * produced as the result of such linking.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+#pragma once
+
+// 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;
+};
--- /dev/null
+/*
+ * This file is part of PowerDNS or dnsdist.
+ * Copyright -- PowerDNS.COM B.V. and its contributors
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ *
+ * In addition, for the avoidance of any doubt, permission is granted to
+ * link this program with OpenSSL and to (re)distribute the binaries
+ * produced as the result of such linking.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+#include "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;
+}
--- /dev/null
+/*
+ * This file is part of PowerDNS or dnsdist.
+ * Copyright -- PowerDNS.COM B.V. and its contributors
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ *
+ * In addition, for the avoidance of any doubt, permission is granted to
+ * link this program with OpenSSL and to (re)distribute the binaries
+ * produced as the result of such linking.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+#pragma once
+
+bool running_in_service_mgr();
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
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
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})
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
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()
.. 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.
* ``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])
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
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
* ``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
: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
::
function luarule(dq)
- if(dq.qtype==dnsdist.NAPTR)
+ if(dq.qtype==DNSQType.NAPTR)
then
return DNSAction.Pool, "abuse" -- send to abuse pool
else
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
: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.
* ``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])
* ``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)
------------
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.
--------------
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.
-
}
}
+ 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());
}
}
fprintf(fp, "%s\n", line);
+ fflush(fp);
}
#endif /* HAVE_SSL_CTX_SET_KEYLOG_CALLBACK */
--- /dev/null
+../../../m4/pdns_with_service_user.m4
\ No newline at end of file
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
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));
};
-DNSName::string_t segmentDNSNameRaw(const char* realinput)
+DNSName::string_t segmentDNSNameRaw(const char* realinput, size_t inputlen)
{
%%{
machine dnsnameraw;
return ret;
}
- unsigned int inputlen=strlen(realinput);
ret.reserve(inputlen+1);
const char *p = realinput, *pe = realinput + inputlen;
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);
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; ) {
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");
}
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#pragma once
+#include <cstring>
#include <string>
#include <vector>
#include <set>
{
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
};
}
-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())
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)
{
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
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
HTTPVersionStats d_http1Stats;
HTTPVersionStats d_http2Stats;
+ bool d_sendCacheControlHeaders{true};
time_t getTicketsKeyRotationDelay() const
{
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);
/* 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);
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"))
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)
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")) {
DNSName domain(zoneName);
zone->setDomain(domain);
zone->setName(polName);
- zone->setRefresh(refresh);
zoneIdx = lci.dfe.addZone(zone);
if (!seedFile.empty()) {
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;
{
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) {
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);
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));
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
.. 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.
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
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
================
^^^^^^^^^^^^^^^^^
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
{"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")},
};
};
}
}
+ if (sr != nullptr) {
+ zone->setRefresh(sr->d_st.refresh);
+ }
return sr;
}
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();
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) {
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(;;) {
}
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,
if (!dumpZoneFileName.empty()) {
dumpZoneToDisk(zoneName, newZone, dumpZoneFileName);
}
+ refresh = std::max(refreshFromConf ? refreshFromConf : newZone->getRefresh(), 1U);
}
}
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
{
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;
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);
}
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;
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);
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) */
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);
}
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);
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);
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);
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);
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);
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);
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);
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);
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);
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);
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.");
}
}
+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();
}
#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
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)
'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)
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)
import base64
import dns
import os
+import re
+import time
import unittest
import clientsubnetoption
from dnsdisttests import 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):
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):
"""
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)
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,
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,
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,
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
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"}
"""
(_, receivedResponse) = sender(query, response=None, useQueue=False)
self.assertTrue(receivedResponse)
self.assertEquals(expectedResponse, receivedResponse)
+ self.assertEquals(receivedResponse.answer[0].ttl, 60)
def testSpoofActionSetAD(self):
"""
(_, receivedResponse) = sender(query, response=None, useQueue=False)
self.assertTrue(receivedResponse)
self.assertEquals(expectedResponse, receivedResponse)
+ self.assertEquals(receivedResponse.answer[0].ttl, 60)
def testSpoofActionSetRA(self):
"""
(_, receivedResponse) = sender(query, response=None, useQueue=False)
self.assertTrue(receivedResponse)
self.assertEquals(expectedResponse, receivedResponse)
+ self.assertEquals(receivedResponse.answer[0].ttl, 60)
def testSpoofActionSetNoRA(self):
"""
(_, 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):
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"}
"""
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 = """
$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
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
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. ...
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. ...