diff --git a/.github/actions/spell-check/expect.txt b/.github/actions/spell-check/expect.txt index 0465e6d06b0d..e88447ab9c12 100644 --- a/.github/actions/spell-check/expect.txt +++ b/.github/actions/spell-check/expect.txt @@ -388,6 +388,7 @@ Ekkelenkamp elgoog endbr Enden +enp ent envoutput epel @@ -449,6 +450,7 @@ gaba gacogne gatech Gavarret +Gbps gdpr Geijn genindex @@ -477,6 +479,7 @@ gettime gettsigkey Geuze GFm +Ghz Gibheer Gieben Gillstrom @@ -648,6 +651,7 @@ ktls KTNAME Kuehrer kvs +kxdpgun Ladot Lafon Lakkas @@ -701,6 +705,7 @@ libsodium libsofthsm libsystemd libtdsodbc +libxdp libyaml libzmq lightningstream @@ -861,6 +866,7 @@ NETWORKMASK Neue Neuf newcontent +nftables nic Nilsen nimber @@ -1183,6 +1189,7 @@ Schueler schwer scopebits scopemask +sdfn sdfoijdfio sdig secpoll @@ -1409,6 +1416,7 @@ Ueber Ueli UIDs Uisms +UMEM unauth unbreak unescaping @@ -1513,6 +1521,8 @@ Xiang xorbooter xpf XRecord +xsk +xskmap XXXXXX yahttp yamlconversion diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 38a0d87b5bbc..3a76bef49841 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -147,7 +147,7 @@ jobs: - name: Install dependencies for dnsdist if: matrix.product == 'dnsdist' run: | - inv install-dnsdist-build-deps + inv install-dnsdist-build-deps --skipXDP - name: Autoreconf dnsdist if: matrix.product == 'dnsdist' working-directory: ./pdns/dnsdistdist/ diff --git a/.github/workflows/misc-dailies.yml b/.github/workflows/misc-dailies.yml index 90b84a94c504..ea31204d8ee2 100644 --- a/.github/workflows/misc-dailies.yml +++ b/.github/workflows/misc-dailies.yml @@ -81,7 +81,7 @@ jobs: submodules: recursive - run: build-scripts/gh-actions-setup-inv-no-dist-upgrade - run: inv install-clang - - run: inv install-dnsdist-build-deps + - run: inv install-dnsdist-build-deps --skipXDP - run: inv install-coverity-tools dnsdist - run: inv coverity-clang-configure - run: inv ci-autoconf diff --git a/.not-formatted b/.not-formatted index 759460e7fd5b..050ad15f107f 100644 --- a/.not-formatted +++ b/.not-formatted @@ -36,31 +36,6 @@ ./pdns/dnscrypt.cc ./pdns/dnscrypt.hh ./pdns/dnsdemog.cc -./pdns/dnsdist-cache.cc -./pdns/dnsdist-cache.hh -./pdns/dnsdist-console.cc -./pdns/dnsdist-console.hh -./pdns/dnsdist-dynbpf.cc -./pdns/dnsdist-dynbpf.hh -./pdns/dnsdist-ecs.cc -./pdns/dnsdist-ecs.hh -./pdns/dnsdist-lbpolicies.hh -./pdns/dnsdist-lua-bindings-dnsquestion.cc -./pdns/dnsdist-lua-bindings.cc -./pdns/dnsdist-lua-inspection.cc -./pdns/dnsdist-lua-rules.cc -./pdns/dnsdist-lua-vars.cc -./pdns/dnsdist-lua.hh -./pdns/dnsdist-rings.cc -./pdns/dnsdist-rings.hh -./pdns/dnsdist-snmp.cc -./pdns/dnsdist-snmp.hh -./pdns/dnsdist-tcp.cc -./pdns/dnsdist-web.cc -./pdns/dnsdist-xpf.cc -./pdns/dnsdist-xpf.hh -./pdns/dnsdist.cc -./pdns/dnsdist.hh ./pdns/dnsdistdist/connection-management.hh ./pdns/dnsdistdist/dnsdist-backend.cc ./pdns/dnsdistdist/dnsdist-kvs.cc @@ -232,8 +207,6 @@ ./pdns/test-common.hh ./pdns/test-distributor_hh.cc ./pdns/test-dnscrypt_cc.cc -./pdns/test-dnsdist_cc.cc -./pdns/test-dnsdistpacketcache_cc.cc ./pdns/test-dnsname_cc.cc ./pdns/test-dnsparser_cc.cc ./pdns/test-dnsparser_hh.cc diff --git a/Dockerfile-dnsdist b/Dockerfile-dnsdist index c9cf09a94eef..2b96ab3ef919 100644 --- a/Dockerfile-dnsdist +++ b/Dockerfile-dnsdist @@ -1,5 +1,5 @@ # our chosen base image -FROM debian:11-slim AS builder +FROM debian:12-slim AS builder ENV NO_LUA_JIT="s390x arm64" @@ -14,7 +14,7 @@ RUN apt-get update && apt-get -y dist-upgrade && apt-get install -y --no-instal COPY builder-support /source/builder-support # TODO: control file is not in tarballs at all right now -RUN mk-build-deps -i -t 'apt-get -y -o Debug::pkgProblemResolver=yes --no-install-recommends' /source/builder-support/debian/dnsdist/debian-buster/control && \ +RUN mk-build-deps -i -t 'apt-get -y -o Debug::pkgProblemResolver=yes --no-install-recommends' /source/builder-support/debian/dnsdist/debian-bookworm/control && \ apt-get clean COPY pdns /source/pdns @@ -84,7 +84,7 @@ RUN cd /tmp && mkdir /build/tmp/ && mkdir debian && \ # Runtime -FROM debian:11-slim +FROM debian:12-slim # Reusable layer for base update - Should be cached from builder RUN apt-get update && apt-get -y dist-upgrade && apt-get clean diff --git a/builder-support/debian/dnsdist/debian-bookworm/compat b/builder-support/debian/dnsdist/debian-bookworm/compat new file mode 100644 index 000000000000..f599e28b8ab0 --- /dev/null +++ b/builder-support/debian/dnsdist/debian-bookworm/compat @@ -0,0 +1 @@ +10 diff --git a/builder-support/debian/dnsdist/debian-bookworm/control b/builder-support/debian/dnsdist/debian-bookworm/control new file mode 100644 index 000000000000..023b966cfbec --- /dev/null +++ b/builder-support/debian/dnsdist/debian-bookworm/control @@ -0,0 +1,39 @@ +Source: dnsdist +Section: net +Priority: optional +Maintainer: PowerDNS.COM BV +Uploaders: PowerDNS.COM BV +Build-Depends: debhelper (>= 10), + libboost-all-dev, + libbpf-dev [linux-any], + libcap-dev, + libcdb-dev, + libedit-dev, + libfstrm-dev, + libgnutls28-dev, + liblmdb-dev, + libluajit-5.1-dev [!arm64 !s390x], + liblua5.3-dev [arm64 s390x], + libnghttp2-dev, + libre2-dev, + libsnmp-dev, + libsodium-dev, + libssl-dev, + libsystemd-dev [linux-any], + libwslay-dev, + libxdp-dev [linux-any], + pkg-config, + ragel, + systemd [linux-any] +Standards-Version: 4.1.5 +Homepage: https://dnsdist.org + +Package: dnsdist +Architecture: any +Depends: ${misc:Depends}, + ${shlibs:Depends} +Description: DNS loadbalancer + Highly DoS- and abuse-aware load balancing tool for DNS traffic, + with Lua scripting and configuration capability. + Can be configured to use various sets of rules to classify, route + and reject traffic. diff --git a/builder-support/debian/dnsdist/debian-bookworm/copyright b/builder-support/debian/dnsdist/debian-bookworm/copyright new file mode 100644 index 000000000000..5fbb60206907 --- /dev/null +++ b/builder-support/debian/dnsdist/debian-bookworm/copyright @@ -0,0 +1,284 @@ +Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ +Upstream-Name: dnsdist +Source: https://dnsdist.org + +Files: * +Copyright: 2002-2022 PowerDNS.COM BV and contributors +License: GPL-2 with OpenSSL Exception + +Files: debian/* +Copyright: 2002-2016 PowerDNS.COM BV and contributors + 2016 Chris Hofstaedtler +License: GPL-2 with OpenSSL Exception +Comment: Debian packaging is under same license as upstream code + +Files: ext/json11/* +Copyright: 2013 Dropbox, Inc. +License: Expat + +Files: ext/libbpf/* +Copyright: 2015, 2016 Alexei Starovoitov +License: GPL-2 +Comment: taken from Linux kernel source + +Files: ext/luawrapper/* +Copyright: 2013, Pierre KRIEGER +License: BSD-3 + +Files: ext/yahttp/* +Copyright: 2014 Aki Tuomi +License: Expat + +Files: compile ltmain.sh +Copyright: 1996-2011 Free Software Foundation, Inc. +License: GPL-2+ + +Files: m4/ax_cxx_compile_stdcxx_11.m4 +Copyright: 2008 Benjamin Kosnik + 2012 Zack Weinberg + 2013 Roy Stogner + 2014, 2015 Google Inc.; contributed by Alexey Sokolov +License: free-generic + +Files: m4/boost.m4 +Copyright: 2007-2011, 2014 Benoit Sigoure +License: GPL-3 or Autoconf + +Files: m4/libtool.m4 m4/lt*.m4 +Copyright: 1996-2011 Free Software Foundation, Inc. +License: free-fsf + +Files: m4/systemd.m4 +Copyright: 2014 Luis R. Rodriguez + 2016 Pieter Lexis +License: GPL-2+ + +Files: m4/warnings.m4 +Copyright: 2008-2015 Free Software Foundation, Inc. +License: free-fsf + +Files: m4/pdns_d_fortify_source.m4 m4/pdns_param_ssp_buffer_size.m4 m4/pdns_pie.m4 m4/pdns_relro.m4 m4/pdns_stack_protector.m4 +Copyright: 2013 Red Hat, Inc. +License: LGPL-2.1+ + +Files: src_js/d3.js +Copyright: 2010-2016 Mike Bostock +License: Expat + +Files: src_js/jquery.js +Copyright: JS Foundation and other contributors +License: Expat + +Files: src_js/moment.js +Copyright: JS Foundation and other contributors +License: Expat + +Files: src_js/rickshaw.js +Copyright: 2011-2014 by Shutterstock Images, LLC +License: Expat + +Files: */libdnsdist-quiche.so +Copyright: 2018-2019, Cloudflare, Inc. +License: BSD-2-clause + +License: Unlicense + This is free and unencumbered software released into the public domain. + . + Anyone is free to copy, modify, publish, use, compile, sell, or + distribute this software, either in source code form or as a compiled + binary, for any purpose, commercial or non-commercial, and by any + means. + . + In jurisdictions that recognize copyright laws, the author or authors + of this software dedicate any and all copyright interest in the + software to the public domain. We make this dedication for the benefit + of the public at large and to the detriment of our heirs and + successors. We intend this dedication to be an overt act of + relinquishment in perpetuity of all present and future rights to this + software under copyright law. + . + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR + OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + OTHER DEALINGS IN THE SOFTWARE. + . + For more information, please refer to + +License: GPL-2 with OpenSSL Exception + 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. + . + On Debian systems, the full text of the GNU General Public + License version 2 can be found in the file + `/usr/share/common-licenses/GPL-2'. + +License: Expat + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + . + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + . + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + +License: BSD-2-clause + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + . + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY + DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +License: BSD-3 + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + . + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY + DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +License: LGPL-2.1+ + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + . + This library 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 + Lesser General Public License for more details. + . + You should have received a copy of the GNU Lesser General Public + License along with this library. If not, see + . + . + On Debian systems, the full text of the GNU Lesser General Public + License version 2.1 can be found in the file + `/usr/share/common-licenses/LGPL-2.1'. + +License: GPL-2 + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License. + . + 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. + . + On Debian systems, the full text of the GNU General Public + License version 2 can be found in the file + `/usr/share/common-licenses/GPL-2'. + +License: GPL-2+ + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + . + 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. + . + On Debian systems, the full text of the GNU General Public + License version 2 can be found in the file + `/usr/share/common-licenses/GPL-2'. + +License: GPL-3 or Autoconf + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + . + Additional permission under section 7 of the GNU General Public + License, version 3 ("GPLv3"): + . + If you convey this file as part of a work that contains a + configuration script generated by Autoconf, you may do so under + terms of your choice. + . + 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, see . + . + On Debian systems, the full text of the GNU General Public + License version 3 can be found in the file + `/usr/share/common-licenses/GPL-3'. + +License: free-fsf + This file is free software; the Free Software Foundation gives + unlimited permission to copy and/or distribute it, with or without + modifications, as long as this notice is preserved. + +License: free-generic + Copying and distribution of this file, with or without modification, are + permitted in any medium without royalty provided the copyright notice + and this notice are preserved. This file is offered as-is, without any + warranty. diff --git a/builder-support/debian/dnsdist/debian-bookworm/dnsdist.dirs b/builder-support/debian/dnsdist/debian-bookworm/dnsdist.dirs new file mode 100644 index 000000000000..a97ae06ea1b7 --- /dev/null +++ b/builder-support/debian/dnsdist/debian-bookworm/dnsdist.dirs @@ -0,0 +1 @@ +/etc/dnsdist diff --git a/builder-support/debian/dnsdist/debian-bookworm/dnsdist.examples b/builder-support/debian/dnsdist/debian-bookworm/dnsdist.examples new file mode 100644 index 000000000000..636562bed4d9 --- /dev/null +++ b/builder-support/debian/dnsdist/debian-bookworm/dnsdist.examples @@ -0,0 +1 @@ +dnsdist.conf diff --git a/builder-support/debian/dnsdist/debian-bookworm/dnsdist.postinst b/builder-support/debian/dnsdist/debian-bookworm/dnsdist.postinst new file mode 100644 index 000000000000..8f7a7ce1811d --- /dev/null +++ b/builder-support/debian/dnsdist/debian-bookworm/dnsdist.postinst @@ -0,0 +1,43 @@ +#! /bin/sh + +set -e + +# summary of how this script can be called: +# * `configure' +# * `abort-upgrade' +# * `abort-remove' `in-favour' +# +# * `abort-deconfigure' `in-favour' +# `removing' +# +# for details, see http://www.debian.org/doc/debian-policy/ or +# the debian-policy package + +case "$1" in + configure) + + 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) + ;; + + *) + echo "postinst called with unknown argument \`$1'" >&2 + exit 1 + ;; +esac + +# dh_installdeb will replace this with shell code automatically +# generated by other debhelper scripts. + +#DEBHELPER# + +exit 0 diff --git a/builder-support/debian/dnsdist/debian-bookworm/docs b/builder-support/debian/dnsdist/debian-bookworm/docs new file mode 100644 index 000000000000..b43bf86b50fd --- /dev/null +++ b/builder-support/debian/dnsdist/debian-bookworm/docs @@ -0,0 +1 @@ +README.md diff --git a/builder-support/debian/dnsdist/debian-bookworm/gbp.conf b/builder-support/debian/dnsdist/debian-bookworm/gbp.conf new file mode 100644 index 000000000000..9eee0d42b88e --- /dev/null +++ b/builder-support/debian/dnsdist/debian-bookworm/gbp.conf @@ -0,0 +1,4 @@ +[DEFAULT] +pristine-tar = True +multimaint-merge = True +patch-numbers = False diff --git a/builder-support/debian/dnsdist/debian-bookworm/missing-sources/d3.js b/builder-support/debian/dnsdist/debian-bookworm/missing-sources/d3.js new file mode 120000 index 000000000000..19eca87ffc38 --- /dev/null +++ b/builder-support/debian/dnsdist/debian-bookworm/missing-sources/d3.js @@ -0,0 +1 @@ +../../src_js/d3.js \ No newline at end of file diff --git a/builder-support/debian/dnsdist/debian-bookworm/missing-sources/jquery.js b/builder-support/debian/dnsdist/debian-bookworm/missing-sources/jquery.js new file mode 120000 index 000000000000..a2586f462d14 --- /dev/null +++ b/builder-support/debian/dnsdist/debian-bookworm/missing-sources/jquery.js @@ -0,0 +1 @@ +../../src_js/jquery.js \ No newline at end of file diff --git a/builder-support/debian/dnsdist/debian-bookworm/missing-sources/moment.js b/builder-support/debian/dnsdist/debian-bookworm/missing-sources/moment.js new file mode 120000 index 000000000000..0cd9cc493593 --- /dev/null +++ b/builder-support/debian/dnsdist/debian-bookworm/missing-sources/moment.js @@ -0,0 +1 @@ +../../src_js/moment.js \ No newline at end of file diff --git a/builder-support/debian/dnsdist/debian-bookworm/missing-sources/rickshaw.js b/builder-support/debian/dnsdist/debian-bookworm/missing-sources/rickshaw.js new file mode 120000 index 000000000000..c13670340d41 --- /dev/null +++ b/builder-support/debian/dnsdist/debian-bookworm/missing-sources/rickshaw.js @@ -0,0 +1 @@ +../../src_js/rickshaw.js \ No newline at end of file diff --git a/builder-support/debian/dnsdist/debian-bookworm/rules b/builder-support/debian/dnsdist/debian-bookworm/rules new file mode 100755 index 000000000000..a633dc0f3e19 --- /dev/null +++ b/builder-support/debian/dnsdist/debian-bookworm/rules @@ -0,0 +1,103 @@ +#!/usr/bin/make -f +include /usr/share/dpkg/architecture.mk +include /usr/share/dpkg/pkg-info.mk + +# Enable hardening features for daemons +export DEB_BUILD_MAINT_OPTIONS=hardening=+bindnow,+pie +# see EXAMPLES in dpkg-buildflags(1) and read /usr/share/dpkg/* +DPKG_EXPORT_BUILDFLAGS = 1 +include /usr/share/dpkg/default.mk + +# for atomic support on powerpc (automatic on mipsel) +LDFLAGS += -latomic + +# Only enable systemd integration on Linux operating systems +ifeq ($(DEB_HOST_ARCH_OS),linux) +CONFIGURE_ARGS += --enable-systemd --with-systemd=/lib/systemd/system +DH_ARGS += --with systemd +else +CONFIGURE_ARGS += --disable-systemd +endif + +# Only enable BPF/XDP on Linux operating systems +ifeq ($(DEB_HOST_ARCH_OS),linux) +CONFIGURE_ARGS += --with-xsk +else +CONFIGURE_ARGS += --without-xsk +endif + +# Only disable luajit on arm64 +ifneq ($(DEB_HOST_ARCH),arm64) +CONFIGURE_ARGS += --with-lua=luajit +else +CONFIGURE_ARGS += --with-lua=lua5.3 +endif + +%: + dh $@ \ + --with autoreconf \ + $(DH_ARGS) + +override_dh_auto_clean: + rm -f dnslabeltext.cc + dh_auto_clean + +override_dh_auto_configure: + ./configure \ + --host=$(DEB_HOST_GNU_TYPE) \ + --build=$(DEB_BUILD_GNU_TYPE) \ + --prefix=/usr \ + --sysconfdir=/etc/dnsdist \ + --mandir=\$${prefix}/share/man \ + --infodir=\$${prefix}/share/info \ + --libdir='$${prefix}/lib/$(DEB_HOST_MULTIARCH)' \ + --libexecdir='$${prefix}/lib' \ + --enable-lto=thin \ + --enable-dns-over-https \ + --enable-dns-over-quic \ + --enable-dns-over-http3 \ + --enable-dns-over-tls \ + --enable-dnscrypt \ + --enable-dnstap \ + --with-ebpf \ + --with-gnutls \ + --with-h2o \ + --with-net-snmp \ + --with-libcap \ + --with-libsodium \ + --with-quiche \ + --with-re2 \ + --with-service-user='_dnsdist' \ + --with-service-group='_dnsdist' \ + $(CONFIGURE_ARGS) \ + PKG_CONFIG_PATH=/opt/lib/pkgconfig + +override_dh_auto_build-arch: + dh_auto_build -- V=1 + +override_dh_install: + dh_auto_install + install -Dm644 /usr/lib/libdnsdist-quiche.so debian/dnsdist/usr/lib/libdnsdist-quiche.so +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 +else + echo Keeping RestrictAddressFamilies in debian/dnsdist/lib/systemd/system/*.service +endif + +override_dh_installexamples: + cp dnsdist.conf-dist dnsdist.conf + dh_installexamples + rm -f dnsdist.conf + +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 + +override_dh_builddeb: + dh_builddeb -- -Zgzip diff --git a/builder-support/debian/dnsdist/debian-bookworm/source/format b/builder-support/debian/dnsdist/debian-bookworm/source/format new file mode 100644 index 000000000000..163aaf8d82b6 --- /dev/null +++ b/builder-support/debian/dnsdist/debian-bookworm/source/format @@ -0,0 +1 @@ +3.0 (quilt) diff --git a/builder-support/debian/dnsdist/debian-bookworm/upstream/signing-key.asc b/builder-support/debian/dnsdist/debian-bookworm/upstream/signing-key.asc new file mode 100644 index 000000000000..bc7e1ec64765 --- /dev/null +++ b/builder-support/debian/dnsdist/debian-bookworm/upstream/signing-key.asc @@ -0,0 +1,189 @@ +-----BEGIN PGP PUBLIC KEY BLOCK----- + +mQINBE5fJpEBEADl7Epp8pxg5ENBY4KM7U/lrxRg33BPDJcZTxqnCLbNCdEOSO1T +Ej3jWl1HEh236NlWLvHsXgrsKiB1jX037q62QKrp10trQMsM6QiEUjwmrGJxgxv2 +D/+U2PJPh6/ElFhx1PqGEC1Ih3pTpP1YINzfX6cQ9/e3nc64BcBTQqYA2/YIv4pH +MYXZrPm398JZbPpT0ot9ggdLulUYSRJQ9dfNJbGpstMMfOkA2IFvfmKc5BT5Y/ZA +ayF7xPBEGbBMLaZuT8q+x5S39ZyzxzCMSIJD7nYAh7qI0xiosfu8YyjXPN3x1OYX +kdBKzYEk8ji9xgyNZ/9Hlsq3JhJzuGKuXKuC3GKf8BcNw0JH+/VWmq+3kd30a8dy +GgCW+YJok+zyo51WWVLeJ//5tZ2/GvRhbIivA6Gp2cxQlwl9Ouj7SkBNRWvjBJUr +N0NF1FxKo1Yq9OECq2KgLn3l2vURW+LUtdXDbZcn9GcYbGHIE0xdCGQSH/H+Dkgl +T63rQIgBN2MTQ4lhun/5ABLq7s82BAtakhQ5S+3gD+LykCcvCxgHApV28yJJT3ZZ ++Qt6uNtHf2y6T4eJpiE+bWJpG3ujCwzQxu3x5L76jOgiRaj6HcwzT79LpjZMzhnK +1sKhDAuJP2VNIYhAXn8UF+z54dmBRK58t8zQVop+BpJAE7QM/DFDp3uLhwARAQAB +tC1QZXRlciB2YW4gRGlqayA8cGV0ZXIudmFuLmRpamtAbmV0aGVybGFicy5ubD6I +RgQQEQIABgUCUWu5/QAKCRAcXumQ0ucVdWzlAKC1r7xlQ54vi/tOqTDFid0eV+UG +qwCeKBoxjQUwcICQpz4wi5u6fTDD1AeJAjgEEwECACIFAk5fJpECGwMGCwkIBwMC +BhUIAgkKCwQWAgMBAh4BAheAAAoJENz1E/p+7RnzoQQQAJjEVUbLcBd4blXL6EW3 +VMqIMFbxBt4CiHRjsSo02+rUMWLOqZBERfynv0oufhrW3AqTO0OMoqPLWjWFNeOH +OdKieBJdcXHDJPO8qRUpbcYh5CXr54X09d5WZU8sGipnd8wxO68J8g+5vux3xscE +aZTwWZTwyelWA77OxJm6WlPPxJ+lTyIuhVC3KoBUWRwfNrxE/ij/0tkVFoIXvczb +AQqB6+nApHZvtoR4Wys4bzmCWuo9PUj0r3+eyjsWEB0A4Ya1bwaJOchubi/Gq99w +fp71zJC8FcSMWmoGPRnpg6oLpkxC8YreV/16DUgiMnxUPyJAEpb+AH0MMudmp6tn +UaWBs/hWnpyWPXqjt6wzs7X31X2oj93ANKjnSpglOgUEBKk4GTyOuBo3S+kyXD9W +W977kyKVtUQf3U5EHUR08UA/DuEJPGDnMa9lujXM17h//iyixa0RhJXX+ZRKRwEA +Zqj6H8wNayF045JdwMJ6TIePuymV2ltyG5E0M5l5SOc4fELNHJyHvjhi1Fb23lqB +xNhvdm8+RtwtFz+QtFwihP/cEBMue5lcj5Bkvwx3NERJxoPi/Qe82mLZLaMCdlP+ ++jzvSrsVrRWkyw+i08T0+Dp9/V5YoEUkhSfNp1w26FtrFVqC4XpVxtjda32Ipw3a +ygpOqEkCxNsy3+C1buzr/QK9iQIcBBABAgAGBQJSExvtAAoJEHcKmPCS30+Zo9wQ +AI+4GsOZCtV1jd8M88KIDl5b0Kh0ogK/pg6orYu0kyDF9W16p5qEn0sTZP2QP4+D +yZWDfPTe1fxHlSac3KXMTqGtLKDq0xP0WIoqjhSnMRmvhmODNxnODueSL7Jmg8cv +XKvj7FEYaI+mqgChyikX9JSJdTWiMuQMOC6adGr2EL77e6e3jAaI31OtCTbam+EA +JFwxpYSlBMop+SemBbeokHHRivEyg2huO6o7m4SprxkvZZWmiT7DmdIGhaPt5CHo +DbUdUbj8ni2EdSZnYJcCUpPHJqF1FUkwsc8NDH6tiAo7cHsjkrDAx5Waetnt9mRk +MkG0tUgnULcChcx6NJiG3lhIWNDnj9MLzhA7kDaktvtEwCyuQA8iyWWfOgIABofY +Jk3zbPG63N1XhWyZ/ic3IbmnWWrEaK2xYDABvTN6s8nOgex0D+kArhsPE+RMWPzF +9F+2YgrbP7R68ek2+/4SdQNifUloMDJJA38nmxkM0SsLNgbIWLaltw3GwT/0LQ7s +TtcLLMhl7bkgyYLmmII8MxXPQhvr1oXX17t6fwJLiQjokO0CVw20CT6QeFo4P+pg +oYkSPn7tFtfkB3sgZhea3Lr545NDpK5Vj/0WxMOYhqEUmgCRjyzmczklyoXPMFD7 +rX5LxEENXUnxkGGkKFB6OIMq7zrheBscB0/wcZcO05pFiQIcBBABAgAGBQJVB+GX +AAoJEF5QcVvy/+GnMCIQAITJ/73QIgrsUFh6fWGfKMOgY8f2JUfwe5g/vSO2BQPS +cSgTjoKdpy4DCILI3WzSZ2xzxOlS0SMj8hoDIwQxSydYuZhIfAmlUmaT0Q5p6Zae +f7/+pFRVkas9CA4NE4V3ZCEhQjVvEI8bXabdld452PE2Fahi6m58JEFwFnU84sII +sQJCiFFFFj7OxNGGMK63vZFxgE9dhW1kpMGBfxdKLFyglEpll2qGbCp13shFLeZS +Cg5WJ/pC6R2t0K5tW2XAHz7TRj94dnFTVD8DMlydrBrxYh7DMVaeFLDgepxtT5n8 +yW/RLThHAvg7Qyvie/l5bt8Ukk12ISPv7sY9bYdM0wHWj0913RKbK5Ic22LM3RK3 +My/yXeKMI0u8PTUuCppIiCRhqNjFr23XsaixOYRDSsvo6ca70oCUzyVMJs1nmknF +oimw9yRhT4bUN5yS30E9jqhgNb06cXUcCM/rPYvVqe2/OoUMYBHjRHqn64uHzvtn +nrJKAGUk0EqTdqyRCfWzo3+mClCzeyes2P0zzGYzwZ/fo+fIVcT552wWCbJa7KW7 +XcW7CTzWgAucupK7tm9jOezvd2Zt68lROAVRL1F+P2HUQvzLcXVaSqOIHkiySMAK +fVEBfwA6y2oBjUkBv0oKuSV8xk+cq3B3sxDQra4Vw2MjyyiCrw+piIntgqnSrzTv +tCxQZXRlciB2YW4gRGlqayA8cGV0ZXIudmFuLmRpamtAcG93ZXJkbnMuY29tPokC +HAQQAQIABgUCVQfhlwAKCRBeUHFb8v/hp2pbD/9YaX+vGZ8ZJTtXbwmbMQ6ZXC+a +nWLygPxk6d3DTFfdbSOwYHH0RSaqymhJ84lVypoUP7tZxRBL5pGvBE5i7iZVTAj9 +y+mV95EKeM/bR/k4EYQi6nAgaSRKFnN/BkimfZRFBiV0ox/3TxBjIZFUG7P+0TgY +/8C8jIpz2Gt1MuC6J6wI+DkUD7mFTjzQSz/HTIQctngcTq4lFvZiP8pcBDt/kg8u +/ATlJih9LPfixyZo7YIunbj77jLomkKkShsNFQEDPOIWsQotjIYnFb3YK1vgIQEp +CZZE9ElAEw/6yfXIVyqPzg8ZiP1UOEDQJw7/AVhfb1uiR/US2yPe8xxe+sVaYDgi +M6VCG1lbun/l79bQEo2tjacOih/Uba5UPOzudseW7XMghwIf+1Y4U1r7HEEbRRd3 +pGqmjBFjGcdnw5XzpZ7KyzwGjie57uf0xzPwpjtIIr+HjiIfRmBuFSx92JvpVieu +ciGT646F2d4vsxKMNsY89vSoO8dqXEFWOjTabZjk5ZWjz3tCbpqDAgAcj8VVn4XX +HPM2Rood1mIENYp2IsICS2Js9RRRfxpY5Gk5E7zFXkHvJLfitwu3VKwB2iWP1m8L +TxQHZFjLN2TPF09yt8qfrCUyuY92HJtoRqJ2X4N2DO0gTrtiCylefdJvkSf8NM4s +z5OwEW38V/8Drni3S4kCOAQTAQIAIgUCVMofzwIbAwYLCQgHAwIGFQgCCQoLBBYC +AwECHgECF4AACgkQ3PUT+n7tGfOjhg/+NtAiH9AVGTmbpdNHNyWrmPxgO5XKtfLZ ++4gz6D1QpFwFO/YPL9iN8RhzsZJCestI6tP9vuBPvku7Nd1IFfuZlUmg26gftUTQ +UmRMd/lMm264WOYPXu12g8+PvkUwXfyoAcd32nOpSMkpiFymRN8GtTzIm4qOgWA2 ++mdFWMl0xTSuKv60MiNIszEKD80UxDS2bSj1cv2VBxDFwmlrzPEa/ozxAI9t9CxY +8Lv6CsEfr5yHSWOkV/mqSo+4OegdNjiRRoeMo0/bUBOtkZSykj2ONSVBn1oIOYQt +ForUhtRyZFLJIiO11szngRDqRYOslmMMLuZ2k+/b/K4E9jvJvz7yt+Y3BPOsjRtV +9tP7oSKJpTN43PTnTbKMM2RpVTN3bPtfhZ1+iQubk0y2H2XaKOnKX6wEdjaWOoBR +6SRzVzSz8dgOqvkfBhA/bJchwWHnyckk7bZJXMszafnkJZzW/eVeuopUgkyWeMHp +6lngpwpSqCPXn10zm3bBYNSOFdCCX2Qs3hB8fLi8OQGv9puikZmvdIwL1jWP1GEk +kP94xFTtv4wqgBykySjTMBs9zEDOOGq2x2ndUJZuUpoY7AHBtzmMrfwuuHwyFHfT +VpalE36S6f7D8UA631vO/BGcDj+5xnAhhTBiiFbworNjAfZs4/bfKam4v5rYb+ri +z+OJYVa0m9K5Ag0ETl8mkQEQAM4WIsHIK/1+/39QZbh376iVXfc4NVdE3ID/Lozz +9JDanjkpScpikwugDwguVx+8JdO2tTyo6JTzpiZ+CoaxmjudJpUTT7fD5ONcAd1s +tpHKUQFwJczU6LSXpTQCpmhV5s13pwumxjymKRlotxLdr9+zxFl0e4VTFb5oj4Ik +2wu6sehcIt73AxM38C8smFRrRegPQL2Xnq9BE+WUF2yyY3TOVAK5TP2MbwQTkrTO +iTYJZdNHNlvjIpZaxHKOLqytNXSmXn1k20nitmyssIzv0aEC1UdktWIL/gD1Z+Sj +rJQB7/y56Dx7o6gr6J2MZZeo7a211TLdblejD6bMjGaH4CTnjzmkMtDC/2b+FUc3 +x3/GlQF4hWB4iaT4aCjiKOVNQgaQyAeRTsv1BUoqf8LDytW1/MdalLYElKS77t69 +HEQ9HSyt7QHU3sjAG6qgso8yWn8ebYCefm1lyZSP3BbvZ/UpoKuB+aGlXjteaXQh +IRLRA1TgijiGA3Yw1dTcz2Cb42w4UNZw4r55yN60QDRBH4l1yrRPltdyAaX3qEg4 +4U/Z7LU2YTDX+4JL1O4ZE+snDVsTPMpuZLvRFkxCLG1FTXZacZRXfzlFzw6YWhpn +HUYORO3fGhb+PKMKYEloTyLywjkVLHFbvaPts96dCxWyDrcMOqhgiLOLJo7qC+/S +q8k9ABEBAAGJAh8EGAECAAkFAk5fJpECGwwACgkQ3PUT+n7tGfNv1A//dYWV+vL1 +jiL+X4vRSCrDM8bBmt/cZfN5O0i3HYPMdSD9lVr9O+WYKJogxEXX1ofgEO74rwZx +Gw0crrMN8VM9SgMZ3jioGI15NF3INnA1r53GNGhJ4JVnz0KV2NKtshk7CtSxrjoR +8qplwbMMICVgTIERVP1enuOb3FEtbhI4rcy+2UTw3hwURBhIfUotVFO6SKu3ZLsc +ItbiNxpTqTpL6AIp9UOrZjcqfCuFs8P+57uusAHcp6GYhhIhNIdXf64RQs7gtdLV +W71z0diSxu3KFWlrXOx0rrm7RTAQn1VOLl4W5oBPvcF2ZVQvd84I74TMtpP0MRDF +gLuK0HHFVyDff0vx76rubQgom6z8ajiIa6MfEmd7z9xhQT5PU0FApYY6H/kW7ao+ +f2h2IIjz/+QjHuYn0CqqcjkkLC76RAgQjHYO9NIpL9Gi9O+I2AFz8YjOK3hOpxMr +F/LjPJtxBXGFEwP4ud+hzDMjwaa7PklcmDPUBuSDIgbNvsVNA6gn7AkbQn6NH+DI +mdrpzgpSr1FHMbjIWqpXWbAZtmOurxn9f5ZXPKAgMvlV4TS4NZqnWT5HZCKs2b5P +ed2L+zAdLP5NmyzJrSIyVTJ7JMLLfCLaWu/qsHRGt1w86gewg7uMPdA1IEvjjXaI +WNhYKUq6ik+DNrq0Y3fUuRg35QHaPTcab+eZAQ0EVjikBwEIAIhTkdGQEbdVwF8l +qp63Eigp0tHFbdeZ4LCu4sW3oM3erxtO2w25Awkdrw5jRopYmheM5BJsGgpIZUAU +pOakJR8fi+ESu3wNarKCVF+KjYvdxN7jwZmOI5t1ctnGewg0DHZZtymgJEpON1Zf +QwfYmD/J/k9Lqdv6CVyVGwNCZUZCO33a/bec12wKnwj2uM/X5tDLmIcHUiJC4Uno +MFAmGBZDOSxPZrNnzdoAO9zj/4WDtUVhLNkeSn3w1/LNSSJTNiLQjk7Lgq/Khd5L +8Jf1a1AYzW+NkBdeIP44MnQ68HYSwJRPq3iL2lZaH/4uc21FYhWfw8l5BsIA7bAm +UzFfbwEAEQEAAbQoUmVtaSBHYWNvZ25lIDxyZW1pLmdhY29nbmVAcG93ZXJkbnMu +Y29tPokBPQQTAQoAJwUCVrBxMgIbAwUJEswDAAULCQgHBAUVCgkICwUWAgMBAAIe +AQIXgAAKCRCiCO1PivWERnTOB/4jLvex0M+TE5iL/FUki8EHyj6648sOCHnUHHnS ++slME2b71iAvLJxClDJjLD43Jj7FL0hu2LOnw+5PQZrhLyB1WEa1tC0tLvIkPuzC +VJPI4FH7+AegmBrGYN6554Hy0C/YRF8mOGngL58hrumJTgjB7vC+CvDp0714WQG/ +SgcKqk4jkIz/Iep2vj3dCifdh+kJkaK/nnzIT1euiOzp8xLByiVbCOdlbvYoVetq +vJcqIhOHCglv045lZcAp9kP9pm/kEzHM34PhkH6SrR/uodshOH4p3Ux0wGgwUbou +DvHUtjlK+GB8cYXdRny0tvdGBYUO7CsFNzPoRC8CvD+VY8DltC1HYWNvZ25lLCBS +ZW1pIDxyZW1pLmdhY29nbmVAb3Blbi14Y2hhbmdlLmNvbT6JATEEEwECABsFAlY4 +pAcCGwMECwkIBwYVCAIJCgsFCRLMAwAACgkQogjtT4r1hEbMMAf/WS0+yuheoWrx +CZ4qYQo+AjlaenFTPQwrEDNioj6gjST/eAaQW1/+trFPzwNrBSenDE6bwPcPdL51 +mXg+30fNzHLWrBPDsMqBlPTIvpBbQ/bVqjV3JnU8I8dHfdKmInJRrCJM21gDTprQ +dqfBfSHJHgM5TG2+fUxpdLIAhBRknXt4+TuE272DJf6gHxnDs1oqQ6kAxC0ANJyE +ufFXJGeERN2OsFtSygOcUiHeXwWyM77RGf73gkS9+bCoftiuM4gbKSibk4BbUVBZ +JCs28fDnAsmIstZldUGZgIuy0vUfH153DTJflN+CIGEvRUwk+nrDIwYkV0pr9eZ0 +lz/OFhwzJ7kBDQRWOKQHAQgAjr1xEZh1yglszi94+HLNFcgRPgRNktg2vxOGf64d +AreJvL5iDrS2lrFMknh5BNuj7nJZ2r40OOS91oH1qkVk+v9Cyo/3xwCpCOPQCkhz +HpuQWXoMGMw/3/0tG6zTxnYdC999faCH0lLA8oDwHCHlZSHgsH9+qSNyjaJXvS+H +VoGYzyuanU6OTM7EM5c7RCPhNjT9JzHLISnwaxgDpwi7Ez6yudcrg6DqS/uUwkyN +tWyesx1DF9y2VJUNwa4NKIJkSH+niEoxK9NBfBAmAKc4o5+KPs6BvpvpiYY9gTKa +aLypPHNcveQTDFv/26XHyzrCZmwuGlcYBjboH/BWzKbhuQARAQABiQExBBgBAgAb +BQJWOKQHAhsMBAsJCAcGFQgCCQoLBQkSzAMAAAoJEKII7U+K9YRGXJQH/3PtQG0A +krXOpkOMXFLTKdCEViNNHN94VIaceVn60zbmXzxhYeKz7K345/EqATi3P3/yDHch +t7j3uYPhvaMjy3smN6vEwX7Ue40PbFDWmm8mHpLdlOfPXF0SRUD8KTSD6+W2VJfE +cDI6DDfUmCx9yYZ1U5u+O8Aj+1l2gdQbgAioPnQgqzf43qgnRcsfNmsVsXg7EbHs +pRpJOR1XyXl/9KrDP7p6kjwWTQ1NoRjCw0qaX93odLeKIpd2riShlB7GteUTps0I +fuiL94CA58PV2YvZapN1KmwDohHU8rndN7zte7jbCyv1Vv9tP6Ns0TvycBAqlOZY +dgabrT+Pccb4jCeZAg0EVPRvsgEQAMeXMm92zU2ooQOE4AbQhYY3gn+MG87l088W +rAMlpTfbH7jBPDQ47EJyAVh3NY+XXucXCMLzJ5e9sAIJk3PrYqDmjWVYDox3Hx5r +MKIY65N1Rud1kMGWsgQCzU5RmarFNLJ0OdpE0K0tMTajS3gxqJ1zOOKdSfZGS+u2 ++UKyLUelB07mZROv9uanu/ia8I/m8RG5jb6pVzUpuoWW0J5XQoA6mvWREbJDgP0s +WWgWSvt+0XRtrcHR9sie7a4ynjowL6M+iTm4ShPrqX5TuxmwJSQcfTZjqz+5cnpp +yTzj+mG2/jHBERGWkL3sx37s3uohhWt2EZVuyIcUQMigGssCp9216K+ihyC4tEj8 +RSfbon35t7OGYJlRS7V/raIm4GdYLCOkQs0yUIila6AcC5xpRnHXHIXvUNHrk1nA +yz5PEER/6BiW+vMObonx+GR8oUfo+2uMg4LSYKx+o5jgWBFiSdNl3gQK5+RswpdF +fzyq9gZf4WvVOCdBH0YKEQ8iJYX17drkn4OWMG7u0QnQs6GdZTcClQJMTWVBIaCt +RMNCxuKX6XI9WOgwj6vHM/ijeugPLOsUSv+uWcK/fH7SSqmtJdMmpwNKFUmr4ZTa +WZ5QLLv6kXbIoKqEKFwXkJAPgm36mVEE+ruy3FoNl0um9S0W8tGXgs7pMmM0AZ/n +Hb6R++6lABEBAAG0KFBpZXRlciBMZXhpcyA8cGlldGVyLmxleGlzQHBvd2VyZG5z +LmNvbT6JAhwEEAECAAYFAlT0dSoACgkQ3PUT+n7tGfPs5RAAlQXWPO+ZJNjCLFcu +AKEyNfNl5ssCVykUmQzfem8Y/Z8NojoMkb1mBZlt1OIItYOcpB+bGiH2fnY4WUjd +s1y29mPThrx8vpm4QY0/lxw+h89IrQrdXcHXS9KvedZfWCnseEMZo/xwGVCBiyDX +LSFv7RwugnJr6VeLg7oYrGyGNgIeiax66OFJ7cWxKX7zG7Z3hfZ2YFM/djCyts2A +Lgb8WKEd1+xaINpmXLIBZb6oKA7JrPHjGHXCtiOFccyONmW4ukcGOsPrn0Wm1rAX +IbHl/sxC/KIONdPSjktuY9uOW8DtinW55eAMxlX91CHpX9XK3WENAqkkSL9hu8ov +JPXrz58XdqHMKdgwGQdYXcUY8lU7YX1dXmp2th/8kMkSQXcqYv/LmbRFRwq7pd8A +6U3LsIT9JIHC1LcTxyIaKZBQegHvtVm4oDOBBDJ9ImBH53FJhJ9hModrG5Hcfmwr +nquITLicuBgMM2SgZmH/ykeDpO3atQrCSDzlozFR6SKsssnfmIrbyUJzrsJ6tuz1 +/3kNosdPuFaHIxLlNawAhUkJL4CrTRLcWuknJo5OOgCtYWRGcCBQj9iS10EZDcvg +uLsotQbTvdeE5T+o97gaF0p7ww6XaFcII9HLUrEjMLQFSFmHAjyxdm1Esh8I8yEA +oR3FXtdNwgs0CBuR2bY399oT26qJAj4EEwECACgFAlT0b7ICGwMFCQlmAYAGCwkI +BwMCBhUIAgkKCwQWAgMBAh4BAheAAAoJEF5QcVvy/+GnS1YQAMbqFxpsYGMsVRpw +rFZ1/NXyfekLk7XCWD++YgwZ10d8jXQAv0BHl5ubkpCEuZdb7SqEkLgKbjRfw4KD +qri7iDGjwoYckmqh1xl20qyTYkfeQnKizuBWmNsVL0JT0xUzKhcHBh1bWx4FPF+i +ojOlZQLKwViPpGOacstlBcPfRPQhaP7QLKrOVvVQd0ebfbbsjSBP+olik6OWRiyX +iIfsAmGq0OFDtk+f7jI2UIMC+/ADpulzMmJdr8l28wgoudtVM+w4LB6hbFOYSvVy ++kcMqWI+yQ64Dy6OnFVI5cZH3jxQXviqEs+QNzM4jUQ37Kp3TPBEDvHNQkdYllk7 +E24ecF/bi/3kDISBfVobSdMGMERxPJhnNWCG3sEP+WPT5beDKywcWX4kOHpmhL3w +RxJbZzEusA7IXyeeb2AWfsJBdMtM5Ur2u4yW9Dq9uNFDvXvo4mQXqM0aPmpKN5Ir +SzTBT/9bKvu6iOI+JBiEY/A5wCySrqIsjqeJ5Wac6dPCcNsxLV1qdEVMV2eg86A/ +lbymPiQg5xJzF6RSFwmVpbye7HFqcKTLwbZJHzf46CpgFyK0Wuavp90Nxrr0oTaH +xfnAHlTDdXMybm3Z7wlEXEg2hSh5rrTKYfjgtoIJRkCjaRh40jRkIM0YLx5uR5PB +k6hAzjik/7sYvgG5XuPra3L6Y8XGuQINBFT0b7IBEACjecdg3e1IF3zBMadFbFah +7ZSPFK4Y8Q+OMbeiu0TzXP65rRXDQi595jdIcQY6/7gB1IguqC0HWUo/Ns7GFnNn +WbrAaoVWpLjHXgMJ9hqdyIEgluoJECH53d0Y73oi+PBoYUU5z2tHi7AiiJc9qMG4 +m9q2P7xUrnqCqmGO4pU9nFJTFUAodf/ioNk9EdmciLmFUm7XkHNtUcKVQGWER7vi +dedWLW1fhHAzhI1hYkN85ZfIULfrVNZBn1U/L4nry7P7HO0IQxoK7POs6apxU4Jy +ATEyvsnjYU+UOCDPXRIKHAZ4joEnFhyHPyURgdMLxQb0s1hnbTEC+szvqb9kC0rC +an1GRb6/VeW9eRi1CoBpHtQEwY6k+YgWpvcfR0w9+6BH5aqypGWnNDCWcOTINUro +uALb68oxgnEAowhWIa0ujUYy+PMYF0AFArjLVxu1IBKaMD/Wsk0ws389xAnbVW81 +bhHN2Ye2NznDe3YfK5FkUyWXO6GA1tFQw+joxt6+TPcTxRJLS/MG/gXcluQE3Kv+ +jteqi/dbt5A+potX6qGN+F1GJwD/mQKyULklzlcZCIYZN9OnKVbSxfn2xQ89bjvk +NvRjuO33x0IozIr/R/uz4T0H9Ve4UoNj2vT4pH/Ba/ergQSfrrAJMDyIB+SRIgY7 +LCQFB3rOIvg/HiqAY3VL1wARAQABiQIlBBgBAgAPBQJU9G+yAhsMBQkJZgGAAAoJ +EF5QcVvy/+Gng+UP/0CjLMF30xjRim/+/qzx+2OZ1S6R6B2mp971lQxB8gCA7dn8 +0UhSZZMHfMeo2N34itI4HEhQb+2jTOgQvNjv36zMppZjHQUg4+xabvZU33FrB2hh +D/ZdNTm7lCD87vKxz07flApkscw20VenY8E81z15GuWLK/UqE9wK5sbVoFB36mwN +Fqgh8W3oBBJTJoxWBFnqZu7arsaXhEWpVW2+36I2SWaWFmIPwrUbuwIXSuv+h+ks +EOdVXr87AgxX4qsPs44N6z57yhCIz6g3ow7R06IolsDSE8wyOWL313X6UE1R5Qyq +AX1yQ0BtmcPRh05SC/vRe7WeP2q+TyHMrM1/YLN/W9X4Y7loPL38fpmWMEaNT/Rg +ZPFjqDbqxYpyN6Kymdsfr3YPYNrzcYlc0WRplvpZh3D67PhI9XuRsb2c7GAU1jVz +e9Za1ZrPpcCCJgp562I9E1D+a4x7w9fsiGDkPOm5Iy3HTg9FH8VUWM3uwret74Zl +QyQkE1XYgRjqHrjqJOajJg8Qw4meItY0QB/5kxmAW1h96OoKBUZq5GaQ8AhtPnH+ +4peGQHG9fvSL58pukqeLGHkSwgdMPIFYZTHiIDt2tVkbi7vE3uvKPm1bZpvM2T6m +9ZUkVWV39P1W9lkqWvXSVfit1GRUpFd2onM7Rs0jxbZ9VfiRi2OblZ9Wkvts +=/oKT +-----END PGP PUBLIC KEY BLOCK----- diff --git a/builder-support/debian/dnsdist/debian-bookworm/watch b/builder-support/debian/dnsdist/debian-bookworm/watch new file mode 100644 index 000000000000..8c81a53389f5 --- /dev/null +++ b/builder-support/debian/dnsdist/debian-bookworm/watch @@ -0,0 +1,3 @@ +# Site Directory Pattern Version Script +version=3 +opts="pgpsigurlmangle=s/$/.asc/,versionmangle=s/-/~/" https://downloads.powerdns.com/releases/ dnsdist-([0-9]+.*)\.tar\.bz2 debian uupdate diff --git a/builder-support/dockerfiles/Dockerfile.target.debian-bookworm b/builder-support/dockerfiles/Dockerfile.target.debian-bookworm index 7f1b953daf19..073bb7645f72 100644 --- a/builder-support/dockerfiles/Dockerfile.target.debian-bookworm +++ b/builder-support/dockerfiles/Dockerfile.target.debian-bookworm @@ -26,7 +26,7 @@ ADD builder-support/debian/recursor/debian-buster/ pdns-recursor-${BUILDER_VERSI @ENDIF @IF [ -n "$M_dnsdist$M_all" ] -ADD builder-support/debian/dnsdist/debian-buster/ dnsdist-${BUILDER_VERSION}/debian/ +ADD builder-support/debian/dnsdist/debian-bookworm/ dnsdist-${BUILDER_VERSION}/debian/ @ENDIF @INCLUDE Dockerfile.debbuild diff --git a/builder-support/dockerfiles/Dockerfile.target.debian-trixie b/builder-support/dockerfiles/Dockerfile.target.debian-trixie index 0c23eb98b00f..8642f323b08d 100644 --- a/builder-support/dockerfiles/Dockerfile.target.debian-trixie +++ b/builder-support/dockerfiles/Dockerfile.target.debian-trixie @@ -26,7 +26,7 @@ ADD builder-support/debian/recursor/debian-buster/ pdns-recursor-${BUILDER_VERSI @ENDIF @IF [ -n "$M_dnsdist$M_all" ] -ADD builder-support/debian/dnsdist/debian-buster/ dnsdist-${BUILDER_VERSION}/debian/ +ADD builder-support/debian/dnsdist/debian-bookworm/ dnsdist-${BUILDER_VERSION}/debian/ @ENDIF @INCLUDE Dockerfile.debbuild diff --git a/builder-support/dockerfiles/Dockerfile.target.ubuntu-mantic b/builder-support/dockerfiles/Dockerfile.target.ubuntu-mantic index 2dbc488e6955..a55089dfe987 100644 --- a/builder-support/dockerfiles/Dockerfile.target.ubuntu-mantic +++ b/builder-support/dockerfiles/Dockerfile.target.ubuntu-mantic @@ -26,7 +26,7 @@ ADD builder-support/debian/recursor/debian-buster/ pdns-recursor-${BUILDER_VERSI @ENDIF @IF [ -n "$M_dnsdist$M_all" ] -ADD builder-support/debian/dnsdist/debian-buster/ dnsdist-${BUILDER_VERSION}/debian/ +ADD builder-support/debian/dnsdist/debian-bookworm/ dnsdist-${BUILDER_VERSION}/debian/ @ENDIF @INCLUDE Dockerfile.debbuild diff --git a/builder-support/specs/dnsdist.spec b/builder-support/specs/dnsdist.spec index 4cbb78b8999a..407a03d3cd7a 100644 --- a/builder-support/specs/dnsdist.spec +++ b/builder-support/specs/dnsdist.spec @@ -55,6 +55,10 @@ Requires(pre): shadow-utils BuildRequires: fstrm-devel %systemd_requires %endif +%if 0%{?rhel} >= 8 +BuildRequires: libbpf-devel +BuildRequires: libxdp-devel +%endif %description dnsdist is a high-performance DNS loadbalancer that is scriptable in Lua. diff --git a/contrib/xdp-filter.ebpf.src b/contrib/xdp-filter.ebpf.src index 3ead6e151b96..ec0d07145d26 100644 --- a/contrib/xdp-filter.ebpf.src +++ b/contrib/xdp-filter.ebpf.src @@ -1,9 +1,13 @@ #include "xdp.h" +#define DISABLE_LOGGING 1 + BPF_TABLE_PINNED("hash", uint32_t, struct map_value, v4filter, 1024, "/sys/fs/bpf/dnsdist/addr-v4"); BPF_TABLE_PINNED("hash", struct in6_addr, struct map_value, v6filter, 1024, "/sys/fs/bpf/dnsdist/addr-v6"); BPF_TABLE_PINNED("hash", struct dns_qname, struct map_value, qnamefilter, 1024, "/sys/fs/bpf/dnsdist/qnames"); +#ifndef DISABLE_LOGGING BPF_TABLE_PINNED("prog", int, int, progsarray, 2, "/sys/fs/bpf/dnsdist/progs"); +#endif /* DISABLE_LOGGING */ /* * bcc has added BPF_TABLE_PINNED7 to the latest commit of the master branch, but it has not yet been released. @@ -17,6 +21,26 @@ BPF_TABLE_PINNED("prog", int, int, progsarray, 2, "/sys/fs/bpf/dnsdist/progs"); BPF_TABLE_PINNED7("lpm_trie", struct CIDR4, struct map_value, cidr4filter, 1024, "/sys/fs/bpf/dnsdist/cidr4", BPF_F_NO_PREALLOC); BPF_TABLE_PINNED7("lpm_trie", struct CIDR6, struct map_value, cidr6filter, 1024, "/sys/fs/bpf/dnsdist/cidr6", BPF_F_NO_PREALLOC); +#ifdef UseXsk +#define BPF_XSKMAP_PIN(_name, _max_entries, _pinned) \ + struct _name##_table_t \ + { \ + u32 key; \ + int leaf; \ + int* (*lookup)(int*); \ + /* xdp_act = map.redirect_map(index, flag) */ \ + u64 (*redirect_map)(int, int); \ + u32 max_entries; \ + }; \ + __attribute__((section("maps/xskmap:" _pinned))) struct _name##_table_t _name = {.max_entries = (_max_entries)} + +BPF_XSKMAP_PIN(xsk_map, 16, "/sys/fs/bpf/dnsdist/xskmap"); +BPF_TABLE_PINNED("hash", struct IPv4AndPort, bool, xskDestinationsV4, 1024, "/sys/fs/bpf/dnsdist/xsk-destinations-v4"); +BPF_TABLE_PINNED("hash", struct IPv6AndPort, bool, xskDestinationsV6, 1024, "/sys/fs/bpf/dnsdist/xsk-destinations-v6"); +#endif /* UseXsk */ + +#define COMPARE_PORT(x, p) ((x) == bpf_htons(p)) + /* * Recalculate the checksum * Copyright 2020, NLnet Labs, All rights reserved. @@ -39,7 +63,7 @@ static inline void update_checksum(uint16_t *csum, uint16_t old_val, uint16_t ne * Set the TC bit and swap UDP ports * Copyright 2020, NLnet Labs, All rights reserved. */ -static inline enum dns_action set_tc_bit(struct udphdr *udp, struct dnshdr *dns) +static inline void set_tc_bit(struct udphdr* udp, struct dnshdr* dns) { uint16_t old_val = dns->flags.as_value; @@ -49,14 +73,12 @@ static inline enum dns_action set_tc_bit(struct udphdr *udp, struct dnshdr *dns) dns->flags.as_bits_and_pieces.tc = 1; // change the UDP destination to the source - udp->dest = udp->source; - udp->source = bpf_htons(DNS_PORT); + uint16_t tmp = udp->dest; + udp->dest = udp->source; + udp->source = tmp; // calculate and write the new checksum update_checksum(&udp->check, old_val, dns->flags.as_value); - - // bounce - return TC; } /* @@ -65,41 +87,43 @@ static inline enum dns_action set_tc_bit(struct udphdr *udp, struct dnshdr *dns) * TC if (modified) message needs to be replied * DROP if message needs to be blocke */ -static inline enum dns_action check_qname(struct cursor *c) +static inline struct map_value* check_qname(struct cursor* c) { struct dns_qname qkey = {0}; uint8_t qname_byte; uint16_t qtype; int length = 0; - for(int i = 0; i<255; i++) { - if (bpf_probe_read_kernel(&qname_byte, sizeof(qname_byte), c->pos)) { - return PASS; - } - c->pos += 1; - if (length == 0) { - if (qname_byte == 0 || qname_byte > 63 ) { - break; + for (int i = 0; i < 255; i++) { + if (bpf_probe_read_kernel(&qname_byte, sizeof(qname_byte), c->pos)) { + return NULL; + } + c->pos += 1; + if (length == 0) { + if (qname_byte == 0 || qname_byte > 63) { + break; } length += qname_byte; - } else { + } + else { length--; } - if (qname_byte >= 'A' && qname_byte <= 'Z') { - qkey.qname[i] = qname_byte + ('a' - 'A'); - } else { - qkey.qname[i] = qname_byte; - } + if (qname_byte >= 'A' && qname_byte <= 'Z') { + qkey.qname[i] = qname_byte + ('a' - 'A'); + } + else { + qkey.qname[i] = qname_byte; + } } // if the last read qbyte is not 0 incorrect QName format), return PASS if (qname_byte != 0) { - return PASS; + return NULL; } // get QType - if(bpf_probe_read_kernel(&qtype, sizeof(qtype), c->pos)) { - return PASS; + if (bpf_probe_read_kernel(&qtype, sizeof(qtype), c->pos)) { + return NULL; } struct map_value* value; @@ -108,125 +132,208 @@ static inline enum dns_action check_qname(struct cursor *c) qkey.qtype = bpf_htons(qtype); value = qnamefilter.lookup(&qkey); if (value) { - __sync_fetch_and_add(&value->counter, 1); - return value->action; + return value; } // check with Qtype 255 (*) qkey.qtype = 255; - value = qnamefilter.lookup(&qkey); - if (value) { - __sync_fetch_and_add(&value->counter, 1); - return value->action; - } - - return PASS; + return qnamefilter.lookup(&qkey); } /* * Parse IPv4 DNS mesage. - * Returns PASS if message needs to go through (i.e. pass) - * TC if (modified) message needs to be replied - * DROP if message needs to be blocked + * Returns XDP_PASS if message needs to go through (i.e. pass) + * XDP_REDIRECT if message needs to be redirected (for AF_XDP, which needs to be translated to the caller into XDP_PASS outside of the AF_XDP) + * XDP_TX if (modified) message needs to be replied + * XDP_DROP if message needs to be blocked */ -static inline enum dns_action udp_dns_reply_v4(struct cursor *c, struct CIDR4 *key) +static inline enum xdp_action parseIPV4(struct xdp_md* ctx, struct cursor* c) { - struct udphdr *udp; - struct dnshdr *dns; + struct iphdr* ipv4; + struct udphdr* udp = NULL; + struct dnshdr* dns = NULL; + if (!(ipv4 = parse_iphdr(c))) { + return XDP_PASS; + } + switch (ipv4->protocol) { + case IPPROTO_UDP: { + if (!(udp = parse_udphdr(c))) { + return XDP_PASS; + } +#ifdef UseXsk + struct IPv4AndPort v4Dest; + memset(&v4Dest, 0, sizeof(v4Dest)); + v4Dest.port = udp->dest; + v4Dest.addr = ipv4->daddr; + if (!xskDestinationsV4.lookup(&v4Dest)) { + return XDP_PASS; + } +#else /* UseXsk */ + if (!IN_DNS_PORT_SET(udp->dest)) { + return XDP_PASS; + } +#endif /* UseXsk */ + if (!(dns = parse_dnshdr(c))) { + return XDP_DROP; + } + break; + } - if (!(udp = parse_udphdr(c)) || udp->dest != bpf_htons(DNS_PORT)) { - return PASS; +#ifdef UseXsk + case IPPROTO_TCP: { + return XDP_PASS; } +#endif /* UseXsk */ - // check that we have a DNS packet - if (!(dns = parse_dnshdr(c))) { - return PASS; - } + default: + return XDP_PASS; + } + + struct CIDR4 key; + key.addr = bpf_htonl(ipv4->saddr); // if the address is blocked, perform the corresponding action - struct map_value* value = v4filter.lookup(&key->addr); + struct map_value* value = v4filter.lookup(&key.addr); if (value) { - __sync_fetch_and_add(&value->counter, 1); - if (value->action == TC) { - return set_tc_bit(udp, dns); - } else { - return value->action; - } + goto res; } - key->cidr = 32; - key->addr = bpf_htonl(key->addr); - value = cidr4filter.lookup(key); + key.cidr = 32; + key.addr = bpf_htonl(key.addr); + value = cidr4filter.lookup(&key); if (value) { + goto res; + } + + // ignore the DF flag + const uint16_t fragMask = htons(~(1 << 14)); + uint16_t frag = ipv4->frag_off & fragMask; + if (frag != 0) { + // MF flag is set, or Fragment Offset is != 0 + return XDP_PASS; + } + + if (dns) { + value = check_qname(c); + } + if (value) { + res: __sync_fetch_and_add(&value->counter, 1); - if (value->action == TC) { - return set_tc_bit(udp, dns); + if (value->action == TC && udp && dns) { + set_tc_bit(udp, dns); + // swap src/dest IP addresses + uint32_t swap_ipv4 = ipv4->daddr; + ipv4->daddr = ipv4->saddr; + ipv4->saddr = swap_ipv4; + +#ifndef DISABLE_LOGGING + progsarray.call(ctx, 1); +#endif /* DISABLE_LOGGING */ + return XDP_TX; } - else { - return value->action; + + if (value->action == DROP) { +#ifndef DISABLE_LOGGING + progsarray.call(ctx, 0); +#endif /* DISABLE_LOGGING */ + return XDP_DROP; } } - enum dns_action action = check_qname(c); - if (action == TC) { - return set_tc_bit(udp, dns); - } - return action; + return XDP_REDIRECT; } /* * Parse IPv6 DNS mesage. - * Returns PASS if message needs to go through (i.e. pass) - * TC if (modified) message needs to be replied - * DROP if message needs to be blocked + * Returns XDP_PASS if message needs to go through (i.e. pass) + * XDP_REDIRECT if message needs to be redirected (for AF_XDP, which needs to be translated to the caller into XDP_PASS outside of the AF_XDP) + * XDP_TX if (modified) message needs to be replied + * XDP_DROP if message needs to be blocked */ -static inline enum dns_action udp_dns_reply_v6(struct cursor *c, struct CIDR6* key) +static inline enum xdp_action parseIPV6(struct xdp_md* ctx, struct cursor* c) { - struct udphdr *udp; - struct dnshdr *dns; + struct ipv6hdr* ipv6; + struct udphdr* udp = NULL; + struct dnshdr* dns = NULL; + if (!(ipv6 = parse_ipv6hdr(c))) { + return XDP_PASS; + } + switch (ipv6->nexthdr) { + case IPPROTO_UDP: { + if (!(udp = parse_udphdr(c))) { + return XDP_PASS; + } +#ifdef UseXsk + struct IPv6AndPort v6Dest; + memset(&v6Dest, 0, sizeof(v6Dest)); + v6Dest.port = udp->dest; + memcpy(&v6Dest.addr, &ipv6->daddr, sizeof(v6Dest.addr)); + if (!xskDestinationsV6.lookup(&v6Dest)) { + return XDP_PASS; + } +#else /* UseXsk */ + if (!IN_DNS_PORT_SET(udp->dest)) { + return XDP_PASS; + } +#endif /* UseXsk */ + if (!(dns = parse_dnshdr(c))) { + return XDP_DROP; + } + break; + } - - if (!(udp = parse_udphdr(c)) || udp->dest != bpf_htons(DNS_PORT)) { - return PASS; +#ifdef UseXsk + case IPPROTO_TCP: { + return XDP_PASS; } +#endif /* UseXsk */ - // check that we have a DNS packet - ; - if (!(dns = parse_dnshdr(c))) { - return PASS; + default: + return XDP_PASS; } + struct CIDR6 key; + key.addr = ipv6->saddr; + // if the address is blocked, perform the corresponding action - struct map_value* value = v6filter.lookup(&key->addr); + struct map_value* value = v6filter.lookup(&key.addr); + if (value) { + goto res; + } + key.cidr = 128; + value = cidr6filter.lookup(&key); if (value) { - __sync_fetch_and_add(&value->counter, 1); - if (value->action == TC) { - return set_tc_bit(udp, dns); - } else { - return value->action; - } + goto res; } - key->cidr = 128; - value = cidr6filter.lookup(key); + if (dns) { + value = check_qname(c); + } if (value) { + res: __sync_fetch_and_add(&value->counter, 1); - if (value->action == TC) { - return set_tc_bit(udp, dns); + if (value->action == TC && udp && dns) { + set_tc_bit(udp, dns); + // swap src/dest IP addresses + struct in6_addr swap_ipv6 = ipv6->daddr; + ipv6->daddr = ipv6->saddr; + ipv6->saddr = swap_ipv6; +#ifndef DISABLE_LOGGING + progsarray.call(ctx, 1); +#endif /* DISABLE_LOGGING */ + return XDP_TX; } - else { - return value->action; + if (value->action == DROP) { +#ifndef DISABLE_LOGGING + progsarray.call(ctx, 0); +#endif /* DISABLE_LOGGING */ + return XDP_DROP; } } - - enum dns_action action = check_qname(c); - if (action == TC) { - return set_tc_bit(udp, dns); - } - return action; + return XDP_REDIRECT; } int xdp_dns_filter(struct xdp_md* ctx) @@ -235,9 +342,7 @@ int xdp_dns_filter(struct xdp_md* ctx) struct cursor c; struct ethhdr *eth; uint16_t eth_proto; - struct iphdr *ipv4; - struct ipv6hdr *ipv6; - int r = 0; + enum xdp_action r; // initialise the cursor cursor_init(&c, ctx); @@ -245,67 +350,36 @@ int xdp_dns_filter(struct xdp_md* ctx) // pass the packet if it is not an ethernet one if ((eth = parse_eth(&c, ð_proto))) { // IPv4 packets - if (eth_proto == bpf_htons(ETH_P_IP)) - { - if (!(ipv4 = parse_iphdr(&c)) || bpf_htons(ipv4->protocol != IPPROTO_UDP)) { - return XDP_PASS; - } - - struct CIDR4 key; - key.addr = bpf_htonl(ipv4->saddr); - // if TC bit must not be set, apply the action - if ((r = udp_dns_reply_v4(&c, &key)) != TC) { - if (r == DROP) { - progsarray.call(ctx, 0); - return XDP_DROP; - } - return XDP_PASS; - } - - // swap src/dest IP addresses - uint32_t swap_ipv4 = ipv4->daddr; - ipv4->daddr = ipv4->saddr; - ipv4->saddr = swap_ipv4; + if (eth_proto == bpf_htons(ETH_P_IP)) { + r = parseIPV4(ctx, &c); + goto res; } // IPv6 packets else if (eth_proto == bpf_htons(ETH_P_IPV6)) { - if (!(ipv6 = parse_ipv6hdr(&c)) || bpf_htons(ipv6->nexthdr != IPPROTO_UDP)) { - return XDP_PASS; - } - struct CIDR6 key; - key.addr = ipv6->saddr; - - // if TC bit must not be set, apply the action - if ((r = udp_dns_reply_v6(&c, &key)) != TC) { - if (r == DROP) { - progsarray.call(ctx, 0); - return XDP_DROP; - } - return XDP_PASS; - } - - // swap src/dest IP addresses - struct in6_addr swap_ipv6 = ipv6->daddr; - ipv6->daddr = ipv6->saddr; - ipv6->saddr = swap_ipv6; + r = parseIPV6(ctx, &c); + goto res; } // pass all non-IP packets - else { - return XDP_PASS; - } + return XDP_PASS; } - else { + return XDP_PASS; +res: + switch (r) { + case XDP_REDIRECT: +#ifdef UseXsk + return xsk_map.redirect_map(ctx->rx_queue_index, 0); +#else return XDP_PASS; +#endif /* UseXsk */ + case XDP_TX: { // swap MAC addresses + uint8_t swap_eth[ETH_ALEN]; + memcpy(swap_eth, eth->h_dest, ETH_ALEN); + memcpy(eth->h_dest, eth->h_source, ETH_ALEN); + memcpy(eth->h_source, swap_eth, ETH_ALEN); + // bounce the request + return XDP_TX; + } + default: + return r; } - - // swap MAC addresses - uint8_t swap_eth[ETH_ALEN]; - memcpy(swap_eth, eth->h_dest, ETH_ALEN); - memcpy(eth->h_dest, eth->h_source, ETH_ALEN); - memcpy(eth->h_source, swap_eth, ETH_ALEN); - - progsarray.call(ctx, 1); - - // bounce the request - return XDP_TX; } diff --git a/contrib/xdp.h b/contrib/xdp.h index 87fef3a776cd..0d63fcfd963d 100644 --- a/contrib/xdp.h +++ b/contrib/xdp.h @@ -108,6 +108,18 @@ struct CIDR6 struct in6_addr addr; }; +struct IPv4AndPort +{ + uint32_t addr; + uint16_t port; +}; + +struct IPv6AndPort +{ + struct in6_addr addr; + uint16_t port; +}; + /* * Store the matching counter and the associated action for a blocked element */ @@ -128,7 +140,7 @@ static inline void cursor_init(struct cursor *c, struct xdp_md *ctx) c->pos = (void *)(long)ctx->data; } -/* +/* * Header parser functions * Copyright 2020, NLnet Labs, All rights reserved. */ @@ -180,4 +192,4 @@ static inline struct ethhdr *parse_eth(struct cursor *c, uint16_t *eth_proto) return eth; } -#endif +#endif diff --git a/contrib/xdp.py b/contrib/xdp.py index 6384c3f8b85d..089e68d9b1b1 100644 --- a/contrib/xdp.py +++ b/contrib/xdp.py @@ -1,10 +1,11 @@ #!/usr/bin/env python3 - -from bcc import BPF +import argparse import ctypes as ct import netaddr import socket +from bcc import BPF + # Constants QTYPES = {'LOC': 29, '*': 255, 'IXFR': 251, 'UINFO': 100, 'NSEC3': 50, 'AAAA': 28, 'CNAME': 5, 'MINFO': 14, 'EID': 31, 'GPOS': 27, 'X25': 19, 'HINFO': 13, 'CAA': 257, 'NULL': 10, 'DNSKEY': 48, 'DS': 43, 'ISDN': 20, 'SOA': 6, 'RP': 17, 'UID': 101, 'TALINK': 58, 'TKEY': 249, 'PX': 26, 'NSAP-PTR': 23, 'TXT': 16, 'IPSECKEY': 45, 'DNAME': 39, 'MAILA': 254, 'AFSDB': 18, 'SSHFP': 44, 'NS': 2, 'PTR': 12, 'SPF': 99, 'TA': 32768, 'A': 1, 'NXT': 30, 'AXFR': 252, 'RKEY': 57, 'KEY': 25, 'NIMLOC': 32, 'A6': 38, 'TLSA': 52, 'MG': 8, 'HIP': 55, 'NSEC': 47, 'GID': 102, 'SRV': 33, 'DLV': 32769, 'NSEC3PARAM': 51, 'UNSPEC': 103, 'TSIG': 250, 'ATMA': 34, 'RRSIG': 46, 'OPT': 41, 'MD': 3, 'NAPTR': 35, 'MF': 4, 'MB': 7, 'DHCID': 49, 'MX': 15, 'MAILB': 253, 'CERT': 37, 'NINFO': 56, 'APL': 42, 'MR': 9, 'SIG': 24, 'WKS': 11, 'KX': 36, 'NSAP': 22, 'RT': 21, 'SINK': 40} INV_QTYPES = {v: k for k, v in QTYPES.items()} @@ -13,9 +14,6 @@ DROP_ACTION = 1 TC_ACTION = 2 -# The interface on wich the filter will be attached -DEV = "eth0" - # The list of blocked IPv4, IPv6 and QNames # IP format : (IPAddress, Action) # CIDR format : (IPAddress/cidr, Action) @@ -27,10 +25,26 @@ blocked_qnames = [("localhost", "A", DROP_ACTION), ("test.com", "*", TC_ACTION)] # Main -xdp = BPF(src_file="xdp-filter.ebpf.src") +parser = argparse.ArgumentParser(description='XDP helper for DNSDist') +parser.add_argument('--xsk', action='store_true', help='Enable XSK (AF_XDP) mode', default=False) +parser.add_argument('--interface', '-i', type=str, default='eth0', help='The interface on which the filter will be attached') + +parameters = parser.parse_args() +cflag = [] +if parameters.xsk: + print(f'Enabling XSK (AF_XDP) on {parameters.interface}..') + cflag.append("-DUseXsk") +else: + Ports = [53] + portsStr = ', '.join(str(port) for port in Ports) + print(f'Enabling XDP on {parameters.interface} and ports {portsStr}..') + IN_DNS_PORT_SET = "||".join("COMPARE_PORT((x),"+str(i)+")" for i in Ports) + cflag.append(r"-DIN_DNS_PORT_SET(x)=(" + IN_DNS_PORT_SET + r")") + +xdp = BPF(src_file="xdp-filter.ebpf.src", cflags=cflag) fn = xdp.load_func("xdp_dns_filter", BPF.XDP) -xdp.attach_xdp(DEV, fn, 0) +xdp.attach_xdp(parameters.interface, fn, 0) v4filter = xdp.get_table("v4filter") v6filter = xdp.get_table("v6filter") @@ -38,6 +52,9 @@ cidr6filter = xdp.get_table("cidr6filter") qnamefilter = xdp.get_table("qnamefilter") +if parameters.xsk: + xskDestinations = xdp.get_table("xskDestinationsV4") + for ip in blocked_ipv4: print(f"Blocking {ip}") key = v4filter.Key(int(netaddr.IPAddress(ip[0]).value)) @@ -96,9 +113,10 @@ leaf.action = qname[2] qnamefilter[key] = leaf -print("Filter is ready") +print(f"Filter is ready on {parameters.interface}") + try: - xdp.trace_print() + xdp.trace_print() except KeyboardInterrupt: pass @@ -114,4 +132,4 @@ for item in qnamefilter.items(): print(f"{''.join(map(chr, item[0].qname)).strip()}/{INV_QTYPES[item[0].qtype]} ({ACTIONS[item[1].action]}): {item[1].counter}") -xdp.remove_xdp(DEV, 0) +xdp.remove_xdp(parameters.interface, 0) diff --git a/ext/libbpf/libbpf.h b/ext/libbpf/libbpf.h index 2fc728190966..f429545a0beb 100644 --- a/ext/libbpf/libbpf.h +++ b/ext/libbpf/libbpf.h @@ -8,19 +8,6 @@ extern "C" { struct bpf_insn; -int bpf_create_map(enum bpf_map_type map_type, int key_size, int value_size, - int max_entries, int map_flags); -int bpf_update_elem(int fd, void *key, void *value, unsigned long long flags); -int bpf_lookup_elem(int fd, void *key, void *value); -int bpf_delete_elem(int fd, void *key); -int bpf_get_next_key(int fd, void *key, void *next_key); - -int bpf_prog_load(enum bpf_prog_type prog_type, - const struct bpf_insn *insns, int insn_len, - const char *license, int kern_version); - -int bpf_obj_pin(int fd, const char *pathname); -int bpf_obj_get(const char *pathname); #define LOG_BUF_SIZE 65536 extern char bpf_log_buf[LOG_BUF_SIZE]; diff --git a/fuzzing/corpus/raw-xsk-frames/v4-udp.raw b/fuzzing/corpus/raw-xsk-frames/v4-udp.raw new file mode 100644 index 000000000000..31084b584951 Binary files /dev/null and b/fuzzing/corpus/raw-xsk-frames/v4-udp.raw differ diff --git a/pdns/bpf-filter.cc b/pdns/bpf-filter.cc index ec6bd05c5528..32f6986d9763 100644 --- a/pdns/bpf-filter.cc +++ b/pdns/bpf-filter.cc @@ -33,18 +33,18 @@ #include "misc.hh" -static __u64 ptr_to_u64(void *ptr) +static __u64 ptr_to_u64(const void *ptr) { return (__u64) (unsigned long) ptr; } /* these can be static as they are not declared in libbpf.h: */ -static int bpf_pin_map(int fd, const std::string& path) +static int bpf_pin_map(int descriptor, const std::string& path) { union bpf_attr attr; memset(&attr, 0, sizeof(attr)); - attr.bpf_fd = fd; - attr.pathname = ptr_to_u64(const_cast(path.c_str())); + attr.bpf_fd = descriptor; + attr.pathname = ptr_to_u64(path.c_str()); return syscall(SYS_bpf, BPF_OBJ_PIN, &attr, sizeof(attr)); } @@ -52,11 +52,11 @@ static int bpf_load_pinned_map(const std::string& path) { union bpf_attr attr; memset(&attr, 0, sizeof(attr)); - attr.pathname = ptr_to_u64(const_cast(path.c_str())); + attr.pathname = ptr_to_u64(path.c_str()); return syscall(SYS_bpf, BPF_OBJ_GET, &attr, sizeof(attr)); } -static void bpf_check_map_sizes(int fd, uint32_t expectedKeySize, uint32_t expectedValueSize) +static void bpf_check_map_sizes(int descriptor, uint32_t expectedKeySize, uint32_t expectedValueSize) { struct bpf_map_info info; uint32_t info_len = sizeof(info); @@ -64,7 +64,7 @@ static void bpf_check_map_sizes(int fd, uint32_t expectedKeySize, uint32_t expec union bpf_attr attr; memset(&attr, 0, sizeof(attr)); - attr.info.bpf_fd = fd; + attr.info.bpf_fd = descriptor; attr.info.info_len = info_len; attr.info.info = ptr_to_u64(&info); @@ -83,8 +83,9 @@ static void bpf_check_map_sizes(int fd, uint32_t expectedKeySize, uint32_t expec } } -int bpf_create_map(enum bpf_map_type map_type, int key_size, int value_size, - int max_entries, int map_flags) +// NOLINTNEXTLINE(bugprone-easily-swappable-parameters) +static int bpf_create_map(enum bpf_map_type map_type, int key_size, int value_size, + int max_entries, int map_flags) { union bpf_attr attr; memset(&attr, 0, sizeof(attr)); @@ -96,56 +97,56 @@ int bpf_create_map(enum bpf_map_type map_type, int key_size, int value_size, return syscall(SYS_bpf, BPF_MAP_CREATE, &attr, sizeof(attr)); } -int bpf_update_elem(int fd, void *key, void *value, unsigned long long flags) +static int bpf_update_elem(int descriptor, void *key, void *value, unsigned long long flags) { union bpf_attr attr; memset(&attr, 0, sizeof(attr)); - attr.map_fd = fd; + attr.map_fd = descriptor; attr.key = ptr_to_u64(key); attr.value = ptr_to_u64(value); attr.flags = flags; return syscall(SYS_bpf, BPF_MAP_UPDATE_ELEM, &attr, sizeof(attr)); } -int bpf_lookup_elem(int fd, void *key, void *value) +static int bpf_lookup_elem(int descriptor, void *key, void *value) { union bpf_attr attr; memset(&attr, 0, sizeof(attr)); - attr.map_fd = fd; + attr.map_fd = descriptor; attr.key = ptr_to_u64(key); attr.value = ptr_to_u64(value); return syscall(SYS_bpf, BPF_MAP_LOOKUP_ELEM, &attr, sizeof(attr)); } -int bpf_delete_elem(int fd, void *key) +static int bpf_delete_elem(int descriptor, void *key) { union bpf_attr attr; memset(&attr, 0, sizeof(attr)); - attr.map_fd = fd; + attr.map_fd = descriptor; attr.key = ptr_to_u64(key); return syscall(SYS_bpf, BPF_MAP_DELETE_ELEM, &attr, sizeof(attr)); } -int bpf_get_next_key(int fd, void *key, void *next_key) +static int bpf_get_next_key(int descriptor, void *key, void *next_key) { union bpf_attr attr; memset(&attr, 0, sizeof(attr)); - attr.map_fd = fd; + attr.map_fd = descriptor; attr.key = ptr_to_u64(key); attr.next_key = ptr_to_u64(next_key); return syscall(SYS_bpf, BPF_MAP_GET_NEXT_KEY, &attr, sizeof(attr)); } -int bpf_prog_load(enum bpf_prog_type prog_type, - const struct bpf_insn *insns, int prog_len, - const char *license, int kern_version) +static int bpf_prog_load(enum bpf_prog_type prog_type, + const struct bpf_insn *insns, size_t prog_len, + const char *license, int kern_version) { char log_buf[65535]; union bpf_attr attr; memset(&attr, 0, sizeof(attr)); attr.prog_type = prog_type; attr.insns = ptr_to_u64((void *) insns); - attr.insn_cnt = prog_len / sizeof(struct bpf_insn); + attr.insn_cnt = static_cast(prog_len / sizeof(struct bpf_insn)); attr.license = ptr_to_u64((void *) license); attr.log_buf = ptr_to_u64(log_buf); attr.log_size = sizeof(log_buf); @@ -337,15 +338,15 @@ BPFFilter::Map::Map(const BPFFilter::MapConfiguration& config, BPFFilter::MapFor static FDWrapper loadProgram(const struct bpf_insn* filter, size_t filterSize) { - auto fd = FDWrapper(bpf_prog_load(BPF_PROG_TYPE_SOCKET_FILTER, - filter, - filterSize, - "GPL", - 0)); - if (fd.getHandle() == -1) { + auto descriptor = FDWrapper(bpf_prog_load(BPF_PROG_TYPE_SOCKET_FILTER, + filter, + filterSize, + "GPL", + 0)); + if (descriptor.getHandle() == -1) { throw std::runtime_error("error loading BPF filter: " + stringerror()); } - return fd; + return descriptor; } @@ -428,8 +429,8 @@ BPFFilter::BPFFilter(std::unordered_map& configs, void BPFFilter::addSocket(int sock) { - int fd = d_mainfilter.getHandle(); - int res = setsockopt(sock, SOL_SOCKET, SO_ATTACH_BPF, &fd, sizeof(fd)); + int descriptor = d_mainfilter.getHandle(); + int res = setsockopt(sock, SOL_SOCKET, SO_ATTACH_BPF, &descriptor, sizeof(descriptor)); if (res != 0) { throw std::runtime_error("Error attaching BPF filter to this socket: " + stringerror()); @@ -438,8 +439,8 @@ void BPFFilter::addSocket(int sock) void BPFFilter::removeSocket(int sock) { - int fd = d_mainfilter.getHandle(); - int res = setsockopt(sock, SOL_SOCKET, SO_DETACH_BPF, &fd, sizeof(fd)); + int descriptor = d_mainfilter.getHandle(); + int res = setsockopt(sock, SOL_SOCKET, SO_DETACH_BPF, &descriptor, sizeof(descriptor)); if (res != 0) { throw std::runtime_error("Error detaching BPF filter from this socket: " + stringerror()); diff --git a/pdns/dnsdist-cache.cc b/pdns/dnsdist-cache.cc deleted file mode 100644 index c3b0e75ef68d..000000000000 --- a/pdns/dnsdist-cache.cc +++ /dev/null @@ -1,628 +0,0 @@ -/* - * This file is part of PowerDNS or dnsdist. - * Copyright -- PowerDNS.COM B.V. and its contributors - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of version 2 of the GNU General Public License as - * published by the Free Software Foundation. - * - * In addition, for the avoidance of any doubt, permission is granted to - * link this program with OpenSSL and to (re)distribute the binaries - * produced as the result of such linking. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ -#include - -#include "dnsdist.hh" -#include "dolog.hh" -#include "dnsparser.hh" -#include "dnsdist-cache.hh" -#include "dnsdist-ecs.hh" -#include "ednssubnet.hh" -#include "packetcache.hh" - -DNSDistPacketCache::DNSDistPacketCache(size_t maxEntries, uint32_t maxTTL, uint32_t minTTL, uint32_t tempFailureTTL, uint32_t maxNegativeTTL, uint32_t staleTTL, bool dontAge, uint32_t shards, bool deferrableInsertLock, bool parseECS): d_maxEntries(maxEntries), d_shardCount(shards), d_maxTTL(maxTTL), d_tempFailureTTL(tempFailureTTL), d_maxNegativeTTL(maxNegativeTTL), d_minTTL(minTTL), d_staleTTL(staleTTL), d_dontAge(dontAge), d_deferrableInsertLock(deferrableInsertLock), d_parseECS(parseECS) -{ - if (d_maxEntries == 0) { - throw std::runtime_error("Trying to create a 0-sized packet-cache"); - } - - d_shards.resize(d_shardCount); - - /* we reserve maxEntries + 1 to avoid rehashing from occurring - when we get to maxEntries, as it means a load factor of 1 */ - for (auto& shard : d_shards) { - shard.setSize((maxEntries / d_shardCount) + 1); - } -} - -bool DNSDistPacketCache::getClientSubnet(const PacketBuffer& packet, size_t qnameWireLength, boost::optional& subnet) -{ - uint16_t optRDPosition; - size_t remaining = 0; - - int res = getEDNSOptionsStart(packet, qnameWireLength, &optRDPosition, &remaining); - - if (res == 0) { - size_t ecsOptionStartPosition = 0; - size_t ecsOptionSize = 0; - - res = getEDNSOption(reinterpret_cast(&packet.at(optRDPosition)), remaining, EDNSOptionCode::ECS, &ecsOptionStartPosition, &ecsOptionSize); - - if (res == 0 && ecsOptionSize > (EDNS_OPTION_CODE_SIZE + EDNS_OPTION_LENGTH_SIZE)) { - - EDNSSubnetOpts eso; - if (getEDNSSubnetOptsFromString(reinterpret_cast(&packet.at(optRDPosition + ecsOptionStartPosition + (EDNS_OPTION_CODE_SIZE + EDNS_OPTION_LENGTH_SIZE))), ecsOptionSize - (EDNS_OPTION_CODE_SIZE + EDNS_OPTION_LENGTH_SIZE), &eso) == true) { - subnet = eso.source; - return true; - } - } - } - - return false; -} - -bool DNSDistPacketCache::cachedValueMatches(const CacheValue& cachedValue, uint16_t queryFlags, const DNSName& qname, uint16_t qtype, uint16_t qclass, bool receivedOverUDP, bool dnssecOK, const boost::optional& subnet) const -{ - if (cachedValue.queryFlags != queryFlags || cachedValue.dnssecOK != dnssecOK || cachedValue.receivedOverUDP != receivedOverUDP || cachedValue.qtype != qtype || cachedValue.qclass != qclass || cachedValue.qname != qname) { - return false; - } - - if (d_parseECS && cachedValue.subnet != subnet) { - return false; - } - - return true; -} - -void DNSDistPacketCache::insertLocked(CacheShard& shard, std::unordered_map& map, uint32_t key, CacheValue& newValue) -{ - /* check again now that we hold the lock to prevent a race */ - if (map.size() >= (d_maxEntries / d_shardCount)) { - return; - } - - std::unordered_map::iterator it; - bool result; - std::tie(it, result) = map.insert({key, newValue}); - - if (result) { - ++shard.d_entriesCount; - return; - } - - /* in case of collision, don't override the existing entry - except if it has expired */ - CacheValue& value = it->second; - bool wasExpired = value.validity <= newValue.added; - - if (!wasExpired && !cachedValueMatches(value, newValue.queryFlags, newValue.qname, newValue.qtype, newValue.qclass, newValue.receivedOverUDP, newValue.dnssecOK, newValue.subnet)) { - ++d_insertCollisions; - return; - } - - /* if the existing entry had a longer TTD, keep it */ - if (newValue.validity <= value.validity) { - return; - } - - value = newValue; -} - -void DNSDistPacketCache::insert(uint32_t key, const boost::optional& subnet, uint16_t queryFlags, bool dnssecOK, const DNSName& qname, uint16_t qtype, uint16_t qclass, const PacketBuffer& response, bool receivedOverUDP, uint8_t rcode, boost::optional tempFailureTTL) -{ - if (response.size() < sizeof(dnsheader) || response.size() > getMaximumEntrySize()) { - return; - } - - if (qtype == QType::AXFR || qtype == QType::IXFR) { - return; - } - - uint32_t minTTL; - - if (rcode == RCode::ServFail || rcode == RCode::Refused) { - minTTL = tempFailureTTL == boost::none ? d_tempFailureTTL : *tempFailureTTL; - if (minTTL == 0) { - return; - } - } - else { - bool seenAuthSOA = false; - minTTL = getMinTTL(reinterpret_cast(response.data()), response.size(), &seenAuthSOA); - - /* no TTL found, we don't want to cache this */ - if (minTTL == std::numeric_limits::max()) { - return; - } - - if (rcode == RCode::NXDomain || (rcode == RCode::NoError && seenAuthSOA)) { - minTTL = std::min(minTTL, d_maxNegativeTTL); - } - else if (minTTL > d_maxTTL) { - minTTL = d_maxTTL; - } - - if (minTTL < d_minTTL) { - ++d_ttlTooShorts; - return; - } - } - - uint32_t shardIndex = getShardIndex(key); - - if (d_shards.at(shardIndex).d_entriesCount >= (d_maxEntries / d_shardCount)) { - return; - } - - const time_t now = time(nullptr); - time_t newValidity = now + minTTL; - CacheValue newValue; - newValue.qname = qname; - newValue.qtype = qtype; - newValue.qclass = qclass; - newValue.queryFlags = queryFlags; - newValue.len = response.size(); - newValue.validity = newValidity; - newValue.added = now; - newValue.receivedOverUDP = receivedOverUDP; - newValue.dnssecOK = dnssecOK; - newValue.value = std::string(response.begin(), response.end()); - newValue.subnet = subnet; - - auto& shard = d_shards.at(shardIndex); - - if (d_deferrableInsertLock) { - auto w = shard.d_map.try_write_lock(); - - if (!w.owns_lock()) { - ++d_deferredInserts; - return; - } - insertLocked(shard, *w, key, newValue); - } - else { - auto w = shard.d_map.write_lock(); - - insertLocked(shard, *w, key, newValue); - } -} - -bool DNSDistPacketCache::get(DNSQuestion& dq, uint16_t queryId, uint32_t* keyOut, boost::optional& subnet, bool dnssecOK, bool receivedOverUDP, uint32_t allowExpired, bool skipAging, bool truncatedOK, bool recordMiss) -{ - if (dq.ids.qtype == QType::AXFR || dq.ids.qtype == QType::IXFR) { - ++d_misses; - return false; - } - - const auto& dnsQName = dq.ids.qname.getStorage(); - uint32_t key = getKey(dnsQName, dq.ids.qname.wirelength(), dq.getData(), receivedOverUDP); - - if (keyOut) { - *keyOut = key; - } - - if (d_parseECS) { - getClientSubnet(dq.getData(), dq.ids.qname.wirelength(), subnet); - } - - uint32_t shardIndex = getShardIndex(key); - time_t now = time(nullptr); - time_t age; - bool stale = false; - auto& response = dq.getMutableData(); - auto& shard = d_shards.at(shardIndex); - { - auto map = shard.d_map.try_read_lock(); - if (!map.owns_lock()) { - ++d_deferredLookups; - return false; - } - - std::unordered_map::const_iterator it = map->find(key); - if (it == map->end()) { - if (recordMiss) { - ++d_misses; - } - return false; - } - - const CacheValue& value = it->second; - if (value.validity <= now) { - if ((now - value.validity) >= static_cast(allowExpired)) { - if (recordMiss) { - ++d_misses; - } - return false; - } - else { - stale = true; - } - } - - if (value.len < sizeof(dnsheader)) { - return false; - } - - /* check for collision */ - if (!cachedValueMatches(value, *(getFlagsFromDNSHeader(dq.getHeader().get())), dq.ids.qname, dq.ids.qtype, dq.ids.qclass, receivedOverUDP, dnssecOK, subnet)) { - ++d_lookupCollisions; - return false; - } - - if (!truncatedOK) { - dnsheader dh; - memcpy(&dh, value.value.data(), sizeof(dh)); - if (dh.tc != 0) { - return false; - } - } - - response.resize(value.len); - memcpy(&response.at(0), &queryId, sizeof(queryId)); - memcpy(&response.at(sizeof(queryId)), &value.value.at(sizeof(queryId)), sizeof(dnsheader) - sizeof(queryId)); - - if (value.len == sizeof(dnsheader)) { - /* DNS header only, our work here is done */ - ++d_hits; - return true; - } - - const size_t dnsQNameLen = dnsQName.length(); - if (value.len < (sizeof(dnsheader) + dnsQNameLen)) { - return false; - } - - memcpy(&response.at(sizeof(dnsheader)), dnsQName.c_str(), dnsQNameLen); - if (value.len > (sizeof(dnsheader) + dnsQNameLen)) { - memcpy(&response.at(sizeof(dnsheader) + dnsQNameLen), &value.value.at(sizeof(dnsheader) + dnsQNameLen), value.len - (sizeof(dnsheader) + dnsQNameLen)); - } - - if (!stale) { - age = now - value.added; - } - else { - age = (value.validity - value.added) - d_staleTTL; - } - } - - if (!d_dontAge && !skipAging) { - if (!stale) { - // coverity[store_truncates_time_t] - dnsheader_aligned dh_aligned(response.data()); - ageDNSPacket(reinterpret_cast(&response[0]), response.size(), age, dh_aligned); - } - else { - editDNSPacketTTL(reinterpret_cast(&response[0]), response.size(), - [staleTTL = d_staleTTL](uint8_t /* section */, uint16_t /* class_ */, uint16_t /* type */, uint32_t /* ttl */) { return staleTTL; }); - } - } - - ++d_hits; - return true; -} - -/* Remove expired entries, until the cache has at most - upTo entries in it. - If the cache has more than one shard, we will try hard - to make sure that every shard has free space remaining. -*/ -size_t DNSDistPacketCache::purgeExpired(size_t upTo, const time_t now) -{ - const size_t maxPerShard = upTo / d_shardCount; - - size_t removed = 0; - - ++d_cleanupCount; - for (auto& shard : d_shards) { - auto map = shard.d_map.write_lock(); - if (map->size() <= maxPerShard) { - continue; - } - - size_t toRemove = map->size() - maxPerShard; - - for (auto it = map->begin(); toRemove > 0 && it != map->end(); ) { - const CacheValue& value = it->second; - - if (value.validity <= now) { - it = map->erase(it); - --toRemove; - --shard.d_entriesCount; - ++removed; - } else { - ++it; - } - } - } - - return removed; -} - -/* Remove all entries, keeping only upTo - entries in the cache. - If the cache has more than one shard, we will try hard - to make sure that every shard has free space remaining. -*/ -size_t DNSDistPacketCache::expunge(size_t upTo) -{ - const size_t maxPerShard = upTo / d_shardCount; - - size_t removed = 0; - - for (auto& shard : d_shards) { - auto map = shard.d_map.write_lock(); - - if (map->size() <= maxPerShard) { - continue; - } - - size_t toRemove = map->size() - maxPerShard; - - auto beginIt = map->begin(); - auto endIt = beginIt; - - if (map->size() >= toRemove) { - std::advance(endIt, toRemove); - map->erase(beginIt, endIt); - shard.d_entriesCount -= toRemove; - removed += toRemove; - } - else { - removed += map->size(); - map->clear(); - shard.d_entriesCount = 0; - } - } - - return removed; -} - -size_t DNSDistPacketCache::expungeByName(const DNSName& name, uint16_t qtype, bool suffixMatch) -{ - size_t removed = 0; - - for (auto& shard : d_shards) { - auto map = shard.d_map.write_lock(); - - for(auto it = map->begin(); it != map->end(); ) { - const CacheValue& value = it->second; - - if ((value.qname == name || (suffixMatch && value.qname.isPartOf(name))) && (qtype == QType::ANY || qtype == value.qtype)) { - it = map->erase(it); - --shard.d_entriesCount; - ++removed; - } else { - ++it; - } - } - } - - return removed; -} - -bool DNSDistPacketCache::isFull() -{ - return (getSize() >= d_maxEntries); -} - -uint64_t DNSDistPacketCache::getSize() -{ - uint64_t count = 0; - - for (auto& shard : d_shards) { - count += shard.d_entriesCount; - } - - return count; -} - -uint32_t DNSDistPacketCache::getMinTTL(const char* packet, uint16_t length, bool* seenNoDataSOA) -{ - return getDNSPacketMinTTL(packet, length, seenNoDataSOA); -} - -uint32_t DNSDistPacketCache::getKey(const DNSName::string_t& qname, size_t qnameWireLength, const PacketBuffer& packet, bool receivedOverUDP) -{ - uint32_t result = 0; - /* skip the query ID */ - if (packet.size() < sizeof(dnsheader)) { - throw std::range_error("Computing packet cache key for an invalid packet size (" + std::to_string(packet.size()) +")"); - } - - result = burtle(&packet.at(2), sizeof(dnsheader) - 2, result); - result = burtleCI((const unsigned char*) qname.c_str(), qname.length(), result); - if (packet.size() < sizeof(dnsheader) + qnameWireLength) { - throw std::range_error("Computing packet cache key for an invalid packet (" + std::to_string(packet.size()) + " < " + std::to_string(sizeof(dnsheader) + qnameWireLength) + ")"); - } - if (packet.size() > ((sizeof(dnsheader) + qnameWireLength))) { - if (!d_optionsToSkip.empty()) { - /* skip EDNS options if any */ - result = PacketCache::hashAfterQname(std::string_view(reinterpret_cast(packet.data()), packet.size()), result, sizeof(dnsheader) + qnameWireLength, d_optionsToSkip); - } - else { - result = burtle(&packet.at(sizeof(dnsheader) + qnameWireLength), packet.size() - (sizeof(dnsheader) + qnameWireLength), result); - } - } - result = burtle((const unsigned char*) &receivedOverUDP, sizeof(receivedOverUDP), result); - return result; -} - -uint32_t DNSDistPacketCache::getShardIndex(uint32_t key) const -{ - return key % d_shardCount; -} - -string DNSDistPacketCache::toString() -{ - return std::to_string(getSize()) + "/" + std::to_string(d_maxEntries); -} - -uint64_t DNSDistPacketCache::getEntriesCount() -{ - return getSize(); -} - -uint64_t DNSDistPacketCache::dump(int fd) -{ - auto fp = std::unique_ptr(fdopen(dup(fd), "w"), fclose); - if (fp == nullptr) { - return 0; - } - - fprintf(fp.get(), "; dnsdist's packet cache dump follows\n;\n"); - - uint64_t count = 0; - time_t now = time(nullptr); - for (auto& shard : d_shards) { - auto map = shard.d_map.read_lock(); - - for (const auto& entry : *map) { - const CacheValue& value = entry.second; - count++; - - try { - uint8_t rcode = 0; - if (value.len >= sizeof(dnsheader)) { - dnsheader dh; - memcpy(&dh, value.value.data(), sizeof(dnsheader)); - rcode = dh.rcode; - } - - fprintf(fp.get(), "%s %" PRId64 " %s ; rcode %" PRIu8 ", key %" PRIu32 ", length %" PRIu16 ", received over UDP %d, added %" PRId64 "\n", value.qname.toString().c_str(), static_cast(value.validity - now), QType(value.qtype).toString().c_str(), rcode, entry.first, value.len, value.receivedOverUDP, static_cast(value.added)); - } - catch(...) { - fprintf(fp.get(), "; error printing '%s'\n", value.qname.empty() ? "EMPTY" : value.qname.toString().c_str()); - } - } - } - - return count; -} - -void DNSDistPacketCache::setSkippedOptions(const std::unordered_set& optionsToSkip) -{ - d_optionsToSkip = optionsToSkip; -} - -std::set DNSDistPacketCache::getDomainsContainingRecords(const ComboAddress& addr) -{ - std::set domains; - - for (auto& shard : d_shards) { - auto map = shard.d_map.read_lock(); - - for (const auto& entry : *map) { - const CacheValue& value = entry.second; - - try { - dnsheader dh; - if (value.len < sizeof(dnsheader)) { - continue; - } - - memcpy(&dh, value.value.data(), sizeof(dnsheader)); - if (dh.rcode != RCode::NoError || (dh.ancount == 0 && dh.nscount == 0 && dh.arcount == 0)) { - continue; - } - - bool found = false; - bool valid = visitDNSPacket(value.value, [addr, &found](uint8_t /* section */, uint16_t qclass, uint16_t qtype, uint32_t /* ttl */, uint16_t rdatalength, const char* rdata) { - if (qtype == QType::A && qclass == QClass::IN && addr.isIPv4() && rdatalength == 4 && rdata != nullptr) { - ComboAddress parsed; - parsed.sin4.sin_family = AF_INET; - memcpy(&parsed.sin4.sin_addr.s_addr, rdata, rdatalength); - if (parsed == addr) { - found = true; - return true; - } - } - else if (qtype == QType::AAAA && qclass == QClass::IN && addr.isIPv6() && rdatalength == 16 && rdata != nullptr) { - ComboAddress parsed; - parsed.sin6.sin6_family = AF_INET6; - memcpy(&parsed.sin6.sin6_addr.s6_addr, rdata, rdatalength); - if (parsed == addr) { - found = true; - return true; - } - } - - return false; - }); - - if (valid && found) { - domains.insert(value.qname); - } - } - catch (...) { - continue; - } - } - } - - return domains; -} - -std::set DNSDistPacketCache::getRecordsForDomain(const DNSName& domain) -{ - std::set addresses; - - for (auto& shard : d_shards) { - auto map = shard.d_map.read_lock(); - - for (const auto& entry : *map) { - const CacheValue& value = entry.second; - - try { - if (value.qname != domain) { - continue; - } - - dnsheader dh; - if (value.len < sizeof(dnsheader)) { - continue; - } - - memcpy(&dh, value.value.data(), sizeof(dnsheader)); - if (dh.rcode != RCode::NoError || (dh.ancount == 0 && dh.nscount == 0 && dh.arcount == 0)) { - continue; - } - - visitDNSPacket(value.value, [&addresses](uint8_t /* section */, uint16_t qclass, uint16_t qtype, uint32_t /* ttl */, uint16_t rdatalength, const char* rdata) { - if (qtype == QType::A && qclass == QClass::IN && rdatalength == 4 && rdata != nullptr) { - ComboAddress parsed; - parsed.sin4.sin_family = AF_INET; - memcpy(&parsed.sin4.sin_addr.s_addr, rdata, rdatalength); - addresses.insert(parsed); - } - else if (qtype == QType::AAAA && qclass == QClass::IN && rdatalength == 16 && rdata != nullptr) { - ComboAddress parsed; - parsed.sin6.sin6_family = AF_INET6; - memcpy(&parsed.sin6.sin6_addr.s6_addr, rdata, rdatalength); - addresses.insert(parsed); - } - - return false; - }); - } - catch (...) { - continue; - } - } - } - - return addresses; -} - -void DNSDistPacketCache::setMaximumEntrySize(size_t maxSize) -{ - d_maximumEntrySize = maxSize; -} diff --git a/pdns/dnsdist-cache.hh b/pdns/dnsdist-cache.hh deleted file mode 100644 index 95667bd4cd56..000000000000 --- a/pdns/dnsdist-cache.hh +++ /dev/null @@ -1,156 +0,0 @@ -/* - * This file is part of PowerDNS or dnsdist. - * Copyright -- PowerDNS.COM B.V. and its contributors - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of version 2 of the GNU General Public License as - * published by the Free Software Foundation. - * - * In addition, for the avoidance of any doubt, permission is granted to - * link this program with OpenSSL and to (re)distribute the binaries - * produced as the result of such linking. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ -#pragma once - -#include -#include - -#include "iputils.hh" -#include "lock.hh" -#include "noinitvector.hh" -#include "stat_t.hh" -#include "ednsoptions.hh" - -struct DNSQuestion; - -class DNSDistPacketCache : boost::noncopyable -{ -public: - DNSDistPacketCache(size_t maxEntries, uint32_t maxTTL=86400, uint32_t minTTL=0, uint32_t tempFailureTTL=60, uint32_t maxNegativeTTL=3600, uint32_t staleTTL=60, bool dontAge=false, uint32_t shards=1, bool deferrableInsertLock=true, bool parseECS=false); - - void insert(uint32_t key, const boost::optional& subnet, uint16_t queryFlags, bool dnssecOK, const DNSName& qname, uint16_t qtype, uint16_t qclass, const PacketBuffer& response, bool receivedOverUDP, uint8_t rcode, boost::optional tempFailureTTL); - bool get(DNSQuestion& dq, uint16_t queryId, uint32_t* keyOut, boost::optional& subnet, bool dnssecOK, bool receivedOverUDP, uint32_t allowExpired = 0, bool skipAging = false, bool truncatedOK = true, bool recordMiss = true); - size_t purgeExpired(size_t upTo, const time_t now); - size_t expunge(size_t upTo=0); - size_t expungeByName(const DNSName& name, uint16_t qtype=QType::ANY, bool suffixMatch=false); - bool isFull(); - string toString(); - uint64_t getSize(); - uint64_t getHits() const { return d_hits.load(); } - uint64_t getMisses() const { return d_misses.load(); } - uint64_t getDeferredLookups() const { return d_deferredLookups.load(); } - uint64_t getDeferredInserts() const { return d_deferredInserts.load(); } - uint64_t getLookupCollisions() const { return d_lookupCollisions.load(); } - uint64_t getInsertCollisions() const { return d_insertCollisions.load(); } - uint64_t getMaxEntries() const { return d_maxEntries; } - uint64_t getTTLTooShorts() const { return d_ttlTooShorts.load(); } - uint64_t getCleanupCount() const { return d_cleanupCount.load(); } - uint64_t getEntriesCount(); - uint64_t dump(int fd); - - /* get the list of domains (qnames) that contains the given address in an A or AAAA record */ - std::set getDomainsContainingRecords(const ComboAddress& addr); - /* get the list of IP addresses contained in A or AAAA for a given domains (qname) */ - std::set getRecordsForDomain(const DNSName& domain); - - void setSkippedOptions(const std::unordered_set& optionsToSkip); - - bool isECSParsingEnabled() const { return d_parseECS; } - - bool keepStaleData() const - { - return d_keepStaleData; - } - void setKeepStaleData(bool keep) - { - d_keepStaleData = keep; - } - - void setECSParsingEnabled(bool enabled) - { - d_parseECS = enabled; - } - - void setMaximumEntrySize(size_t maxSize); - size_t getMaximumEntrySize() const { return d_maximumEntrySize; } - - uint32_t getKey(const DNSName::string_t& qname, size_t qnameWireLength, const PacketBuffer& packet, bool receivedOverUDP); - - static uint32_t getMinTTL(const char* packet, uint16_t length, bool* seenNoDataSOA); - static bool getClientSubnet(const PacketBuffer& packet, size_t qnameWireLength, boost::optional& subnet); - -private: - - struct CacheValue - { - time_t getTTD() const { return validity; } - std::string value; - DNSName qname; - boost::optional subnet; - uint16_t qtype{0}; - uint16_t qclass{0}; - uint16_t queryFlags{0}; - time_t added{0}; - time_t validity{0}; - uint16_t len{0}; - bool receivedOverUDP{false}; - bool dnssecOK{false}; - }; - - class CacheShard - { - public: - CacheShard() - { - } - CacheShard(const CacheShard& /* old */) - { - } - - void setSize(size_t maxSize) - { - d_map.write_lock()->reserve(maxSize); - } - - SharedLockGuarded> d_map; - std::atomic d_entriesCount{0}; - }; - - bool cachedValueMatches(const CacheValue& cachedValue, uint16_t queryFlags, const DNSName& qname, uint16_t qtype, uint16_t qclass, bool receivedOverUDP, bool dnssecOK, const boost::optional& subnet) const; - uint32_t getShardIndex(uint32_t key) const; - void insertLocked(CacheShard& shard, std::unordered_map& map, uint32_t key, CacheValue& newValue); - - std::vector d_shards; - std::unordered_set d_optionsToSkip{EDNSOptionCode::COOKIE}; - - pdns::stat_t d_deferredLookups{0}; - pdns::stat_t d_deferredInserts{0}; - pdns::stat_t d_hits{0}; - pdns::stat_t d_misses{0}; - pdns::stat_t d_insertCollisions{0}; - pdns::stat_t d_lookupCollisions{0}; - pdns::stat_t d_ttlTooShorts{0}; - pdns::stat_t d_cleanupCount{0}; - - const size_t d_maxEntries; - size_t d_maximumEntrySize{4096}; - const uint32_t d_shardCount; - const uint32_t d_maxTTL; - const uint32_t d_tempFailureTTL; - const uint32_t d_maxNegativeTTL; - const uint32_t d_minTTL; - const uint32_t d_staleTTL; - const bool d_dontAge; - const bool d_deferrableInsertLock; - bool d_parseECS; - bool d_keepStaleData{false}; -}; diff --git a/pdns/dnsdist-carbon.cc b/pdns/dnsdist-carbon.cc deleted file mode 100644 index 1b1c0fe5525b..000000000000 --- a/pdns/dnsdist-carbon.cc +++ /dev/null @@ -1,365 +0,0 @@ -/* - * This file is part of PowerDNS or dnsdist. - * Copyright -- PowerDNS.COM B.V. and its contributors - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of version 2 of the GNU General Public License as - * published by the Free Software Foundation. - * - * In addition, for the avoidance of any doubt, permission is granted to - * link this program with OpenSSL and to (re)distribute the binaries - * produced as the result of such linking. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ -#ifdef HAVE_CONFIG_H -#include "config.h" -#endif - -#include "dnsdist-carbon.hh" -#include "dnsdist.hh" -#include "dnsdist-backoff.hh" -#include "dnsdist-metrics.hh" - -#ifndef DISABLE_CARBON -#include "dolog.hh" -#include "sstuff.hh" -#include "threadname.hh" - -namespace dnsdist -{ - -LockGuarded Carbon::s_config; - -static bool doOneCarbonExport(const Carbon::Endpoint& endpoint) -{ - const auto& server = endpoint.server; - const std::string& namespace_name = endpoint.namespace_name; - const std::string& hostname = endpoint.ourname; - const std::string& instance_name = endpoint.instance_name; - - try { - Socket s(server.sin4.sin_family, SOCK_STREAM); - s.setNonBlocking(); - s.connect(server); // we do the connect so the attempt happens while we gather stats - ostringstream str; - - const time_t now = time(nullptr); - - { - auto entries = dnsdist::metrics::g_stats.entries.read_lock(); - for (const auto& entry : *entries) { - str << namespace_name << "." << hostname << "." << instance_name << "." << entry.d_name << ' '; - if (const auto& val = std::get_if(&entry.d_value)) { - str << (*val)->load(); - } - else if (const auto& adval = std::get_if*>(&entry.d_value)) { - str << (*adval)->load(); - } - else if (const auto& dval = std::get_if(&entry.d_value)) { - str << **dval; - } - else if (const auto& func = std::get_if(&entry.d_value)) { - str << (*func)(entry.d_name); - } - str << ' ' << now << "\r\n"; - } - } - - auto states = g_dstates.getLocal(); - for (const auto& state : *states) { - string serverName = state->getName().empty() ? state->d_config.remote.toStringWithPort() : state->getName(); - boost::replace_all(serverName, ".", "_"); - const string base = namespace_name + "." + hostname + "." + instance_name + ".servers." + serverName + "."; - str << base << "queries" << ' ' << state->queries.load() << " " << now << "\r\n"; - str << base << "responses" << ' ' << state->responses.load() << " " << now << "\r\n"; - str << base << "drops" << ' ' << state->reuseds.load() << " " << now << "\r\n"; - str << base << "latency" << ' ' << (state->d_config.availability != DownstreamState::Availability::Down ? state->latencyUsec / 1000.0 : 0) << " " << now << "\r\n"; - str << base << "latencytcp" << ' ' << (state->d_config.availability != DownstreamState::Availability::Down ? state->latencyUsecTCP / 1000.0 : 0) << " " << now << "\r\n"; - str << base << "senderrors" << ' ' << state->sendErrors.load() << " " << now << "\r\n"; - str << base << "outstanding" << ' ' << state->outstanding.load() << " " << now << "\r\n"; - str << base << "tcpdiedsendingquery" << ' ' << state->tcpDiedSendingQuery.load() << " " << now << "\r\n"; - str << base << "tcpdiedreaddingresponse" << ' ' << state->tcpDiedReadingResponse.load() << " " << now << "\r\n"; - str << base << "tcpgaveup" << ' ' << state->tcpGaveUp.load() << " " << now << "\r\n"; - str << base << "tcpreadimeouts" << ' ' << state->tcpReadTimeouts.load() << " " << now << "\r\n"; - str << base << "tcpwritetimeouts" << ' ' << state->tcpWriteTimeouts.load() << " " << now << "\r\n"; - str << base << "tcpconnecttimeouts" << ' ' << state->tcpConnectTimeouts.load() << " " << now << "\r\n"; - str << base << "tcpcurrentconnections" << ' ' << state->tcpCurrentConnections.load() << " " << now << "\r\n"; - str << base << "tcpmaxconcurrentconnections" << ' ' << state->tcpMaxConcurrentConnections.load() << " " << now << "\r\n"; - str << base << "tcpnewconnections" << ' ' << state->tcpNewConnections.load() << " " << now << "\r\n"; - str << base << "tcpreusedconnections" << ' ' << state->tcpReusedConnections.load() << " " << now << "\r\n"; - str << base << "tlsresumptions" << ' ' << state->tlsResumptions.load() << " " << now << "\r\n"; - str << base << "tcpavgqueriesperconnection" << ' ' << state->tcpAvgQueriesPerConnection.load() << " " << now << "\r\n"; - str << base << "tcpavgconnectionduration" << ' ' << state->tcpAvgConnectionDuration.load() << " " << now << "\r\n"; - str << base << "tcptoomanyconcurrentconnections" << ' ' << state->tcpTooManyConcurrentConnections.load() << " " << now << "\r\n"; - str << base << "healthcheckfailures" << ' ' << state->d_healthCheckMetrics.d_failures << " " << now << "\r\n"; - str << base << "healthcheckfailuresparsing" << ' ' << state->d_healthCheckMetrics.d_parseErrors << " " << now << "\r\n"; - str << base << "healthcheckfailurestimeout" << ' ' << state->d_healthCheckMetrics.d_timeOuts << " " << now << "\r\n"; - str << base << "healthcheckfailuresnetwork" << ' ' << state->d_healthCheckMetrics.d_networkErrors << " " << now << "\r\n"; - str << base << "healthcheckfailuresmismatch" << ' ' << state->d_healthCheckMetrics.d_mismatchErrors << " " << now << "\r\n"; - str << base << "healthcheckfailuresinvalid" << ' ' << state->d_healthCheckMetrics.d_invalidResponseErrors << " " << now << "\r\n"; - } - - std::map frontendDuplicates; - for (const auto& front : g_frontends) { - if (front->udpFD == -1 && front->tcpFD == -1) { - continue; - } - - string frontName = front->local.toStringWithPort() + (front->udpFD >= 0 ? "_udp" : "_tcp"); - boost::replace_all(frontName, ".", "_"); - auto dupPair = frontendDuplicates.insert({frontName, 1}); - if (!dupPair.second) { - frontName = frontName + "_" + std::to_string(dupPair.first->second); - ++(dupPair.first->second); - } - - const string base = namespace_name + "." + hostname + "." + instance_name + ".frontends." + frontName + "."; - str << base << "queries" << ' ' << front->queries.load() << " " << now << "\r\n"; - str << base << "responses" << ' ' << front->responses.load() << " " << now << "\r\n"; - str << base << "tcpdiedreadingquery" << ' ' << front->tcpDiedReadingQuery.load() << " " << now << "\r\n"; - str << base << "tcpdiedsendingresponse" << ' ' << front->tcpDiedSendingResponse.load() << " " << now << "\r\n"; - str << base << "tcpgaveup" << ' ' << front->tcpGaveUp.load() << " " << now << "\r\n"; - str << base << "tcpclienttimeouts" << ' ' << front->tcpClientTimeouts.load() << " " << now << "\r\n"; - str << base << "tcpdownstreamtimeouts" << ' ' << front->tcpDownstreamTimeouts.load() << " " << now << "\r\n"; - str << base << "tcpcurrentconnections" << ' ' << front->tcpCurrentConnections.load() << " " << now << "\r\n"; - str << base << "tcpmaxconcurrentconnections" << ' ' << front->tcpMaxConcurrentConnections.load() << " " << now << "\r\n"; - str << base << "tcpavgqueriesperconnection" << ' ' << front->tcpAvgQueriesPerConnection.load() << " " << now << "\r\n"; - str << base << "tcpavgconnectionduration" << ' ' << front->tcpAvgConnectionDuration.load() << " " << now << "\r\n"; - str << base << "tls10-queries" << ' ' << front->tls10queries.load() << " " << now << "\r\n"; - str << base << "tls11-queries" << ' ' << front->tls11queries.load() << " " << now << "\r\n"; - str << base << "tls12-queries" << ' ' << front->tls12queries.load() << " " << now << "\r\n"; - str << base << "tls13-queries" << ' ' << front->tls13queries.load() << " " << now << "\r\n"; - str << base << "tls-unknown-queries" << ' ' << front->tlsUnknownqueries.load() << " " << now << "\r\n"; - str << base << "tlsnewsessions" << ' ' << front->tlsNewSessions.load() << " " << now << "\r\n"; - str << base << "tlsresumptions" << ' ' << front->tlsResumptions.load() << " " << now << "\r\n"; - str << base << "tlsunknownticketkeys" << ' ' << front->tlsUnknownTicketKey.load() << " " << now << "\r\n"; - str << base << "tlsinactiveticketkeys" << ' ' << front->tlsInactiveTicketKey.load() << " " << now << "\r\n"; - - const TLSErrorCounters* errorCounters = nullptr; - if (front->tlsFrontend != nullptr) { - errorCounters = &front->tlsFrontend->d_tlsCounters; - } - else if (front->dohFrontend != nullptr) { - errorCounters = &front->dohFrontend->d_tlsContext.d_tlsCounters; - } - if (errorCounters != nullptr) { - str << base << "tlsdhkeytoosmall" << ' ' << errorCounters->d_dhKeyTooSmall << " " << now << "\r\n"; - str << base << "tlsinappropriatefallback" << ' ' << errorCounters->d_inappropriateFallBack << " " << now << "\r\n"; - str << base << "tlsnosharedcipher" << ' ' << errorCounters->d_noSharedCipher << " " << now << "\r\n"; - str << base << "tlsunknownciphertype" << ' ' << errorCounters->d_unknownCipherType << " " << now << "\r\n"; - str << base << "tlsunknownkeyexchangetype" << ' ' << errorCounters->d_unknownKeyExchangeType << " " << now << "\r\n"; - str << base << "tlsunknownprotocol" << ' ' << errorCounters->d_unknownProtocol << " " << now << "\r\n"; - str << base << "tlsunsupportedec" << ' ' << errorCounters->d_unsupportedEC << " " << now << "\r\n"; - str << base << "tlsunsupportedprotocol" << ' ' << errorCounters->d_unsupportedProtocol << " " << now << "\r\n"; - } - } - - auto localPools = g_pools.getLocal(); - for (const auto& entry : *localPools) { - string poolName = entry.first; - boost::replace_all(poolName, ".", "_"); - if (poolName.empty()) { - poolName = "_default_"; - } - const string base = namespace_name + "." + hostname + "." + instance_name + ".pools." + poolName + "."; - const std::shared_ptr pool = entry.second; - str << base << "servers" - << " " << pool->countServers(false) << " " << now << "\r\n"; - str << base << "servers-up" - << " " << pool->countServers(true) << " " << now << "\r\n"; - if (pool->packetCache != nullptr) { - const auto& cache = pool->packetCache; - str << base << "cache-size" - << " " << cache->getMaxEntries() << " " << now << "\r\n"; - str << base << "cache-entries" - << " " << cache->getEntriesCount() << " " << now << "\r\n"; - str << base << "cache-hits" - << " " << cache->getHits() << " " << now << "\r\n"; - str << base << "cache-misses" - << " " << cache->getMisses() << " " << now << "\r\n"; - str << base << "cache-deferred-inserts" - << " " << cache->getDeferredInserts() << " " << now << "\r\n"; - str << base << "cache-deferred-lookups" - << " " << cache->getDeferredLookups() << " " << now << "\r\n"; - str << base << "cache-lookup-collisions" - << " " << cache->getLookupCollisions() << " " << now << "\r\n"; - str << base << "cache-insert-collisions" - << " " << cache->getInsertCollisions() << " " << now << "\r\n"; - str << base << "cache-ttl-too-shorts" - << " " << cache->getTTLTooShorts() << " " << now << "\r\n"; - str << base << "cache-cleanup-count" - << " " << cache->getCleanupCount() << " " << now << "\r\n"; - } - } - -#ifdef HAVE_DNS_OVER_HTTPS - { - std::map dohFrontendDuplicates; - const string base = "dnsdist." + hostname + ".main.doh."; - for (const auto& doh : g_dohlocals) { - string name = doh->d_tlsContext.d_addr.toStringWithPort(); - boost::replace_all(name, ".", "_"); - boost::replace_all(name, ":", "_"); - boost::replace_all(name, "[", "_"); - boost::replace_all(name, "]", "_"); - - auto dupPair = dohFrontendDuplicates.insert({name, 1}); - if (!dupPair.second) { - name = name + "_" + std::to_string(dupPair.first->second); - ++(dupPair.first->second); - } - - vector> v{ - {"http-connects", doh->d_httpconnects}, - {"http1-queries", doh->d_http1Stats.d_nbQueries}, - {"http2-queries", doh->d_http2Stats.d_nbQueries}, - {"http1-200-responses", doh->d_http1Stats.d_nb200Responses}, - {"http2-200-responses", doh->d_http2Stats.d_nb200Responses}, - {"http1-400-responses", doh->d_http1Stats.d_nb400Responses}, - {"http2-400-responses", doh->d_http2Stats.d_nb400Responses}, - {"http1-403-responses", doh->d_http1Stats.d_nb403Responses}, - {"http2-403-responses", doh->d_http2Stats.d_nb403Responses}, - {"http1-500-responses", doh->d_http1Stats.d_nb500Responses}, - {"http2-500-responses", doh->d_http2Stats.d_nb500Responses}, - {"http1-502-responses", doh->d_http1Stats.d_nb502Responses}, - {"http2-502-responses", doh->d_http2Stats.d_nb502Responses}, - {"http1-other-responses", doh->d_http1Stats.d_nbOtherResponses}, - {"http2-other-responses", doh->d_http2Stats.d_nbOtherResponses}, - {"get-queries", doh->d_getqueries}, - {"post-queries", doh->d_postqueries}, - {"bad-requests", doh->d_badrequests}, - {"error-responses", doh->d_errorresponses}, - {"redirect-responses", doh->d_redirectresponses}, - {"valid-responses", doh->d_validresponses}}; - - for (const auto& item : v) { - str << base << name << "." << item.first << " " << item.second << " " << now << "\r\n"; - } - } - } -#endif /* HAVE_DNS_OVER_HTTPS */ - - { - std::string qname; - auto records = g_qcount.records.write_lock(); - for (const auto& record : *records) { - qname = record.first; - boost::replace_all(qname, ".", "_"); - str << "dnsdist.querycount." << qname << ".queries " << record.second << " " << now << "\r\n"; - } - records->clear(); - } - - const string msg = str.str(); - - int ret = waitForRWData(s.getHandle(), false, 1, 0); - if (ret <= 0) { - vinfolog("Unable to write data to carbon server on %s: %s", server.toStringWithPort(), (ret < 0 ? stringerror() : "Timeout")); - return false; - } - s.setBlocking(); - writen2(s.getHandle(), msg.c_str(), msg.size()); - } - catch (const std::exception& e) { - warnlog("Problem sending carbon data to %s: %s", server.toStringWithPort(), e.what()); - return false; - } - - return true; -} - -static void carbonHandler(Carbon::Endpoint&& endpoint) -{ - setThreadName("dnsdist/carbon"); - const auto intervalUSec = endpoint.interval * 1000 * 1000; - /* maximum interval between two attempts is 10 minutes */ - const ExponentialBackOffTimer backOffTimer(10 * 60); - - try { - uint8_t consecutiveFailures = 0; - do { - DTime dt; - dt.set(); - if (doOneCarbonExport(endpoint)) { - const auto elapsedUSec = dt.udiff(); - if (elapsedUSec < 0 || static_cast(elapsedUSec) <= intervalUSec) { - useconds_t toSleepUSec = intervalUSec - elapsedUSec; - usleep(toSleepUSec); - } - else { - vinfolog("Carbon export for %s took longer (%s us) than the configured interval (%d us)", endpoint.server.toStringWithPort(), elapsedUSec, intervalUSec); - } - consecutiveFailures = 0; - } - else { - const auto backOff = backOffTimer.get(consecutiveFailures); - if (consecutiveFailures < std::numeric_limits::max()) { - consecutiveFailures++; - } - vinfolog("Run for %s - %s failed, next attempt in %d", endpoint.server.toStringWithPort(), endpoint.ourname, backOff); - sleep(backOff); - } - } while (true); - } - catch (const PDNSException& e) { - errlog("Carbon thread for %s died, PDNSException: %s", endpoint.server.toStringWithPort(), e.reason); - } - catch (...) { - errlog("Carbon thread for %s died", endpoint.server.toStringWithPort()); - } -} - -bool Carbon::addEndpoint(Carbon::Endpoint&& endpoint) -{ - if (endpoint.ourname.empty()) { - try { - endpoint.ourname = getCarbonHostName(); - } - catch (const std::exception& e) { - throw std::runtime_error(std::string("The 'ourname' setting in 'carbonServer()' has not been set and we are unable to determine the system's hostname: ") + e.what()); - } - } - - auto config = s_config.lock(); - if (config->d_running) { - // we already started the threads, let's just spawn a new one - std::thread newHandler(carbonHandler, std::move(endpoint)); - newHandler.detach(); - } - else { - config->d_endpoints.push_back(std::move(endpoint)); - } - return true; -} - -void Carbon::run() -{ - auto config = s_config.lock(); - if (config->d_running) { - throw std::runtime_error("The carbon threads are already running"); - } - for (auto& endpoint : config->d_endpoints) { - std::thread newHandler(carbonHandler, std::move(endpoint)); - newHandler.detach(); - } - config->d_endpoints.clear(); - config->d_running = true; -} - -} -#endif /* DISABLE_CARBON */ - -static time_t s_start = time(nullptr); - -uint64_t uptimeOfProcess(const std::string& str) -{ - return time(nullptr) - s_start; -} diff --git a/pdns/dnsdist-console.cc b/pdns/dnsdist-console.cc deleted file mode 100644 index 0f9e085735f9..000000000000 --- a/pdns/dnsdist-console.cc +++ /dev/null @@ -1,1073 +0,0 @@ -/* - * This file is part of PowerDNS or dnsdist. - * Copyright -- PowerDNS.COM B.V. and its contributors - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of version 2 of the GNU General Public License as - * published by the Free Software Foundation. - * - * In addition, for the avoidance of any doubt, permission is granted to - * link this program with OpenSSL and to (re)distribute the binaries - * produced as the result of such linking. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ -#include "config.h" - -#include -// we need this to get the home directory of the current user -#include -#include - -#ifdef HAVE_LIBEDIT -#if defined (__OpenBSD__) || defined(__NetBSD__) -// If this is not undeffed, __attribute__ wil be redefined by /usr/include/readline/rlstdc.h -#undef __STRICT_ANSI__ -#include -#include -#else -#include -#endif -#endif /* HAVE_LIBEDIT */ - -#include "ext/json11/json11.hpp" - -#include "connection-management.hh" -#include "dolog.hh" -#include "dnsdist.hh" -#include "dnsdist-console.hh" -#include "dnsdist-crypto.hh" -#include "threadname.hh" - -GlobalStateHolder g_consoleACL; -vector > g_confDelta; -std::string g_consoleKey; -bool g_logConsoleConnections{true}; -bool g_consoleEnabled{false}; -uint32_t g_consoleOutputMsgMaxSize{10000000}; - -static ConcurrentConnectionManager s_connManager(100); - -class ConsoleConnection -{ -public: - ConsoleConnection(const ComboAddress& client, FDWrapper&& fileDesc): d_client(client), d_fileDesc(std::move(fileDesc)) - { - if (!s_connManager.registerConnection()) { - throw std::runtime_error("Too many concurrent console connections"); - } - } - ConsoleConnection(ConsoleConnection&& rhs) noexcept: d_client(rhs.d_client), d_fileDesc(std::move(rhs.d_fileDesc)) - { - } - - ConsoleConnection(const ConsoleConnection&) = delete; - ConsoleConnection& operator=(const ConsoleConnection&) = delete; - ConsoleConnection& operator=(ConsoleConnection&&) = delete; - - ~ConsoleConnection() - { - if (d_fileDesc.getHandle() != -1) { - s_connManager.releaseConnection(); - } - } - - [[nodiscard]] int getFD() const - { - return d_fileDesc.getHandle(); - } - - [[nodiscard]] const ComboAddress& getClient() const - { - return d_client; - } - -private: - ComboAddress d_client; - FDWrapper d_fileDesc; -}; - -void setConsoleMaximumConcurrentConnections(size_t max) -{ - s_connManager.setMaxConcurrentConnections(max); -} - -// MUST BE CALLED UNDER A LOCK - right now the LuaLock -static void feedConfigDelta(const std::string& line) -{ - if (line.empty()) { - return; - } - timeval now{}; - gettimeofday(&now, nullptr); - g_confDelta.emplace_back(now, line); -} - -#ifdef HAVE_LIBEDIT -static string historyFile(const bool &ignoreHOME = false) -{ - string ret; - - passwd pwd{}; - passwd *result{nullptr}; - std::array buf{}; - getpwuid_r(geteuid(), &pwd, buf.data(), buf.size(), &result); - - // NOLINTNEXTLINE(concurrency-mt-unsafe): we are not modifying the environment - const char *homedir = getenv("HOME"); - if (result != nullptr) { - ret = string(pwd.pw_dir); - } - if (homedir != nullptr && !ignoreHOME) { // $HOME overrides what the OS tells us - ret = string(homedir); - } - if (ret.empty()) { - ret = "."; // CWD if nothing works.. - } - ret.append("/.dnsdist_history"); - return ret; -} -#endif /* HAVE_LIBEDIT */ - -enum class ConsoleCommandResult : uint8_t { - Valid = 0, - ConnectionClosed, - TooLarge -}; - -static ConsoleCommandResult getMsgLen32(int fileDesc, uint32_t* len) -{ - try { - uint32_t raw{0}; - size_t ret = readn2(fileDesc, &raw, sizeof(raw)); - - if (ret != sizeof raw) { - return ConsoleCommandResult::ConnectionClosed; - } - - *len = ntohl(raw); - if (*len > g_consoleOutputMsgMaxSize) { - return ConsoleCommandResult::TooLarge; - } - - return ConsoleCommandResult::Valid; - } - catch (...) { - return ConsoleCommandResult::ConnectionClosed; - } -} - -static bool putMsgLen32(int fileDesc, uint32_t len) -{ - try - { - uint32_t raw = htonl(len); - size_t ret = writen2(fileDesc, &raw, sizeof raw); - return ret == sizeof raw; - } - catch(...) { - return false; - } -} - -static ConsoleCommandResult sendMessageToServer(int fileDesc, const std::string& line, dnsdist::crypto::authenticated::Nonce& readingNonce, dnsdist::crypto::authenticated::Nonce& writingNonce, const bool outputEmptyLine) -{ - string msg = dnsdist::crypto::authenticated::encryptSym(line, g_consoleKey, writingNonce); - const auto msgLen = msg.length(); - if (msgLen > std::numeric_limits::max()) { - cerr << "Encrypted message is too long to be sent to the server, "<< std::to_string(msgLen) << " > " << std::numeric_limits::max() << endl; - return ConsoleCommandResult::TooLarge; - } - - putMsgLen32(fileDesc, static_cast(msgLen)); - - if (!msg.empty()) { - writen2(fileDesc, msg); - } - - uint32_t len{0}; - auto commandResult = getMsgLen32(fileDesc, &len); - if (commandResult == ConsoleCommandResult::ConnectionClosed) { - cout << "Connection closed by the server." << endl; - return commandResult; - } - if (commandResult == ConsoleCommandResult::TooLarge) { - cerr << "Received a console message whose length (" << len << ") is exceeding the allowed one (" << g_consoleOutputMsgMaxSize << "), closing that connection" << endl; - return commandResult; - } - - if (len == 0) { - if (outputEmptyLine) { - cout << endl; - } - - return ConsoleCommandResult::Valid; - } - - msg.clear(); - msg.resize(len); - readn2(fileDesc, msg.data(), len); - msg = dnsdist::crypto::authenticated::decryptSym(msg, g_consoleKey, readingNonce); - cout << msg; - cout.flush(); - - return ConsoleCommandResult::Valid; -} - -void doClient(ComboAddress server, const std::string& command) -{ - if (!dnsdist::crypto::authenticated::isValidKey(g_consoleKey)) { - cerr << "The currently configured console key is not valid, please configure a valid key using the setKey() directive" << endl; - return; - } - - if (g_verbose) { - cout<<"Connecting to "< "); - rl_bind_key('\t',rl_complete); - if (sline == nullptr) { - break; - } - - string line(sline); - if (!line.empty() && line != lastline) { - add_history(sline); - history << sline < getNextConsoleLine(ofstream& history, std::string& lastline) -{ - char* sline = readline("> "); - rl_bind_key('\t', rl_complete); - if (sline == nullptr) { - return std::nullopt; - } - - string line(sline); - if (!line.empty() && line != lastline) { - add_history(sline); - history << sline < getNextConsoleLine() -{ - std::string line; - if (!std::getline(std::cin, line)) { - return std::nullopt; - } - return line; -} -#endif /* HAVE_LIBEDIT */ - -void doConsole() -{ -#ifdef HAVE_LIBEDIT - string histfile = historyFile(true); - { - ifstream history(histfile); - string line; - while (getline(history, line)) { - add_history(line.c_str()); - } - } - ofstream history(histfile, std::ios_base::app); - string lastline; -#endif /* HAVE_LIBEDIT */ - - for (;;) { -#ifdef HAVE_LIBEDIT - auto line = getNextConsoleLine(history, lastline); -#else /* HAVE_LIBEDIT */ - auto line = getNextConsoleLine(); -#endif /* HAVE_LIBEDIT */ - if (!line) { - break; - } - - if (*line == "quit") { - break; - } - if (*line == "help" || *line == "?") { - line = "help()"; - } - - string response; - try { - bool withReturn = true; - retry:; - try { - auto lua = g_lua.lock(); - g_outputBuffer.clear(); - resetLuaSideEffect(); - auto ret = lua->executeCode< - boost::optional< - boost::variant< - string, - shared_ptr, - ClientState*, - std::unordered_map - > - > - >(withReturn ? ("return "+*line) : *line); - if (ret) { - if (const auto* dsValue = boost::get>(&*ret)) { - if (*dsValue) { - cout<<(*dsValue)->getName()<(&*ret)) { - if (*csValue != nullptr) { - cout<<(*csValue)->local.toStringWithPort()<(&*ret)) { - cout<<*strValue< >(&*ret)) { - using namespace json11; - Json::object obj; - for (const auto& value : *mapValue) { - obj[value.first] = value.second; - } - Json out = obj; - cout< g_consoleKeywords{ - /* keyword, function, parameters, description */ - { "addACL", true, "netmask", "add to the ACL set who can use this server" }, - { "addAction", true, R"(DNS rule, DNS action [, {uuid="UUID", name="name"}])", "add a rule" }, - { "addBPFFilterDynBlocks", true, "addresses, dynbpf[[, seconds=10], msg]", "This is the eBPF equivalent of addDynBlocks(), blocking a set of addresses for (optionally) a number of seconds, using an eBPF dynamic filter" }, - { "addCapabilitiesToRetain", true, "capability or list of capabilities", "Linux capabilities to retain after startup, like CAP_BPF" }, - { "addConsoleACL", true, "netmask", "add a netmask to the console ACL" }, - { "addDNSCryptBind", true, R"('127.0.0.1:8443", "provider name", "/path/to/resolver.cert", "/path/to/resolver.key", {reusePort=false, tcpFastOpenQueueSize=0, interface="", cpus={}})", "listen to incoming DNSCrypt queries on 127.0.0.1 port 8443, with a provider name of `provider name`, using a resolver certificate and associated key stored respectively in the `resolver.cert` and `resolver.key` files. The fifth optional parameter is a table of parameters" }, - { "addDOHLocal", true, "addr, certFile, keyFile [, urls [, vars]]", "listen to incoming DNS over HTTPS queries on the specified address using the specified certificate and key. The last two parameters are tables" }, - { "addDOH3Local", true, "addr, certFile, keyFile [, vars]", "listen to incoming DNS over HTTP/3 queries on the specified address using the specified certificate and key. The last parameter is a table" }, - { "addDOQLocal", true, "addr, certFile, keyFile [, vars]", "listen to incoming DNS over QUIC queries on the specified address using the specified certificate and key. The last parameter is a table" }, - { "addDynamicBlock", true, "address, message[, action [, seconds [, clientIPMask [, clientIPPortMask]]]]", "block the supplied address with message `msg`, for `seconds` seconds (10 by default), applying `action` (default to the one set with `setDynBlocksAction()`)" }, - { "addDynBlocks", true, "addresses, message[, seconds[, action]]", "block the set of addresses with message `msg`, for `seconds` seconds (10 by default), applying `action` (default to the one set with `setDynBlocksAction()`)" }, - { "addDynBlockSMT", true, "names, message[, seconds [, action]]", "block the set of names with message `msg`, for `seconds` seconds (10 by default), applying `action` (default to the one set with `setDynBlocksAction()`)" }, - { "addLocal", true, R"(addr [, {doTCP=true, reusePort=false, tcpFastOpenQueueSize=0, interface="", cpus={}}])", "add `addr` to the list of addresses we listen on" }, - { "addCacheHitResponseAction", true, R"(DNS rule, DNS response action [, {uuid="UUID", name="name"}}])", "add a cache hit response rule" }, - { "addCacheInsertedResponseAction", true, R"(DNS rule, DNS response action [, {uuid="UUID", name="name"}}])", "add a cache inserted response rule" }, - { "addResponseAction", true, R"(DNS rule, DNS response action [, {uuid="UUID", name="name"}}])", "add a response rule" }, - { "addSelfAnsweredResponseAction", true, R"(DNS rule, DNS response action [, {uuid="UUID", name="name"}}])", "add a self-answered response rule" }, - { "addTLSLocal", true, "addr, certFile(s), keyFile(s) [,params]", "listen to incoming DNS over TLS queries on the specified address using the specified certificate (or list of) and key (or list of). The last parameter is a table" }, - { "AllowAction", true, "", "let these packets go through" }, - { "AllowResponseAction", true, "", "let these packets go through" }, - { "AllRule", true, "", "matches all traffic" }, - { "AndRule", true, "list of DNS rules", "matches if all sub-rules matches" }, - { "benchRule", true, "DNS Rule [, iterations [, suffix]]", "bench the specified DNS rule" }, - { "carbonServer", true, "serverIP, [ourname], [interval]", "report statistics to serverIP using our hostname, or 'ourname' if provided, every 'interval' seconds" }, - { "clearConsoleHistory", true, "", "clear the internal (in-memory) history of console commands" }, - { "clearDynBlocks", true, "", "clear all dynamic blocks" }, - { "clearQueryCounters", true, "", "clears the query counter buffer" }, - { "clearRules", true, "", "remove all current rules" }, - { "controlSocket", true, "addr", "open a control socket on this address / connect to this address in client mode" }, - { "ContinueAction", true, "action", "execute the specified action and continue the processing of the remaining rules, regardless of the return of the action" }, - { "declareMetric", true, "name, type, description [, prometheusName]", "Declare a custom metric" }, - { "decMetric", true, "name", "Decrement a custom metric" }, - { "DelayAction", true, "milliseconds", "delay the response by the specified amount of milliseconds (UDP-only)" }, - { "DelayResponseAction", true, "milliseconds", "delay the response by the specified amount of milliseconds (UDP-only)" }, - { "delta", true, "", "shows all commands entered that changed the configuration" }, - { "DNSSECRule", true, "", "matches queries with the DO bit set" }, - { "DnstapLogAction", true, "identity, FrameStreamLogger [, alterFunction]", "send the contents of this query to a FrameStreamLogger or RemoteLogger as dnstap. `alterFunction` is a callback, receiving a DNSQuestion and a DnstapMessage, that can be used to modify the dnstap message" }, - { "DnstapLogResponseAction", true, "identity, FrameStreamLogger [, alterFunction]", "send the contents of this response to a remote or FrameStreamLogger or RemoteLogger as dnstap. `alterFunction` is a callback, receiving a DNSResponse and a DnstapMessage, that can be used to modify the dnstap message" }, - { "DropAction", true, "", "drop these packets" }, - { "DropResponseAction", true, "", "drop these packets" }, - { "DSTPortRule", true, "port", "matches questions received to the destination port specified" }, - { "dumpStats", true, "", "print all statistics we gather" }, - { "dynBlockRulesGroup", true, "", "return a new DynBlockRulesGroup object" }, - { "EDNSVersionRule", true, "version", "matches queries with the specified EDNS version" }, - { "EDNSOptionRule", true, "optcode", "matches queries with the specified EDNS0 option present" }, - { "ERCodeAction", true, "ercode", "Reply immediately by turning the query into a response with the specified EDNS extended rcode" }, - { "ERCodeRule", true, "rcode", "matches responses with the specified extended rcode (EDNS0)" }, - { "exceedNXDOMAINs", true, "rate, seconds", "get set of addresses that exceed `rate` NXDOMAIN/s over `seconds` seconds" }, - { "exceedQRate", true, "rate, seconds", "get set of address that exceed `rate` queries/s over `seconds` seconds" }, - { "exceedQTypeRate", true, "type, rate, seconds", "get set of address that exceed `rate` queries/s for queries of type `type` over `seconds` seconds" }, - { "exceedRespByterate", true, "rate, seconds", "get set of addresses that exceeded `rate` bytes/s answers over `seconds` seconds" }, - { "exceedServFails", true, "rate, seconds", "get set of addresses that exceed `rate` servfails/s over `seconds` seconds" }, - { "firstAvailable", false, "", "picks the server with the lowest `order` that has not exceeded its QPS limit" }, - { "fixupCase", true, "bool", "if set (default to no), rewrite the first qname of the question part of the answer to match the one from the query. It is only useful when you have a downstream server that messes up the case of the question qname in the answer" }, - { "generateDNSCryptCertificate", true, R"("/path/to/providerPrivate.key", "/path/to/resolver.cert", "/path/to/resolver.key", serial, validFrom, validUntil)", "generate a new resolver private key and related certificate, valid from the `validFrom` timestamp until the `validUntil` one, signed with the provider private key" }, - { "generateDNSCryptProviderKeys", true, R"("/path/to/providerPublic.key", "/path/to/providerPrivate.key")", "generate a new provider keypair" }, - { "getAction", true, "n", "Returns the Action associated with rule n" }, - { "getBind", true, "n", "returns the listener at index n" }, - { "getBindCount", true, "", "returns the number of listeners all kinds" }, - { "getCacheHitResponseRule", true, "selector", "Return the cache-hit response rule corresponding to the selector, if any" }, - { "getCacheInsertedResponseRule", true, "selector", "Return the cache-inserted response rule corresponding to the selector, if any" }, - { "getCurrentTime", true, "", "returns the current time" }, - { "getDynamicBlocks", true, "", "returns a table of the current network-based dynamic blocks" }, - { "getDynamicBlocksSMT", true, "", "returns a table of the current suffix-based dynamic blocks" }, - { "getDNSCryptBind", true, "n", "return the `DNSCryptContext` object corresponding to the bind `n`" }, - { "getDNSCryptBindCount", true, "", "returns the number of DNSCrypt listeners" }, - { "getDOHFrontend", true, "n", "returns the DOH frontend with index n" }, - { "getDOHFrontendCount", true, "", "returns the number of DoH listeners" }, - { "getListOfAddressesOfNetworkInterface", true, "itf", "returns the list of addresses configured on a given network interface, as strings" }, - { "getListOfNetworkInterfaces", true, "", "returns the list of network interfaces present on the system, as strings" }, - { "getListOfRangesOfNetworkInterface", true, "itf", "returns the list of network ranges configured on a given network interface, as strings" }, - { "getMACAddress", true, "IP addr", "return the link-level address (MAC) corresponding to the supplied neighbour IP address, if known by the kernel" }, - { "getMetric", true, "name", "Get the value of a custom metric" }, - { "getOutgoingTLSSessionCacheSize", true, "", "returns the number of TLS sessions (for outgoing connections) currently cached" }, - { "getPool", true, "name", "return the pool named `name`, or \"\" for the default pool" }, - { "getPoolServers", true, "pool", "return servers part of this pool" }, - { "getPoolNames", true, "", "returns a table with all the pool names" }, - { "getQueryCounters", true, "[max=10]", "show current buffer of query counters, limited by 'max' if provided" }, - { "getResponseRing", true, "", "return the current content of the response ring" }, - { "getResponseRule", true, "selector", "Return the response rule corresponding to the selector, if any" }, - { "getRespRing", true, "", "return the qname/rcode content of the response ring" }, - { "getRule", true, "selector", "Return the rule corresponding to the selector, if any" }, - { "getSelfAnsweredResponseRule", true, "selector", "Return the self-answered response rule corresponding to the selector, if any" }, - { "getServer", true, "id", "returns server with index 'n' or whose uuid matches if 'id' is an UUID string" }, - { "getServers", true, "", "returns a table with all defined servers" }, - { "getStatisticsCounters", true, "", "returns a map of statistic counters" }, - { "getTopCacheHitResponseRules", true, "[top]", "return the `top` cache-hit response rules" }, - { "getTopCacheInsertedResponseRules", true, "[top]", "return the `top` cache-inserted response rules" }, - { "getTopResponseRules", true, "[top]", "return the `top` response rules" }, - { "getTopRules", true, "[top]", "return the `top` rules" }, - { "getTopSelfAnsweredResponseRules", true, "[top]", "return the `top` self-answered response rules" }, - { "getTLSContext", true, "n", "returns the TLS context with index n" }, - { "getTLSFrontend", true, "n", "returns the TLS frontend with index n" }, - { "getTLSFrontendCount", true, "", "returns the number of DoT listeners" }, - { "getVerbose", true, "", "get whether log messages at the verbose level will be logged" }, - { "grepq", true, R"(Netmask|DNS Name|100ms|{"::1", "powerdns.com", "100ms"} [, n] [,options])", "shows the last n queries and responses matching the specified client address or range (Netmask), or the specified DNS Name, or slower than 100ms" }, - { "hashPassword", true, "password [, workFactor]", "Returns a hashed and salted version of the supplied password, usable with 'setWebserverConfig()'"}, - { "HTTPHeaderRule", true, "name, regex", "matches DoH queries with a HTTP header 'name' whose content matches the regular expression 'regex'"}, - { "HTTPPathRegexRule", true, "regex", "matches DoH queries whose HTTP path matches 'regex'"}, - { "HTTPPathRule", true, "path", "matches DoH queries whose HTTP path is an exact match to 'path'"}, - { "HTTPStatusAction", true, "status, reason, body", "return an HTTP response"}, - { "inClientStartup", true, "", "returns true during console client parsing of configuration" }, - { "includeDirectory", true, "path", "include configuration files from `path`" }, - { "incMetric", true, "name", "Increment a custom metric" }, - { "KeyValueLookupKeyQName", true, "[wireFormat]", "Return a new KeyValueLookupKey object that, when passed to KeyValueStoreLookupAction or KeyValueStoreLookupRule, will return the qname of the query, either in wire format (default) or in plain text if 'wireFormat' is false" }, - { "KeyValueLookupKeySourceIP", true, "[v4Mask [, v6Mask [, includePort]]]", "Return a new KeyValueLookupKey object that, when passed to KeyValueStoreLookupAction or KeyValueStoreLookupRule, will return the (possibly bitmasked) source IP of the client in network byte-order." }, - { "KeyValueLookupKeySuffix", true, "[minLabels [,wireFormat]]", "Return a new KeyValueLookupKey object that, when passed to KeyValueStoreLookupAction or KeyValueStoreLookupRule, will return a vector of keys based on the labels of the qname in DNS wire format or plain text" }, - { "KeyValueLookupKeyTag", true, "tag", "Return a new KeyValueLookupKey object that, when passed to KeyValueStoreLookupAction or KeyValueStoreLookupRule, will return the value of the corresponding tag for this query, if it exists" }, - { "KeyValueStoreLookupAction", true, "kvs, lookupKey, destinationTag", "does a lookup into the key value store referenced by 'kvs' using the key returned by 'lookupKey', and storing the result if any into the tag named 'destinationTag'" }, - { "KeyValueStoreRangeLookupAction", true, "kvs, lookupKey, destinationTag", "does a range-based lookup into the key value store referenced by 'kvs' using the key returned by 'lookupKey', and storing the result if any into the tag named 'destinationTag'" }, - { "KeyValueStoreLookupRule", true, "kvs, lookupKey", "matches queries if the key is found in the specified Key Value store" }, - { "KeyValueStoreRangeLookupRule", true, "kvs, lookupKey", "matches queries if the key is found in the specified Key Value store" }, - { "leastOutstanding", false, "", "Send traffic to downstream server with least outstanding queries, with the lowest 'order', and within that the lowest recent latency"}, -#if defined(HAVE_LIBSSL) && !defined(HAVE_TLS_PROVIDERS) - { "loadTLSEngine", true, "engineName [, defaultString]", "Load the OpenSSL engine named 'engineName', setting the engine default string to 'defaultString' if supplied"}, -#endif -#if defined(HAVE_LIBSSL) && OPENSSL_VERSION_MAJOR >= 3 && defined(HAVE_TLS_PROVIDERS) - { "loadTLSProvider", true, "providerName", "Load the OpenSSL provider named 'providerName'"}, -#endif - { "LogAction", true, "[filename], [binary], [append], [buffered]", "Log a line for each query, to the specified file if any, to the console (require verbose) otherwise. When logging to a file, the `binary` optional parameter specifies whether we log in binary form (default) or in textual form, the `append` optional parameter specifies whether we open the file for appending or truncate each time (default), and the `buffered` optional parameter specifies whether writes to the file are buffered (default) or not." }, - { "LogResponseAction", true, "[filename], [append], [buffered]", "Log a line for each response, to the specified file if any, to the console (require verbose) otherwise. The `append` optional parameter specifies whether we open the file for appending or truncate each time (default), and the `buffered` optional parameter specifies whether writes to the file are buffered (default) or not." }, - { "LuaAction", true, "function", "Invoke a Lua function that accepts a DNSQuestion" }, - { "LuaFFIAction", true, "function", "Invoke a Lua FFI function that accepts a DNSQuestion" }, - { "LuaFFIPerThreadAction", true, "function", "Invoke a Lua FFI function that accepts a DNSQuestion, with a per-thread Lua context" }, - { "LuaFFIPerThreadResponseAction", true, "function", "Invoke a Lua FFI function that accepts a DNSResponse, with a per-thread Lua context" }, - { "LuaFFIResponseAction", true, "function", "Invoke a Lua FFI function that accepts a DNSResponse" }, - { "LuaFFIRule", true, "function", "Invoke a Lua FFI function that filters DNS questions" }, - { "LuaResponseAction", true, "function", "Invoke a Lua function that accepts a DNSResponse" }, - { "LuaRule", true, "function", "Invoke a Lua function that filters DNS questions" }, -#ifdef HAVE_IPCIPHER - { "makeIPCipherKey", true, "password", "generates a 16-byte key that can be used to pseudonymize IP addresses with IP cipher" }, -#endif /* HAVE_IPCIPHER */ - { "makeKey", true, "", "generate a new server access key, emit configuration line ready for pasting" }, - { "makeRule", true, "rule", "Make a NetmaskGroupRule() or a SuffixMatchNodeRule(), depending on how it is called" } , - { "MaxQPSIPRule", true, "qps, [v4Mask=32 [, v6Mask=64 [, burst=qps [, expiration=300 [, cleanupDelay=60 [, scanFraction=10 [, shards=10]]]]]]]", "matches traffic exceeding the qps limit per subnet" }, - { "MaxQPSRule", true, "qps", "matches traffic **not** exceeding this qps limit" }, - { "mvCacheHitResponseRule", true, "from, to", "move cache hit response rule 'from' to a position where it is in front of 'to'. 'to' can be one larger than the largest rule" }, - { "mvCacheHitResponseRuleToTop", true, "", "move the last cache hit response rule to the first position" }, - { "mvCacheInsertedResponseRule", true, "from, to", "move cache inserted response rule 'from' to a position where it is in front of 'to'. 'to' can be one larger than the largest rule" }, - { "mvCacheInsertedResponseRuleToTop", true, "", "move the last cache inserted response rule to the first position" }, - { "mvResponseRule", true, "from, to", "move response rule 'from' to a position where it is in front of 'to'. 'to' can be one larger than the largest rule" }, - { "mvResponseRuleToTop", true, "", "move the last response rule to the first position" }, - { "mvRule", true, "from, to", "move rule 'from' to a position where it is in front of 'to'. 'to' can be one larger than the largest rule, in which case the rule will be moved to the last position" }, - { "mvRuleToTop", true, "", "move the last rule to the first position" }, - { "mvSelfAnsweredResponseRule", true, "from, to", "move self-answered response rule 'from' to a position where it is in front of 'to'. 'to' can be one larger than the largest rule" }, - { "mvSelfAnsweredResponseRuleToTop", true, "", "move the last self-answered response rule to the first position" }, - { "NetmaskGroupRule", true, "nmg[, src]", "Matches traffic from/to the network range specified in nmg. Set the src parameter to false to match nmg against destination address instead of source address. This can be used to differentiate between clients" }, - { "newBPFFilter", true, "{ipv4MaxItems=int, ipv4PinnedPath=string, ipv6MaxItems=int, ipv6PinnedPath=string, cidr4MaxItems=int, cidr4PinnedPath=string, cidr6MaxItems=int, cidr6PinnedPath=string, qnamesMaxItems=int, qnamesPinnedPath=string, external=bool}", "Return a new eBPF socket filter with specified options." }, - { "newCA", true, "address", "Returns a ComboAddress based on `address`" }, -#ifdef HAVE_CDB - { "newCDBKVStore", true, "fname, refreshDelay", "Return a new KeyValueStore object associated to the corresponding CDB database" }, -#endif - { "newDNSName", true, "name", "make a DNSName based on this .-terminated name" }, - { "newDNSNameSet", true, "", "returns a new DNSNameSet" }, - { "newDynBPFFilter", true, "bpf", "Return a new dynamic eBPF filter associated to a given BPF Filter" }, - { "newFrameStreamTcpLogger", true, "addr [, options]", "create a FrameStream logger object writing to a TCP address (addr should be ip:port), to use with `DnstapLogAction()` and `DnstapLogResponseAction()`" }, - { "newFrameStreamUnixLogger", true, "socket [, options]", "create a FrameStream logger object writing to a local unix socket, to use with `DnstapLogAction()` and `DnstapLogResponseAction()`" }, -#ifdef HAVE_LMDB - { "newLMDBKVStore", true, "fname, dbName [, noLock]", "Return a new KeyValueStore object associated to the corresponding LMDB database" }, -#endif - { "newNMG", true, "", "Returns a NetmaskGroup" }, - { "newPacketCache", true, "maxEntries[, maxTTL=86400, minTTL=0, temporaryFailureTTL=60, staleTTL=60, dontAge=false, numberOfShards=1, deferrableInsertLock=true, options={}]", "return a new Packet Cache" }, - { "newQPSLimiter", true, "rate, burst", "configure a QPS limiter with that rate and that burst capacity" }, - { "newRemoteLogger", true, "address:port [, timeout=2, maxQueuedEntries=100, reconnectWaitTime=1]", "create a Remote Logger object, to use with `RemoteLogAction()` and `RemoteLogResponseAction()`" }, - { "newRuleAction", true, R"(DNS rule, DNS action [, {uuid="UUID", name="name"}])", "return a pair of DNS Rule and DNS Action, to be used with `setRules()`" }, - { "newServer", true, R"({address="ip:port", qps=1000, order=1, weight=10, pool="abuse", retries=5, tcpConnectTimeout=5, tcpSendTimeout=30, tcpRecvTimeout=30, checkName="a.root-servers.net.", checkType="A", maxCheckFailures=1, mustResolve=false, useClientSubnet=true, source="address|interface name|address@interface", sockets=1, reconnectOnUp=false})", "instantiate a server" }, - { "newServerPolicy", true, "name, function", "create a policy object from a Lua function" }, - { "newSuffixMatchNode", true, "", "returns a new SuffixMatchNode" }, - { "newSVCRecordParameters", true, "priority, target, mandatoryParams, alpns, noDefaultAlpn [, port [, ech [, ipv4hints [, ipv6hints [, additionalParameters ]]]]]", "return a new SVCRecordParameters object, to use with SpoofSVCAction" }, - { "NegativeAndSOAAction", 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" }, - { "NoneAction", true, "", "Does nothing. Subsequent rules are processed after this action" }, - { "NotRule", true, "selector", "Matches the traffic if the selector rule does not match" }, - { "OpcodeRule", true, "code", "Matches queries with opcode code. code can be directly specified as an integer, or one of the built-in DNSOpcodes" }, - { "OrRule", true, "selectors", "Matches the traffic if one or more of the the selectors rules does match" }, - { "PoolAction", true, "poolname [, stop]", "set the packet into the specified pool" }, - { "PoolAvailableRule", true, "poolname", "Check whether a pool has any servers available to handle queries" }, - { "PoolOutstandingRule", true, "poolname, limit", "Check whether a pool has outstanding queries above limit" }, - { "printDNSCryptProviderFingerprint", true, R"("/path/to/providerPublic.key")", "display the fingerprint of the provided resolver public key" }, - { "ProbaRule", true, "probability", "Matches queries with a given probability. 1.0 means always" }, - { "ProxyProtocolValueRule", true, "type [, value]", "matches queries with a specified Proxy Protocol TLV value of that type, optionally matching the content of the option as well" }, - { "QClassRule", true, "qclass", "Matches queries with the specified qclass. class can be specified as an integer or as one of the built-in DNSClass" }, - { "QNameLabelsCountRule", true, "min, max", "matches if the qname has less than `min` or more than `max` labels" }, - { "QNameRule", true, "qname", "matches queries with the specified qname" }, - { "QNameSetRule", true, "set", "Matches if the set contains exact qname" }, - { "QNameWireLengthRule", true, "min, max", "matches if the qname's length on the wire is less than `min` or more than `max` bytes" }, - { "QPSAction", true, "maxqps", "Drop a packet if it does exceed the maxqps queries per second limits. Letting the subsequent rules apply otherwise" }, - { "QPSPoolAction", true, "maxqps, poolname [, stop]", "Send the packet into the specified pool only if it does not exceed the maxqps queries per second limits. Letting the subsequent rules apply otherwise" }, - { "QTypeRule", true, "qtype", "matches queries with the specified qtype" }, - { "RCodeAction", true, "rcode", "Reply immediately by turning the query into a response with the specified rcode" }, - { "RCodeRule", true, "rcode", "matches responses with the specified rcode" }, - { "RDRule", true, "", "Matches queries with the RD flag set" }, - { "RecordsCountRule", true, "section, minCount, maxCount", "Matches if there is at least minCount and at most maxCount records in the section section. section can be specified as an integer or as a DNS Packet Sections" }, - { "RecordsTypeCountRule", true, "section, qtype, minCount, maxCount", "Matches if there is at least minCount and at most maxCount records of type type in the section section" }, - { "RegexRule", true, "regex", "matches the query name against the supplied regex" }, - { "registerDynBPFFilter", true, "DynBPFFilter", "register this dynamic BPF filter into the web interface so that its counters are displayed" }, - { "reloadAllCertificates", true, "", "reload all DNSCrypt and TLS certificates, along with their associated keys" }, - { "RemoteLogAction", true, "RemoteLogger [, alterFunction [, serverID]]", "send the content of this query to a remote logger via Protocol Buffer. `alterFunction` is a callback, receiving a DNSQuestion and a DNSDistProtoBufMessage, that can be used to modify the Protocol Buffer content, for example for anonymization purposes. `serverID` is the server identifier." }, - { "RemoteLogResponseAction", true, "RemoteLogger [,alterFunction [,includeCNAME [, serverID]]]", "send the content of this response to a remote logger via Protocol Buffer. `alterFunction` is the same callback than the one in `RemoteLogAction` and `includeCNAME` indicates whether CNAME records inside the response should be parsed and exported. The default is to only exports A and AAAA records. `serverID` is the server identifier." }, - { "requestTCPStatesDump", true, "", "Request a dump of the TCP states (incoming connections, outgoing connections) during the next scan. Useful for debugging purposes only" }, - { "rmACL", true, "netmask", "remove netmask from ACL" }, - { "rmCacheHitResponseRule", true, "id", "remove cache hit response rule in position 'id', or whose uuid matches if 'id' is an UUID string, or finally whose name matches if 'id' is a string but not a valid UUID" }, - { "rmCacheInsertedResponseRule", true, "id", "remove cache inserted response rule in position 'id', or whose uuid matches if 'id' is an UUID string, or finally whose name matches if 'id' is a string but not a valid UUID" }, - { "rmResponseRule", true, "id", "remove response rule in position 'id', or whose uuid matches if 'id' is an UUID string, or finally whose name matches if 'id' is a string but not a valid UUID" }, - { "rmRule", true, "id", "remove rule in position 'id', or whose uuid matches if 'id' is an UUID string, or finally whose name matches if 'id' is a string but not a valid UUID" }, - { "rmSelfAnsweredResponseRule", true, "id", "remove self-answered response rule in position 'id', or whose uuid matches if 'id' is an UUID string, or finally whose name matches if 'id' is a string but not a valid UUID" }, - { "rmServer", true, "id", "remove server with index 'id' or whose uuid matches if 'id' is an UUID string" }, - { "roundrobin", false, "", "Simple round robin over available servers" }, - { "sendCustomTrap", true, "str", "send a custom `SNMP` trap from Lua, containing the `str` string"}, - { "setACL", true, "{netmask, netmask}", "replace the ACL set with these netmasks. Use `setACL({})` to reset the list, meaning no one can use us" }, - { "setACLFromFile", true, "file", "replace the ACL set with netmasks in this file" }, - { "setAddEDNSToSelfGeneratedResponses", true, "add", "set whether to add EDNS to self-generated responses, provided that the initial query had EDNS" }, - { "setAllowEmptyResponse", true, "allow", "Set to true (defaults to false) to allow empty responses (qdcount=0) with a NoError or NXDomain rcode (default) from backends" }, - { "setAPIWritable", true, "bool, dir", "allow modifications via the API. if `dir` is set, it must be a valid directory where the configuration files will be written by the API" }, - { "setCacheCleaningDelay", true, "num", "Set the interval in seconds between two runs of the cache cleaning algorithm, removing expired entries" }, - { "setCacheCleaningPercentage", true, "num", "Set the percentage of the cache that the cache cleaning algorithm will try to free by removing expired entries. By default (100), all expired entries are remove" }, - { "setConsistentHashingBalancingFactor", true, "factor", "Set the balancing factor for bounded-load consistent hashing" }, - { "setConsoleACL", true, "{netmask, netmask}", "replace the console ACL set with these netmasks" }, - { "setConsoleConnectionsLogging", true, "enabled", "whether to log the opening and closing of console connections" }, - { "setConsoleMaximumConcurrentConnections", true, "max", "Set the maximum number of concurrent console connections" }, - { "setConsoleOutputMaxMsgSize", true, "messageSize", "set console message maximum size in bytes, default is 10 MB" }, - { "setDefaultBPFFilter", true, "filter", "When used at configuration time, the corresponding BPFFilter will be attached to every bind" }, - { "setDoHDownstreamCleanupInterval", true, "interval", "minimum interval in seconds between two cleanups of the idle DoH downstream connections" }, - { "setDoHDownstreamMaxIdleTime", true, "time", "Maximum time in seconds that a downstream DoH connection to a backend might stay idle" }, - { "setDynBlocksAction", true, "action", "set which action is performed when a query is blocked. Only DNSAction.Drop (the default) and DNSAction.Refused are supported" }, - { "setDynBlocksPurgeInterval", true, "sec", "set how often the expired dynamic block entries should be removed" }, - { "setDropEmptyQueries", true, "drop", "Whether to drop empty queries right away instead of sending a NOTIMP response" }, - { "setECSOverride", true, "bool", "whether to override an existing EDNS Client Subnet value in the query" }, - { "setECSSourcePrefixV4", true, "prefix-length", "the EDNS Client Subnet prefix-length used for IPv4 queries" }, - { "setECSSourcePrefixV6", true, "prefix-length", "the EDNS Client Subnet prefix-length used for IPv6 queries" }, - { "setKey", true, "key", "set access key to that key" }, - { "setLocal", true, R"(addr [, {doTCP=true, reusePort=false, tcpFastOpenQueueSize=0, interface="", cpus={}}])", "reset the list of addresses we listen on to this address" }, - { "setMaxCachedDoHConnectionsPerDownstream", true, "max", "Set the maximum number of inactive DoH connections to a backend cached by each worker DoH thread" }, - { "setMaxCachedTCPConnectionsPerDownstream", true, "max", "Set the maximum number of inactive TCP connections to a backend cached by each worker TCP thread" }, - { "setMaxTCPClientThreads", true, "n", "set the maximum of TCP client threads, handling TCP connections" }, - { "setMaxTCPConnectionDuration", true, "n", "set the maximum duration of an incoming TCP connection, in seconds. 0 means unlimited" }, - { "setMaxTCPConnectionsPerClient", true, "n", "set the maximum number of TCP connections per client. 0 means unlimited" }, - { "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" }, - { "setMetric", true, "name, value", "Set the value of a custom metric to the supplied value" }, - { "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, function, pool", "set the server selection policy for this pool to one named 'name' and provided by 'function'" }, - { "setPoolServerPolicyLuaFFI", true, "name, function, pool", "set the server selection policy for this pool to one named 'name' and provided by 'function'" }, - { "setPoolServerPolicyLuaFFIPerThread", true, "name, code", "set server selection policy for this pool to one named 'name' and returned by the Lua FFI code passed in 'code'" }, - { "setProxyProtocolACL", true, "{netmask, netmask}", "Set the netmasks who are allowed to send Proxy Protocol headers in front of queries/connections" }, - { "setProxyProtocolApplyACLToProxiedClients", true, "apply", "Whether the general ACL should be applied to the source IP address gathered from a Proxy Protocol header, in addition to being first applied to the source address seen by dnsdist" }, - { "setProxyProtocolMaximumPayloadSize", true, "max", "Set the maximum size of a Proxy Protocol payload, in bytes" }, - { "setQueryCount", true, "bool", "set whether queries should be counted" }, - { "setQueryCountFilter", true, "func", "filter queries that would be counted, where `func` is a function with parameter `dq` which decides whether a query should and how it should be counted" }, - { "SetReducedTTLResponseAction", true, "percentage", "Reduce the TTL of records in a response to a given percentage" }, - { "setRingBuffersLockRetries", true, "n", "set the number of attempts to get a non-blocking lock to a ringbuffer shard before blocking" }, - { "setRingBuffersOptions", true, "{ lockRetries=int, recordQueries=true, recordResponses=true }", "set ringbuffer options" }, - { "setRingBuffersSize", true, "n [, numberOfShards]", "set the capacity of the ringbuffers used for live traffic inspection to `n`, and optionally the number of shards to use to `numberOfShards`" }, - { "setRoundRobinFailOnNoServer", true, "value", "By default the roundrobin load-balancing policy will still try to select a backend even if all backends are currently down. Setting this to true will make the policy fail and return that no server is available instead" }, - { "setRules", true, "list of rules", "replace the current rules with the supplied list of pairs of DNS Rules and DNS Actions (see `newRuleAction()`)" }, - { "setSecurityPollInterval", true, "n", "set the security polling interval to `n` seconds" }, - { "setSecurityPollSuffix", true, "suffix", "set the security polling suffix to the specified value" }, - { "setServerPolicy", true, "policy", "set server selection policy to that policy" }, - { "setServerPolicyLua", true, "name, function", "set server selection policy to one named 'name' and provided by 'function'" }, - { "setServerPolicyLuaFFI", true, "name, function", "set server selection policy to one named 'name' and provided by the Lua FFI 'function'" }, - { "setServerPolicyLuaFFIPerThread", true, "name, code", "set server selection policy to one named 'name' and returned by the Lua FFI code passed in 'code'" }, - { "setServFailWhenNoServer", true, "bool", "if set, return a ServFail when no servers are available, instead of the default behaviour of dropping the query" }, - { "setStaleCacheEntriesTTL", true, "n", "allows using cache entries expired for at most n seconds when there is no backend available to answer for a query" }, - { "setStructuredLogging", true, "value [, options]", "set whether log messages should be in structured-logging-like format" }, - { "setSyslogFacility", true, "facility", "set the syslog logging facility to 'facility'. Defaults to LOG_DAEMON" }, - { "setTCPDownstreamCleanupInterval", true, "interval", "minimum interval in seconds between two cleanups of the idle TCP downstream connections" }, - { "setTCPFastOpenKey", true, "string", "TCP Fast Open Key" }, - { "setTCPDownstreamMaxIdleTime", true, "time", "Maximum time in seconds that a downstream TCP connection to a backend might stay idle" }, - { "setTCPInternalPipeBufferSize", true, "size", "Set the size in bytes of the internal buffer of the pipes used internally to distribute connections to TCP (and DoT) workers threads" }, - { "setTCPRecvTimeout", true, "n", "set the read timeout on TCP connections from the client, in seconds" }, - { "setTCPSendTimeout", true, "n", "set the write timeout on TCP connections from the client, in seconds" }, - { "setUDPMultipleMessagesVectorSize", true, "n", "set the size of the vector passed to recvmmsg() to receive UDP messages. Default to 1 which means that the feature is disabled and recvmsg() is used instead" }, - { "setUDPSocketBufferSizes", true, "recv, send", "Set the size of the receive (SO_RCVBUF) and send (SO_SNDBUF) buffers for incoming UDP sockets" }, - { "setUDPTimeout", true, "n", "set the maximum time dnsdist will wait for a response from a backend over UDP, in seconds" }, - { "setVerbose", true, "bool", "set whether log messages at the verbose level will be logged" }, - { "setVerboseHealthChecks", true, "bool", "set whether health check errors will be logged" }, - { "setVerboseLogDestination", true, "destination file", "Set a destination file to write the 'verbose' log messages to, instead of sending them to syslog and/or the standard output" }, - { "setWebserverConfig", true, "[{password=string, apiKey=string, customHeaders, statsRequireAuthentication}]", "Updates webserver configuration" }, - { "setWeightedBalancingFactor", true, "factor", "Set the balancing factor for bounded-load weighted policies (whashed, wrandom)" }, - { "setWHashedPertubation", true, "value", "Set the hash perturbation value to be used in the whashed policy instead of a random one, allowing to have consistent whashed results on different instance" }, - { "show", true, "string", "outputs `string`" }, - { "showACL", true, "", "show our ACL set" }, - { "showBinds", true, "", "show listening addresses (frontends)" }, - { "showCacheHitResponseRules", true, "[{showUUIDs=false, truncateRuleWidth=-1}]", "show all defined cache hit response rules, optionally with their UUIDs and optionally truncated to a given width" }, - { "showConsoleACL", true, "", "show our current console ACL set" }, - { "showDNSCryptBinds", true, "", "display the currently configured DNSCrypt binds" }, - { "showDOHFrontends", true, "", "list all the available DOH frontends" }, - { "showDOH3Frontends", true, "", "list all the available DOH3 frontends" }, - { "showDOHResponseCodes", true, "", "show the HTTP response code statistics for the DoH frontends"}, - { "showDOQFrontends", true, "", "list all the available DOQ frontends" }, - { "showDynBlocks", true, "", "show dynamic blocks in force" }, - { "showPools", true, "", "show the available pools" }, - { "showPoolServerPolicy", true, "pool", "show server selection policy for this pool" }, - { "showResponseLatency", true, "", "show a plot of the response time latency distribution" }, - { "showResponseRules", true, "[{showUUIDs=false, truncateRuleWidth=-1}]", "show all defined response rules, optionally with their UUIDs and optionally truncated to a given width" }, - { "showRules", true, "[{showUUIDs=false, truncateRuleWidth=-1}]", "show all defined rules, optionally with their UUIDs and optionally truncated to a given width" }, - { "showSecurityStatus", true, "", "Show the security status"}, - { "showSelfAnsweredResponseRules", true, "[{showUUIDs=false, truncateRuleWidth=-1}]", "show all defined self-answered response rules, optionally with their UUIDs and optionally truncated to a given width" }, - { "showServerPolicy", true, "", "show name of currently operational server selection policy" }, - { "showServers", true, "[{showUUIDs=false}]", "output all servers, optionally with their UUIDs" }, - { "showTCPStats", true, "", "show some statistics regarding TCP" }, - { "showTLSContexts", true, "", "list all the available TLS contexts" }, - { "showTLSErrorCounters", true, "", "show metrics about TLS handshake failures" }, - { "showVersion", true, "", "show the current version" }, - { "showWebserverConfig", true, "", "Show the current webserver configuration" }, - { "shutdown", true, "", "shut down `dnsdist`" }, - { "snmpAgent", true, "enableTraps [, daemonSocket]", "enable `SNMP` support. `enableTraps` is a boolean indicating whether traps should be sent and `daemonSocket` an optional string specifying how to connect to the daemon agent"}, - { "SetAdditionalProxyProtocolValueAction", true, "type, value", "Add a Proxy Protocol TLV value of this type" }, - { "SetDisableECSAction", true, "", "Disable the sending of ECS to the backend. Subsequent rules are processed after this action." }, - { "SetDisableValidationAction", true, "", "set the CD bit in the question, let it go through" }, - { "SetECSAction", true, "v4[, v6]", "Set the ECS prefix and prefix length sent to backends to an arbitrary value" }, - { "SetECSOverrideAction", true, "override", "Whether an existing EDNS Client Subnet value should be overridden (true) or not (false). Subsequent rules are processed after this action" }, - { "SetECSPrefixLengthAction", true, "v4, v6", "Set the ECS prefix length. Subsequent rules are processed after this action" }, - { "SetMacAddrAction", true, "option", "Add the source MAC address to the query as EDNS0 option option. This action is currently only supported on Linux. Subsequent rules are processed after this action" }, - { "SetEDNSOptionAction", true, "option, data", "Add arbitrary EDNS option and data to the query. Subsequent rules are processed after this action" }, - { "SetExtendedDNSErrorAction", true, "infoCode [, extraText]", "Set an Extended DNS Error status that will be added to the response corresponding to the current query. Subsequent rules are processed after this action" }, - { "SetExtendedDNSErrorResponseAction", true, "infoCode [, extraText]", "Set an Extended DNS Error status that will be added to this response. Subsequent rules are processed after this action" }, - { "SetNoRecurseAction", true, "", "strip RD bit from the question, let it go through" }, - { "setOutgoingDoHWorkerThreads", true, "n", "Number of outgoing DoH worker threads" }, - { "SetProxyProtocolValuesAction", true, "values", "Set the Proxy-Protocol values for this queries to 'values'" }, - { "SetSkipCacheAction", true, "", "Don’t lookup the cache for this query, don’t store the answer" }, - { "SetSkipCacheResponseAction", true, "", "Don’t store this response into the cache" }, - { "SetTagAction", true, "name, value", "set the tag named 'name' to the given value" }, - { "SetTagResponseAction", true, "name, value", "set the tag named 'name' to the given value" }, - { "SetTempFailureCacheTTLAction", true, "ttl", "set packetcache TTL for temporary failure replies" }, - { "SNIRule", true, "name", "Create a rule which matches on the incoming TLS SNI value, if any (DoT or DoH)" }, - { "SNMPTrapAction", true, "[reason]", "send an SNMP trap, adding the optional `reason` string as the query description"}, - { "SNMPTrapResponseAction", true, "[reason]", "send an SNMP trap, adding the optional `reason` string as the response description"}, - { "SpoofAction", true, "ip|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|list of raws [, options]", "Forge a response with the specified record data as raw bytes. If you specify multiple raws (it is assumed they match the query type), all will get spoofed in" }, - { "SpoofSVCAction", true, "list of svcParams [, options]", "Forge a response with the specified SVC record data" } , - { "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" }, - { "TagRule", true, "name [, value]", "matches if the tag named 'name' is present, with the given 'value' matching if any" }, - { "TCAction", true, "", "create answer to query with TC and RD bits set, to move to TCP" }, - { "TCPRule", true, "[tcp]", "Matches question received over TCP if tcp is true, over UDP otherwise" }, - { "TCResponseAction", true, "", "truncate a response" }, - { "TeeAction", true, "remote [, addECS [, local]]", "send copy of query to remote, optionally adding ECS info, optionally set local address" }, - { "testCrypto", true, "", "test of the crypto all works" }, - { "TimedIPSetRule", true, "", "Create a rule which matches a set of IP addresses which expire"}, - { "topBandwidth", true, "top", "show top-`top` clients that consume the most bandwidth over length of ringbuffer" }, - { "topCacheHitResponseRules", true, "[top][, vars]", "show `top` cache-hit response rules" }, - { "topCacheInsertedResponseRules", true, "[top][, vars]", "show `top` cache-inserted response rules" }, - { "topClients", true, "n", "show top-`n` clients sending the most queries over length of ringbuffer" }, - { "topQueries", true, "n[, labels]", "show top 'n' queries, as grouped when optionally cut down to 'labels' labels" }, - { "topResponses", true, "n, kind[, labels]", "show top 'n' responses with RCODE=kind (0=NO Error, 2=ServFail, 3=NXDomain), as grouped when optionally cut down to 'labels' labels" }, - { "topResponseRules", true, "[top][, vars]", "show `top` response rules" }, - { "topRules", true, "[top][, vars]", "show `top` rules" }, - { "topSelfAnsweredResponseRules", true, "[top][, vars]", "show `top` self-answered response rules" }, - { "topSlow", true, "[top][, limit][, labels]", "show `top` queries slower than `limit` milliseconds, grouped by last `labels` labels" }, - { "TrailingDataRule", true, "", "Matches if the query has trailing data" }, - { "truncateTC", true, "bool", "if set (defaults to no starting with dnsdist 1.2.0) truncate TC=1 answers so they are actually empty. Fixes an issue for PowerDNS Authoritative Server 2.9.22. Note: turning this on breaks compatibility with RFC 6891." }, - { "unregisterDynBPFFilter", true, "DynBPFFilter", "unregister this dynamic BPF filter" }, - { "webserver", true, "address:port", "launch a webserver with stats on that address" }, - { "whashed", false, "", "Weighted hashed ('sticky') distribution over available servers, based on the server 'weight' parameter" }, - { "chashed", false, "", "Consistent hashed ('sticky') distribution over available servers, also based on the server 'weight' parameter" }, - { "wrandom", false, "", "Weighted random over available servers, based on the server 'weight' parameter" }, -}; - -#if defined(HAVE_LIBEDIT) -extern "C" { -static char* my_generator(const char* text, int state) -{ - string textStr(text); - /* to keep it readable, we try to keep only 4 keywords per line - and to start a new line when the first letter changes */ - static int s_counter = 0; - int counter = 0; - if (state == 0) { - s_counter = 0; - } - - for (const auto& keyword : g_consoleKeywords) { - if (boost::starts_with(keyword.name, textStr) && counter++ == s_counter) { - std::string value(keyword.name); - s_counter++; - if (keyword.function) { - value += "("; - if (keyword.parameters.empty()) { - value += ")"; - } - } - return strdup(value.c_str()); - } - } - return nullptr; -} - -char** my_completion( const char * text , int start, int end) -{ - char **matches = nullptr; - if (start == 0) { - // NOLINTNEXTLINE(cppcoreguidelines-pro-type-const-cast): readline - matches = rl_completion_matches (const_cast(text), &my_generator); - } - - // skip default filename completion. - rl_attempted_completion_over = 1; - - return matches; -} -} -#endif /* HAVE_LIBEDIT */ -#endif /* DISABLE_COMPLETION */ - -static void controlClientThread(ConsoleConnection&& conn) -{ - try { - setThreadName("dnsdist/conscli"); - - setTCPNoDelay(conn.getFD()); - - dnsdist::crypto::authenticated::Nonce theirs; - dnsdist::crypto::authenticated::Nonce ours; - dnsdist::crypto::authenticated::Nonce readingNonce; - dnsdist::crypto::authenticated::Nonce writingNonce; - ours.init(); - readn2(conn.getFD(), theirs.value.data(), theirs.value.size()); - writen2(conn.getFD(), ours.value.data(), ours.value.size()); - readingNonce.merge(ours, theirs); - writingNonce.merge(theirs, ours); - - for (;;) { - uint32_t len{0}; - if (getMsgLen32(conn.getFD(), &len) != ConsoleCommandResult::Valid) { - break; - } - - if (len == 0) { - /* just ACK an empty message - with an empty response */ - putMsgLen32(conn.getFD(), 0); - continue; - } - - std::string line; - line.resize(len); - readn2(conn.getFD(), line.data(), len); - - line = dnsdist::crypto::authenticated::decryptSym(line, g_consoleKey, readingNonce); - - string response; - try { - bool withReturn = true; - retry:; - try { - auto lua = g_lua.lock(); - - g_outputBuffer.clear(); - resetLuaSideEffect(); - auto ret = lua->executeCode< - boost::optional< - boost::variant< - string, - shared_ptr, - ClientState*, - std::unordered_map - > - > - >(withReturn ? ("return "+line) : line); - - if (ret) { - if (const auto* dsValue = boost::get>(&*ret)) { - if (*dsValue) { - response = (*dsValue)->getName()+"\n"; - } else { - response = ""; - } - } - else if (const auto* csValue = boost::get(&*ret)) { - if (*csValue != nullptr) { - response = (*csValue)->local.toStringWithPort()+"\n"; - } else { - response = ""; - } - } - else if (const auto* strValue = boost::get(&*ret)) { - response = *strValue+"\n"; - } - else if (const auto* mapValue = boost::get >(&*ret)) { - using namespace json11; - Json::object obj; - for (const auto& value : *mapValue) { - obj[value.first] = value.second; - } - Json out = obj; - response = out.dump()+"\n"; - } - } - else { - response = g_outputBuffer; - } - if (!getLuaNoSideEffect()) { - feedConfigDelta(line); - } - } - catch (const LuaContext::SyntaxErrorException&) { - if (withReturn) { - withReturn = false; - // NOLINTNEXTLINE(cppcoreguidelines-avoid-goto) - goto retry; - } - throw; - } - } - catch(const LuaContext::WrongTypeException& e) { - response = "Command returned an object we can't print: " +std::string(e.what()) + "\n"; - // tried to return something we don't understand - } - catch (const LuaContext::ExecutionErrorException& e) { - if (strcmp(e.what(),"invalid key to 'next'") == 0) { - response = "Error: Parsing function parameters, did you forget parameter name?"; - } - else { - response = "Error: " + string(e.what()); - } - - try { - std::rethrow_if_nested(e); - } catch (const std::exception& ne) { - // ne is the exception that was thrown from inside the lambda - response+= ": " + string(ne.what()); - } - catch (const PDNSException& ne) { - // ne is the exception that was thrown from inside the lambda - response += ": " + string(ne.reason); - } - } - catch (const LuaContext::SyntaxErrorException& e) { - response = "Error: " + string(e.what()) + ": "; - } - response = dnsdist::crypto::authenticated::encryptSym(response, g_consoleKey, writingNonce); - putMsgLen32(conn.getFD(), response.length()); - writen2(conn.getFD(), response.c_str(), response.length()); - } - if (g_logConsoleConnections) { - infolog("Closed control connection from %s", conn.getClient().toStringWithPort()); - } - } - catch (const std::exception& e) { - infolog("Got an exception in client connection from %s: %s", conn.getClient().toStringWithPort(), e.what()); - } -} - -// NOLINTNEXTLINE(performance-unnecessary-value-param): this is thread -void controlThread(std::shared_ptr acceptFD, ComboAddress local) -{ - try - { - setThreadName("dnsdist/control"); - ComboAddress client; - int sock{-1}; - auto localACL = g_consoleACL.getLocal(); - infolog("Accepting control connections on %s", local.toStringWithPort()); - - while ((sock = SAccept(acceptFD->getHandle(), client)) >= 0) { - - FDWrapper socket(sock); - if (!dnsdist::crypto::authenticated::isValidKey(g_consoleKey)) { - vinfolog("Control connection from %s dropped because we don't have a valid key configured, please configure one using setKey()", client.toStringWithPort()); - continue; - } - - if (!localACL->match(client)) { - vinfolog("Control connection from %s dropped because of ACL", client.toStringWithPort()); - continue; - } - - try { - ConsoleConnection conn(client, std::move(socket)); - if (g_logConsoleConnections) { - warnlog("Got control connection from %s", client.toStringWithPort()); - } - - std::thread clientThread(controlClientThread, std::move(conn)); - clientThread.detach(); - } - catch (const std::exception& e) { - infolog("Control connection died: %s", e.what()); - } - } - } - catch (const std::exception& e) { - errlog("Control thread died: %s", e.what()); - } -} - -void clearConsoleHistory() -{ -#ifdef HAVE_LIBEDIT - clear_history(); -#endif /* HAVE_LIBEDIT */ - g_confDelta.clear(); -} diff --git a/pdns/dnsdist-console.hh b/pdns/dnsdist-console.hh deleted file mode 100644 index 6e227d47165d..000000000000 --- a/pdns/dnsdist-console.hh +++ /dev/null @@ -1,62 +0,0 @@ -/* - * This file is part of PowerDNS or dnsdist. - * Copyright -- PowerDNS.COM B.V. and its contributors - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of version 2 of the GNU General Public License as - * published by the Free Software Foundation. - * - * In addition, for the avoidance of any doubt, permission is granted to - * link this program with OpenSSL and to (re)distribute the binaries - * produced as the result of such linking. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ -#pragma once - -#include "config.h" -#include "sstuff.hh" - -#ifndef DISABLE_COMPLETION -struct ConsoleKeyword { - std::string name; - bool function; - std::string parameters; - std::string description; - std::string toString() const - { - std::string res(name); - if (function) { - res += "(" + parameters + ")"; - } - res += ": "; - res += description; - return res; - } -}; -extern const std::vector g_consoleKeywords; -extern "C" { -char** my_completion( const char * text , int start, int end); -} - -#endif /* DISABLE_COMPLETION */ - -extern GlobalStateHolder g_consoleACL; -extern std::string g_consoleKey; // in theory needs locking -extern bool g_logConsoleConnections; -extern bool g_consoleEnabled; -extern uint32_t g_consoleOutputMsgMaxSize; - -void doClient(ComboAddress server, const std::string& command); -void doConsole(); -void controlThread(std::shared_ptr acceptFD, ComboAddress local); -void clearConsoleHistory(); - -void setConsoleMaximumConcurrentConnections(size_t max); diff --git a/pdns/dnsdist-dnscrypt.cc b/pdns/dnsdist-dnscrypt.cc deleted file mode 100644 index 8f02910aaa8a..000000000000 --- a/pdns/dnsdist-dnscrypt.cc +++ /dev/null @@ -1,50 +0,0 @@ -/* - * This file is part of PowerDNS or dnsdist. - * Copyright -- PowerDNS.COM B.V. and its contributors - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of version 2 of the GNU General Public License as - * published by the Free Software Foundation. - * - * In addition, for the avoidance of any doubt, permission is granted to - * link this program with OpenSSL and to (re)distribute the binaries - * produced as the result of such linking. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ -#include "dolog.hh" -#include "dnsdist.hh" -#include "dnsdist-metrics.hh" -#include "dnscrypt.hh" - -#ifdef HAVE_DNSCRYPT -int handleDNSCryptQuery(PacketBuffer& packet, DNSCryptQuery& query, bool tcp, time_t now, PacketBuffer& response) -{ - query.parsePacket(packet, tcp, now); - - if (query.isValid() == false) { - vinfolog("Dropping DNSCrypt invalid query"); - return false; - } - - if (query.isEncrypted() == false) { - query.getCertificateResponse(now, response); - - return false; - } - - if (packet.size() < static_cast(sizeof(struct dnsheader))) { - ++dnsdist::metrics::g_stats.nonCompliantQueries; - return false; - } - - return true; -} -#endif diff --git a/pdns/dnsdist-doh-common.hh b/pdns/dnsdist-doh-common.hh deleted file mode 100644 index 0dc714df23a3..000000000000 --- a/pdns/dnsdist-doh-common.hh +++ /dev/null @@ -1,248 +0,0 @@ -/* - * This file is part of PowerDNS or dnsdist. - * Copyright -- PowerDNS.COM B.V. and its contributors - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of version 2 of the GNU General Public License as - * published by the Free Software Foundation. - * - * In addition, for the avoidance of any doubt, permission is granted to - * link this program with OpenSSL and to (re)distribute the binaries - * produced as the result of such linking. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ -#pragma once - -#include -#include -#include -#include - -#include "config.h" -#include "iputils.hh" -#include "libssl.hh" -#include "noinitvector.hh" -#include "stat_t.hh" -#include "tcpiohandler.hh" - -namespace dnsdist::doh -{ -std::optional getPayloadFromPath(const std::string_view& path); -} - -struct DOHServerConfig; - -class DOHResponseMapEntry -{ -public: - DOHResponseMapEntry(const std::string& regex, uint16_t status, const PacketBuffer& content, const boost::optional>& headers) : - d_regex(regex), d_customHeaders(headers), d_content(content), d_status(status) - { - if (status >= 400 && !d_content.empty() && d_content.at(d_content.size() - 1) != 0) { - // we need to make sure it's null-terminated - d_content.push_back(0); - } - } - - bool matches(const std::string& path) const - { - return d_regex.match(path); - } - - uint16_t getStatusCode() const - { - return d_status; - } - - const PacketBuffer& getContent() const - { - return d_content; - } - - const boost::optional>& getHeaders() const - { - return d_customHeaders; - } - -private: - Regex d_regex; - boost::optional> d_customHeaders; - PacketBuffer d_content; - uint16_t d_status; -}; - -struct DOHFrontend -{ - DOHFrontend() - { - } - DOHFrontend(std::shared_ptr tlsCtx) : - d_tlsContext(std::move(tlsCtx)) - { - } - - virtual ~DOHFrontend() - { - } - - std::shared_ptr d_dsc{nullptr}; - std::shared_ptr>> d_responsesMap; - TLSFrontend d_tlsContext{TLSFrontend::ALPN::DoH}; - std::string d_serverTokens{"h2o/dnsdist"}; - std::unordered_map d_customResponseHeaders; - std::string d_library; - - uint32_t d_idleTimeout{30}; // HTTP idle timeout in seconds - std::set> d_urls; - - pdns::stat_t d_httpconnects{0}; // number of TCP/IP connections established - pdns::stat_t d_getqueries{0}; // valid DNS queries received via GET - pdns::stat_t d_postqueries{0}; // valid DNS queries received via POST - pdns::stat_t d_badrequests{0}; // request could not be converted to dns query - pdns::stat_t d_errorresponses{0}; // dnsdist set 'error' on response - pdns::stat_t d_redirectresponses{0}; // dnsdist set 'redirect' on response - pdns::stat_t d_validresponses{0}; // valid responses sent out - - struct HTTPVersionStats - { - pdns::stat_t d_nbQueries{0}; // valid DNS queries received - pdns::stat_t d_nb200Responses{0}; - pdns::stat_t d_nb400Responses{0}; - pdns::stat_t d_nb403Responses{0}; - pdns::stat_t d_nb500Responses{0}; - pdns::stat_t d_nb502Responses{0}; - pdns::stat_t d_nbOtherResponses{0}; - }; - - HTTPVersionStats d_http1Stats; - HTTPVersionStats d_http2Stats; -#ifdef __linux__ - // On Linux this gives us 128k pending queries (default is 8192 queries), - // which should be enough to deal with huge spikes - uint32_t d_internalPipeBufferSize{1024 * 1024}; -#else - uint32_t d_internalPipeBufferSize{0}; -#endif - bool d_sendCacheControlHeaders{true}; - bool d_trustForwardedForHeader{false}; - bool d_earlyACLDrop{true}; - /* whether we require tue query path to exactly match one of configured ones, - or accept everything below these paths. */ - bool d_exactPathMatching{true}; - bool d_keepIncomingHeaders{false}; - - time_t getTicketsKeyRotationDelay() const - { - return d_tlsContext.d_tlsConfig.d_ticketsKeyRotationDelay; - } - - bool isHTTPS() const - { - return !d_tlsContext.d_tlsConfig.d_certKeyPairs.empty(); - } - -#ifndef HAVE_DNS_OVER_HTTPS - virtual void setup() - { - } - - virtual void reloadCertificates() - { - } - - virtual void rotateTicketsKey(time_t /* now */) - { - } - - virtual void loadTicketsKeys(const std::string& /* keyFile */) - { - } - - virtual void handleTicketsKeyRotation() - { - } - - virtual std::string getNextTicketsKeyRotation() - { - return std::string(); - } - - virtual size_t getTicketsKeysCount() const - { - size_t res = 0; - return res; - } - -#else - virtual void setup(); - virtual void reloadCertificates(); - - virtual void rotateTicketsKey(time_t now); - virtual void loadTicketsKeys(const std::string& keyFile); - virtual void handleTicketsKeyRotation(); - virtual std::string getNextTicketsKeyRotation() const; - virtual size_t getTicketsKeysCount(); -#endif /* HAVE_DNS_OVER_HTTPS */ -}; - -#include "dnsdist-idstate.hh" - -struct DownstreamState; - -#ifndef HAVE_DNS_OVER_HTTPS -struct DOHUnitInterface -{ - virtual ~DOHUnitInterface() - { - } - static void handleTimeout(std::unique_ptr) - { - } - - static void handleUDPResponse(std::unique_ptr, PacketBuffer&&, InternalQueryState&&, const std::shared_ptr&) - { - } -}; -#else /* HAVE_DNS_OVER_HTTPS */ -struct DOHUnitInterface -{ - virtual ~DOHUnitInterface() - { - } - - virtual std::string getHTTPPath() const = 0; - virtual std::string getHTTPQueryString() const = 0; - virtual const std::string& getHTTPHost() const = 0; - virtual const std::string& getHTTPScheme() const = 0; - virtual const std::unordered_map& getHTTPHeaders() const = 0; - virtual void setHTTPResponse(uint16_t statusCode, PacketBuffer&& body, const std::string& contentType = "") = 0; - virtual void handleTimeout() = 0; - virtual void handleUDPResponse(PacketBuffer&& response, InternalQueryState&& state, const std::shared_ptr&) = 0; - - static void handleTimeout(std::unique_ptr unit) - { - if (unit) { - unit->handleTimeout(); - unit.release(); - } - } - - static void handleUDPResponse(std::unique_ptr unit, PacketBuffer&& response, InternalQueryState&& state, const std::shared_ptr& ds) - { - if (unit) { - unit->handleUDPResponse(std::move(response), std::move(state), ds); - unit.release(); - } - } - - std::shared_ptr downstream{nullptr}; -}; -#endif /* HAVE_DNS_OVER_HTTPS */ diff --git a/pdns/dnsdist-dynblocks.hh b/pdns/dnsdist-dynblocks.hh deleted file mode 100644 index 1a8b3a68e81f..000000000000 --- a/pdns/dnsdist-dynblocks.hh +++ /dev/null @@ -1,393 +0,0 @@ -/* - * This file is part of PowerDNS or dnsdist. - * Copyright -- PowerDNS.COM B.V. and its contributors - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of version 2 of the GNU General Public License as - * published by the Free Software Foundation. - * - * In addition, for the avoidance of any doubt, permission is granted to - * link this program with OpenSSL and to (re)distribute the binaries - * produced as the result of such linking. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ -#pragma once - -#ifndef DISABLE_DYNBLOCKS -#include - -#include "dolog.hh" -#include "dnsdist-rings.hh" -#include "statnode.hh" - -extern "C" -{ -#include "dnsdist-lua-inspection-ffi.h" -} - -// dnsdist_ffi_stat_node_t is a lightuserdata -template <> -struct LuaContext::Pusher -{ - static const int minSize = 1; - static const int maxSize = 1; - - static PushedObject push(lua_State* state, dnsdist_ffi_stat_node_t* ptr) noexcept - { - lua_pushlightuserdata(state, ptr); - return PushedObject{state, 1}; - } -}; - -using dnsdist_ffi_stat_node_visitor_t = std::function; - -struct SMTBlockParameters -{ - std::optional d_reason; - std::optional d_action; -}; - -struct dnsdist_ffi_stat_node_t -{ - dnsdist_ffi_stat_node_t(const StatNode& node_, const StatNode::Stat& self_, const StatNode::Stat& children_, SMTBlockParameters& blockParameters) : - node(node_), self(self_), children(children_), d_blockParameters(blockParameters) - { - } - - const StatNode& node; - const StatNode::Stat& self; - const StatNode::Stat& children; - SMTBlockParameters& d_blockParameters; -}; - -using dnsdist_ffi_dynamic_block_inserted_hook = std::function; - -class DynBlockRulesGroup -{ -private: - struct Counts - { - std::map d_rcodeCounts; - std::map d_qtypeCounts; - uint64_t queries{0}; - uint64_t responses{0}; - uint64_t respBytes{0}; - uint64_t cacheMisses{0}; - }; - - struct DynBlockRule - { - DynBlockRule() = default; - DynBlockRule(const std::string& blockReason, unsigned int blockDuration, unsigned int rate, unsigned int warningRate, unsigned int seconds, DNSAction::Action action) : - d_blockReason(blockReason), d_blockDuration(blockDuration), d_rate(rate), d_warningRate(warningRate), d_seconds(seconds), d_action(action), d_enabled(true) - { - } - - bool matches(const struct timespec& when); - bool rateExceeded(unsigned int count, const struct timespec& now) const; - bool warningRateExceeded(unsigned int count, const struct timespec& now) const; - - bool isEnabled() const - { - return d_enabled; - } - - std::string toString() const; - - std::string d_blockReason; - struct timespec d_cutOff; - struct timespec d_minTime; - unsigned int d_blockDuration{0}; - unsigned int d_rate{0}; - unsigned int d_warningRate{0}; - unsigned int d_seconds{0}; - DNSAction::Action d_action{DNSAction::Action::None}; - bool d_enabled{false}; - }; - - struct DynBlockRatioRule : DynBlockRule - { - DynBlockRatioRule() = default; - DynBlockRatioRule(const std::string& blockReason, unsigned int blockDuration, double ratio, double warningRatio, unsigned int seconds, DNSAction::Action action, size_t minimumNumberOfResponses) : - DynBlockRule(blockReason, blockDuration, 0, 0, seconds, action), d_minimumNumberOfResponses(minimumNumberOfResponses), d_ratio(ratio), d_warningRatio(warningRatio) - { - } - - bool ratioExceeded(unsigned int total, unsigned int count) const; - bool warningRatioExceeded(unsigned int total, unsigned int count) const; - std::string toString() const; - - size_t d_minimumNumberOfResponses{0}; - double d_ratio{0.0}; - double d_warningRatio{0.0}; - }; - - struct DynBlockCacheMissRatioRule : public DynBlockRatioRule - { - DynBlockCacheMissRatioRule() = default; - DynBlockCacheMissRatioRule(const std::string& blockReason, unsigned int blockDuration, double ratio, double warningRatio, unsigned int seconds, DNSAction::Action action, size_t minimumNumberOfResponses, double minimumGlobalCacheHitRatio) : - DynBlockRatioRule(blockReason, blockDuration, ratio, warningRatio, seconds, action, minimumNumberOfResponses), d_minimumGlobalCacheHitRatio(minimumGlobalCacheHitRatio) - { - } - - bool checkGlobalCacheHitRatio() const; - bool ratioExceeded(unsigned int total, unsigned int count) const; - bool warningRatioExceeded(unsigned int total, unsigned int count) const; - std::string toString() const; - - double d_minimumGlobalCacheHitRatio{0.0}; - }; - - using counts_t = std::unordered_map; - -public: - DynBlockRulesGroup() - { - } - - void setQueryRate(unsigned int rate, unsigned int warningRate, unsigned int seconds, const std::string& reason, unsigned int blockDuration, DNSAction::Action action) - { - d_queryRateRule = DynBlockRule(reason, blockDuration, rate, warningRate, seconds, action); - } - - /* rate is in bytes per second */ - void setResponseByteRate(unsigned int rate, unsigned int warningRate, unsigned int seconds, const std::string& reason, unsigned int blockDuration, DNSAction::Action action) - { - d_respRateRule = DynBlockRule(reason, blockDuration, rate, warningRate, seconds, action); - } - - void setRCodeRate(uint8_t rcode, unsigned int rate, unsigned int warningRate, unsigned int seconds, const std::string& reason, unsigned int blockDuration, DNSAction::Action action) - { - auto& entry = d_rcodeRules[rcode]; - entry = DynBlockRule(reason, blockDuration, rate, warningRate, seconds, action); - } - - void setRCodeRatio(uint8_t rcode, double ratio, double warningRatio, unsigned int seconds, const std::string& reason, unsigned int blockDuration, DNSAction::Action action, size_t minimumNumberOfResponses) - { - auto& entry = d_rcodeRatioRules[rcode]; - entry = DynBlockRatioRule(reason, blockDuration, ratio, warningRatio, seconds, action, minimumNumberOfResponses); - } - - void setQTypeRate(uint16_t qtype, unsigned int rate, unsigned int warningRate, unsigned int seconds, const std::string& reason, unsigned int blockDuration, DNSAction::Action action) - { - auto& entry = d_qtypeRules[qtype]; - entry = DynBlockRule(reason, blockDuration, rate, warningRate, seconds, action); - } - - void setCacheMissRatio(double ratio, double warningRatio, unsigned int seconds, const std::string& reason, unsigned int blockDuration, DNSAction::Action action, size_t minimumNumberOfResponses, double minimumGlobalCacheHitRatio) - { - d_respCacheMissRatioRule = DynBlockCacheMissRatioRule(reason, blockDuration, ratio, warningRatio, seconds, action, minimumNumberOfResponses, minimumGlobalCacheHitRatio); - } - - using smtVisitor_t = std::function, boost::optional>(const StatNode&, const StatNode::Stat&, const StatNode::Stat&)>; - - void setSuffixMatchRule(unsigned int seconds, const std::string& reason, unsigned int blockDuration, DNSAction::Action action, smtVisitor_t visitor) - { - d_suffixMatchRule = DynBlockRule(reason, blockDuration, 0, 0, seconds, action); - d_smtVisitor = std::move(visitor); - } - - void setSuffixMatchRuleFFI(unsigned int seconds, const std::string& reason, unsigned int blockDuration, DNSAction::Action action, dnsdist_ffi_stat_node_visitor_t visitor) - { - d_suffixMatchRule = DynBlockRule(reason, blockDuration, 0, 0, seconds, action); - d_smtVisitorFFI = std::move(visitor); - } - - void setNewBlockHook(const dnsdist_ffi_dynamic_block_inserted_hook& callback) - { - d_newBlockHook = callback; - } - - void setMasks(uint8_t v4, uint8_t v6, uint8_t port) - { - d_v4Mask = v4; - d_v6Mask = v6; - d_portMask = port; - } - - void apply() - { - struct timespec now; - gettime(&now); - - apply(now); - } - - void apply(const struct timespec& now); - - void excludeRange(const Netmask& range) - { - d_excludedSubnets.addMask(range); - } - - void excludeRange(const NetmaskGroup& group) - { - d_excludedSubnets.addMasks(group, true); - } - - void includeRange(const Netmask& range) - { - d_excludedSubnets.addMask(range, false); - } - - void includeRange(const NetmaskGroup& group) - { - d_excludedSubnets.addMasks(group, false); - } - - void removeRange(const Netmask& range) - { - d_excludedSubnets.deleteMask(range); - } - - void removeRange(const NetmaskGroup& group) - { - d_excludedSubnets.deleteMasks(group); - } - - void excludeDomain(const DNSName& domain) - { - d_excludedDomains.add(domain); - } - - std::string toString() const - { - std::stringstream result; - - result << "Query rate rule: " << d_queryRateRule.toString() << std::endl; - result << "Response rate rule: " << d_respRateRule.toString() << std::endl; - result << "SuffixMatch rule: " << d_suffixMatchRule.toString() << std::endl; - result << "Response cache-miss ratio rule: " << d_respCacheMissRatioRule.toString() << std::endl; - result << "RCode rules: " << std::endl; - for (const auto& rule : d_rcodeRules) { - result << "- " << RCode::to_s(rule.first) << ": " << rule.second.toString() << std::endl; - } - for (const auto& rule : d_rcodeRatioRules) { - result << "- " << RCode::to_s(rule.first) << ": " << rule.second.toString() << std::endl; - } - result << "QType rules: " << std::endl; - for (const auto& rule : d_qtypeRules) { - result << "- " << QType(rule.first).toString() << ": " << rule.second.toString() << std::endl; - } - result << "Excluded Subnets: " << d_excludedSubnets.toString() << std::endl; - result << "Excluded Domains: " << d_excludedDomains.toString() << std::endl; - - return result.str(); - } - - void setQuiet(bool quiet) - { - d_beQuiet = quiet; - } - -private: - void applySMT(const struct timespec& now, StatNode& statNodeRoot); - bool checkIfQueryTypeMatches(const Rings::Query& query); - bool checkIfResponseCodeMatches(const Rings::Response& response); - void addOrRefreshBlock(boost::optional>& blocks, const struct timespec& now, const AddressAndPortRange& requestor, const DynBlockRule& rule, bool& updated, bool warning); - void addOrRefreshBlockSMT(SuffixMatchTree& blocks, const struct timespec& now, const DNSName& name, const DynBlockRule& rule, bool& updated); - - void addBlock(boost::optional>& blocks, const struct timespec& now, const AddressAndPortRange& requestor, const DynBlockRule& rule, bool& updated) - { - addOrRefreshBlock(blocks, now, requestor, rule, updated, false); - } - - void handleWarning(boost::optional>& blocks, const struct timespec& now, const AddressAndPortRange& requestor, const DynBlockRule& rule, bool& updated) - { - addOrRefreshBlock(blocks, now, requestor, rule, updated, true); - } - - bool hasQueryRules() const - { - return d_queryRateRule.isEnabled() || !d_qtypeRules.empty(); - } - - bool hasResponseRules() const - { - return d_respRateRule.isEnabled() || !d_rcodeRules.empty() || !d_rcodeRatioRules.empty() || d_respCacheMissRatioRule.isEnabled(); - } - - bool hasSuffixMatchRules() const - { - return d_suffixMatchRule.isEnabled(); - } - - bool hasRules() const - { - return hasQueryRules() || hasResponseRules(); - } - - void processQueryRules(counts_t& counts, const struct timespec& now); - void processResponseRules(counts_t& counts, StatNode& root, const struct timespec& now); - - std::map d_rcodeRules; - std::map d_rcodeRatioRules; - std::map d_qtypeRules; - DynBlockRule d_queryRateRule; - DynBlockRule d_respRateRule; - DynBlockRule d_suffixMatchRule; - DynBlockCacheMissRatioRule d_respCacheMissRatioRule; - NetmaskGroup d_excludedSubnets; - SuffixMatchNode d_excludedDomains; - smtVisitor_t d_smtVisitor; - dnsdist_ffi_stat_node_visitor_t d_smtVisitorFFI; - dnsdist_ffi_dynamic_block_inserted_hook d_newBlockHook; - uint8_t d_v6Mask{128}; - uint8_t d_v4Mask{32}; - uint8_t d_portMask{0}; - bool d_beQuiet{false}; -}; - -class DynBlockMaintenance -{ -public: - static void run(); - - /* return the (cached) number of hits per second for the top offenders, averaged over 60s */ - static std::map>> getHitsForTopNetmasks(); - static std::map>> getHitsForTopSuffixes(); - - /* get the the top offenders based on the current value of the counters */ - static std::map>> getTopNetmasks(size_t topN); - static std::map>> getTopSuffixes(size_t topN); - static void purgeExpired(const struct timespec& now); - - static time_t s_expiredDynBlocksPurgeInterval; - -private: - static void collectMetrics(); - static void generateMetrics(); - - struct MetricsSnapshot - { - std::map>> nmgData; - std::map>> smtData; - }; - - struct Tops - { - std::map>> topNMGsByReason; - std::map>> topSMTsByReason; - }; - - static LockGuarded s_tops; - /* s_metricsData should only be accessed by the dynamic blocks maintenance thread so it does not need a lock */ - // need N+1 datapoints to be able to do the diff after a collection point has been reached - static std::list s_metricsData; - static size_t s_topN; -}; - -namespace dnsdist::DynamicBlocks -{ -bool addOrRefreshBlock(NetmaskTree& blocks, const struct timespec& now, const AddressAndPortRange& requestor, const std::string& reason, unsigned int duration, DNSAction::Action action, bool warning, bool beQuiet); -bool addOrRefreshBlockSMT(SuffixMatchTree& blocks, const struct timespec& now, const DNSName& name, const std::string& reason, unsigned int duration, DNSAction::Action action, bool beQuiet); -} -#endif /* DISABLE_DYNBLOCKS */ diff --git a/pdns/dnsdist-dynbpf.cc b/pdns/dnsdist-dynbpf.cc deleted file mode 100644 index 9ede03fabf86..000000000000 --- a/pdns/dnsdist-dynbpf.cc +++ /dev/null @@ -1,85 +0,0 @@ -/* - * This file is part of PowerDNS or dnsdist. - * Copyright -- PowerDNS.COM B.V. and its contributors - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of version 2 of the GNU General Public License as - * published by the Free Software Foundation. - * - * In addition, for the avoidance of any doubt, permission is granted to - * link this program with OpenSSL and to (re)distribute the binaries - * produced as the result of such linking. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ -#include "dnsdist-dynbpf.hh" - -bool DynBPFFilter::block(const ComboAddress& addr, const struct timespec& until) -{ - bool inserted = false; - auto data = d_data.lock(); - - if (data->d_excludedSubnets.match(addr)) { - /* do not add a block for excluded subnets */ - return inserted; - } - - const container_t::iterator it = data->d_entries.find(addr); - if (it != data->d_entries.end()) { - if (it->d_until < until) { - data->d_entries.replace(it, BlockEntry(addr, until)); - } - } - else { - data->d_bpf->block(addr, BPFFilter::MatchAction::Drop); - data->d_entries.insert(BlockEntry(addr, until)); - inserted = true; - } - return inserted; -} - -void DynBPFFilter::purgeExpired(const struct timespec& now) -{ - auto data = d_data.lock(); - - typedef boost::multi_index::nth_index::type ordered_until; - ordered_until& ou = boost::multi_index::get<1>(data->d_entries); - - for (ordered_until::iterator it = ou.begin(); it != ou.end(); ) { - if (it->d_until < now) { - ComboAddress addr = it->d_addr; - it = ou.erase(it); - data->d_bpf->unblock(addr); - } - else { - break; - } - } -} - -std::vector > DynBPFFilter::getAddrStats() -{ - std::vector > result; - auto data = d_data.lock(); - - if (!data->d_bpf) { - return result; - } - - const auto& stats = data->d_bpf->getAddrStats(); - result.reserve(stats.size()); - for (const auto& stat : stats) { - const container_t::iterator it = data->d_entries.find(stat.first); - if (it != data->d_entries.end()) { - result.emplace_back(stat.first, stat.second, it->d_until); - } - } - return result; -} diff --git a/pdns/dnsdist-dynbpf.hh b/pdns/dnsdist-dynbpf.hh deleted file mode 100644 index 907a7300b9e8..000000000000 --- a/pdns/dnsdist-dynbpf.hh +++ /dev/null @@ -1,76 +0,0 @@ -/* - * This file is part of PowerDNS or dnsdist. - * Copyright -- PowerDNS.COM B.V. and its contributors - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of version 2 of the GNU General Public License as - * published by the Free Software Foundation. - * - * In addition, for the avoidance of any doubt, permission is granted to - * link this program with OpenSSL and to (re)distribute the binaries - * produced as the result of such linking. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ -#pragma once -#include "config.h" - -#include "bpf-filter.hh" -#include "iputils.hh" - -#include -#include -#include - -class DynBPFFilter -{ -public: - DynBPFFilter(std::shared_ptr& bpf) - { - d_data.lock()->d_bpf = bpf; - } - ~DynBPFFilter() - { - } - void excludeRange(const Netmask& range) - { - d_data.lock()->d_excludedSubnets.addMask(range); - } - void includeRange(const Netmask& range) - { - d_data.lock()->d_excludedSubnets.addMask(range, false); - } - /* returns true if the addr wasn't already blocked, false otherwise */ - bool block(const ComboAddress& addr, const struct timespec& until); - void purgeExpired(const struct timespec& now); - std::vector > getAddrStats(); -private: - struct BlockEntry - { - BlockEntry(const ComboAddress& addr, const struct timespec until): d_addr(addr), d_until(until) - { - } - ComboAddress d_addr; - struct timespec d_until; - }; - typedef boost::multi_index_container, ComboAddress::addressOnlyLessThan >, - boost::multi_index::ordered_non_unique< boost::multi_index::member > - > - > container_t; - struct Data { - container_t d_entries; - std::shared_ptr d_bpf{nullptr}; - NetmaskGroup d_excludedSubnets; - }; - LockGuarded d_data; -}; - diff --git a/pdns/dnsdist-ecs.cc b/pdns/dnsdist-ecs.cc deleted file mode 100644 index 2cad1945bca8..000000000000 --- a/pdns/dnsdist-ecs.cc +++ /dev/null @@ -1,1178 +0,0 @@ -/* - * This file is part of PowerDNS or dnsdist. - * Copyright -- PowerDNS.COM B.V. and its contributors - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of version 2 of the GNU General Public License as - * published by the Free Software Foundation. - * - * In addition, for the avoidance of any doubt, permission is granted to - * link this program with OpenSSL and to (re)distribute the binaries - * produced as the result of such linking. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ -#include "dolog.hh" -#include "dnsdist.hh" -#include "dnsdist-dnsparser.hh" -#include "dnsdist-ecs.hh" -#include "dnsparser.hh" -#include "dnswriter.hh" -#include "ednsoptions.hh" -#include "ednssubnet.hh" - -/* when we add EDNS to a query, we don't want to advertise - a large buffer size */ -size_t g_EdnsUDPPayloadSize = 512; -static const uint16_t defaultPayloadSizeSelfGenAnswers = 1232; -static_assert(defaultPayloadSizeSelfGenAnswers < s_udpIncomingBufferSize, "The UDP responder's payload size should be smaller or equal to our incoming buffer size"); -uint16_t g_PayloadSizeSelfGenAnswers{defaultPayloadSizeSelfGenAnswers}; - -/* draft-ietf-dnsop-edns-client-subnet-04 "11.1. Privacy" */ -uint16_t g_ECSSourcePrefixV4 = 24; -uint16_t g_ECSSourcePrefixV6 = 56; - -bool g_ECSOverride{false}; -bool g_addEDNSToSelfGeneratedResponses{true}; - -int rewriteResponseWithoutEDNS(const PacketBuffer& initialPacket, PacketBuffer& newContent) -{ - assert(initialPacket.size() >= sizeof(dnsheader)); - const dnsheader_aligned dh(initialPacket.data()); - - if (ntohs(dh->arcount) == 0) { - return ENOENT; - } - - if (ntohs(dh->qdcount) == 0) { - return ENOENT; - } - - PacketReader pr(std::string_view(reinterpret_cast(initialPacket.data()), initialPacket.size())); - - 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(); - - GenericDNSPacketWriter 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 { - - pr.skip(ah.d_clen); - } - } - pw.commit(); - - return 0; -} - -static bool addOrReplaceEDNSOption(std::vector>& options, uint16_t optionCode, bool& optionAdded, bool overrideExisting, const string& newOptionContent) -{ - for (auto it = options.begin(); it != options.end(); ) { - if (it->first == optionCode) { - optionAdded = false; - - if (!overrideExisting) { - return false; - } - - it = options.erase(it); - } - else { - ++it; - } - } - - options.emplace_back(optionCode, std::string(&newOptionContent.at(EDNS_OPTION_CODE_SIZE + EDNS_OPTION_LENGTH_SIZE), newOptionContent.size() - (EDNS_OPTION_CODE_SIZE + EDNS_OPTION_LENGTH_SIZE))); - return true; -} - -bool slowRewriteEDNSOptionInQueryWithRecords(const PacketBuffer& initialPacket, PacketBuffer& newContent, bool& ednsAdded, uint16_t optionToReplace, bool& optionAdded, bool overrideExisting, const string& newOptionContent) -{ - assert(initialPacket.size() >= sizeof(dnsheader)); - const dnsheader_aligned dh(initialPacket.data()); - - if (ntohs(dh->qdcount) == 0) { - return false; - } - - if (ntohs(dh->ancount) == 0 && ntohs(dh->nscount) == 0 && ntohs(dh->arcount) == 0) { - throw std::runtime_error(std::string(__PRETTY_FUNCTION__) + " should not be called for queries that have no records"); - } - - optionAdded = false; - ednsAdded = true; - - PacketReader pr(std::string_view(reinterpret_cast(initialPacket.data()), initialPacket.size())); - - 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(); - - GenericDNSPacketWriter 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> options; - getEDNSOptionsFromContent(blob, options); - - /* getDnsrecordheader() has helpfully converted the TTL for us, which we do not want in that case */ - uint32_t ttl = htonl(ah.d_ttl); - EDNS0Record edns0; - static_assert(sizeof(edns0) == sizeof(ttl), "sizeof(EDNS0Record) must match sizeof(uint32_t) AKA RR TTL size"); - memcpy(&edns0, &ttl, sizeof(edns0)); - - /* addOrReplaceEDNSOption will set it to false if there is already an existing option */ - optionAdded = true; - addOrReplaceEDNSOption(options, optionToReplace, optionAdded, overrideExisting, newOptionContent); - pw.addOpt(ah.d_class, edns0.extRCode, edns0.extFlags, options, edns0.version); - } - } - - if (ednsAdded) { - pw.addOpt(g_EdnsUDPPayloadSize, 0, 0, {{optionToReplace, std::string(&newOptionContent.at(EDNS_OPTION_CODE_SIZE + EDNS_OPTION_LENGTH_SIZE), newOptionContent.size() - (EDNS_OPTION_CODE_SIZE + EDNS_OPTION_LENGTH_SIZE))}}, 0); - optionAdded = true; - } - - pw.commit(); - - return true; -} - -static bool slowParseEDNSOptions(const PacketBuffer& packet, EDNSOptionViewMap& options) -{ - if (packet.size() < sizeof(dnsheader)) { - return false; - } - - const dnsheader_aligned dh(packet.data()); - - if (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(reinterpret_cast(&packet.at(0))), packet.size()); - 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 >= packet.size()) { - return false; - } - /* if we survive this call, we can parse it safely */ - dpm.skipRData(); - return getEDNSOptions(reinterpret_cast(&packet.at(offset)), packet.size() - offset, options) == 0; - } - else { - dpm.skipRData(); - } - } - } - catch(...) - { - return false; - } - - return true; -} - -int locateEDNSOptRR(const PacketBuffer& packet, uint16_t * optStart, size_t * optLen, bool * last) -{ - assert(optStart != NULL); - assert(optLen != NULL); - assert(last != NULL); - const dnsheader_aligned dh(packet.data()); - - if (ntohs(dh->arcount) == 0) { - return ENOENT; - } - - PacketReader pr(std::string_view(reinterpret_cast(packet.data()), packet.size())); - - 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; - struct dnsrecordheader ah; - - /* consume qd */ - for(idx = 0; idx < qdcount; idx++) { - rrname = pr.getName(); - rrtype = pr.get16BitInt(); - rrclass = pr.get16BitInt(); - (void) rrtype; - (void) rrclass; - } - - /* consume AN and NS */ - for (idx = 0; idx < ancount + nscount; idx++) { - rrname = pr.getName(); - pr.getDnsrecordheader(ah); - pr.skip(ah.d_clen); - } - - /* consume AR, looking for OPT */ - for (idx = 0; idx < arcount; idx++) { - uint16_t start = pr.getPosition(); - rrname = pr.getName(); - pr.getDnsrecordheader(ah); - - if (ah.d_type == QType::OPT) { - *optStart = start; - *optLen = (pr.getPosition() - start) + ah.d_clen; - - if (packet.size() < (*optStart + *optLen)) { - throw std::range_error("Opt record overflow"); - } - - if (idx == ((size_t) arcount - 1)) { - *last = true; - } - else { - *last = false; - } - return 0; - } - pr.skip(ah.d_clen); - } - - return ENOENT; -} - -/* extract the start of the OPT RR in a QUERY packet if any */ -int getEDNSOptionsStart(const PacketBuffer& packet, const size_t offset, uint16_t* optRDPosition, size_t* remaining) -{ - assert(optRDPosition != nullptr); - assert(remaining != nullptr); - const dnsheader_aligned dh(packet.data()); - - if (offset >= packet.size()) { - return ENOENT; - } - - if (ntohs(dh->qdcount) != 1 || ntohs(dh->ancount) != 0 || ntohs(dh->arcount) != 1 || ntohs(dh->nscount) != 0) { - return ENOENT; - } - - size_t pos = sizeof(dnsheader) + offset; - pos += DNS_TYPE_SIZE + DNS_CLASS_SIZE; - - if (pos >= packet.size()) - return ENOENT; - - if ((pos + /* root */ 1 + DNS_TYPE_SIZE + DNS_CLASS_SIZE) >= packet.size()) { - return ENOENT; - } - - if (packet[pos] != 0) { - /* not the root so not an OPT record */ - return ENOENT; - } - pos += 1; - - uint16_t qtype = packet.at(pos)*256 + packet.at(pos+1); - pos += DNS_TYPE_SIZE; - pos += DNS_CLASS_SIZE; - - if (qtype != QType::OPT || (packet.size() - pos) < (DNS_TTL_SIZE + DNS_RDLENGTH_SIZE)) { - return ENOENT; - } - - pos += DNS_TTL_SIZE; - *optRDPosition = pos; - *remaining = packet.size() - pos; - - return 0; -} - -void generateECSOption(const ComboAddress& source, string& res, uint16_t ECSPrefixLength) -{ - Netmask sourceNetmask(source, ECSPrefixLength); - EDNSSubnetOpts ecsOpts; - ecsOpts.source = sourceNetmask; - string payload = makeEDNSSubnetOptsString(ecsOpts); - generateEDNSOption(EDNSOptionCode::ECS, payload, res); -} - -bool generateOptRR(const std::string& optRData, PacketBuffer& res, size_t maximumSize, uint16_t udpPayloadSize, uint8_t ednsrcode, bool dnssecOK) -{ - const uint8_t name = 0; - dnsrecordheader dh; - EDNS0Record edns0; - edns0.extRCode = ednsrcode; - edns0.version = 0; - edns0.extFlags = dnssecOK ? htons(EDNS_HEADER_FLAG_DO) : 0; - - if ((maximumSize - res.size()) < (sizeof(name) + sizeof(dh) + optRData.length())) { - return false; - } - - dh.d_type = htons(QType::OPT); - dh.d_class = htons(udpPayloadSize); - static_assert(sizeof(EDNS0Record) == sizeof(dh.d_ttl), "sizeof(EDNS0Record) must match sizeof(dnsrecordheader.d_ttl)"); - memcpy(&dh.d_ttl, &edns0, sizeof edns0); - dh.d_clen = htons(static_cast(optRData.length())); - - res.reserve(res.size() + sizeof(name) + sizeof(dh) + optRData.length()); - res.insert(res.end(), reinterpret_cast(&name), reinterpret_cast(&name) + sizeof(name)); - res.insert(res.end(), reinterpret_cast(&dh), reinterpret_cast(&dh) + sizeof(dh)); - res.insert(res.end(), reinterpret_cast(optRData.data()), reinterpret_cast(optRData.data()) + optRData.length()); - - return true; -} - -static bool replaceEDNSClientSubnetOption(PacketBuffer& packet, size_t maximumSize, size_t const oldEcsOptionStartPosition, size_t const oldEcsOptionSize, size_t const optRDLenPosition, const string& newECSOption) -{ - assert(oldEcsOptionStartPosition < packet.size()); - assert(optRDLenPosition < packet.size()); - - if (newECSOption.size() == oldEcsOptionSize) { - /* same size as the existing option */ - memcpy(&packet.at(oldEcsOptionStartPosition), newECSOption.c_str(), oldEcsOptionSize); - } - else { - /* different size than the existing option */ - const unsigned int newPacketLen = packet.size() + (newECSOption.length() - oldEcsOptionSize); - const size_t beforeOptionLen = oldEcsOptionStartPosition; - const size_t dataBehindSize = packet.size() - beforeOptionLen - oldEcsOptionSize; - - /* check that it fits in the existing buffer */ - if (newPacketLen > packet.size()) { - if (newPacketLen > maximumSize) { - return false; - } - - packet.resize(newPacketLen); - } - - /* fix the size of ECS Option RDLen */ - uint16_t newRDLen = (packet.at(optRDLenPosition) * 256) + packet.at(optRDLenPosition + 1); - newRDLen += (newECSOption.size() - oldEcsOptionSize); - packet.at(optRDLenPosition) = newRDLen / 256; - packet.at(optRDLenPosition + 1) = newRDLen % 256; - - if (dataBehindSize > 0) { - memmove(&packet.at(oldEcsOptionStartPosition), &packet.at(oldEcsOptionStartPosition + oldEcsOptionSize), dataBehindSize); - } - memcpy(&packet.at(oldEcsOptionStartPosition + dataBehindSize), newECSOption.c_str(), newECSOption.size()); - packet.resize(newPacketLen); - } - - return true; -} - -/* This function looks for an OPT RR, return true if a valid one was found (even if there was no options) - and false otherwise. */ -bool parseEDNSOptions(const DNSQuestion& dq) -{ - const auto dh = dq.getHeader(); - if (dq.ednsOptions != nullptr) { - return true; - } - - // dq.ednsOptions is mutable - dq.ednsOptions = std::make_unique(); - - if (ntohs(dh->arcount) == 0) { - /* nothing in additional so no EDNS */ - return false; - } - - if (ntohs(dh->ancount) != 0 || ntohs(dh->nscount) != 0 || ntohs(dh->arcount) > 1) { - return slowParseEDNSOptions(dq.getData(), *dq.ednsOptions); - } - - size_t remaining = 0; - uint16_t optRDPosition; - int res = getEDNSOptionsStart(dq.getData(), dq.ids.qname.wirelength(), &optRDPosition, &remaining); - - if (res == 0) { - res = getEDNSOptions(reinterpret_cast(&dq.getData().at(optRDPosition)), remaining, *dq.ednsOptions); - return (res == 0); - } - - return false; -} - -static bool addECSToExistingOPT(PacketBuffer& packet, size_t maximumSize, const string& newECSOption, size_t optRDLenPosition, 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, - no NS and no AN */ - uint16_t oldRDLen = (packet.at(optRDLenPosition) * 256) + packet.at(optRDLenPosition + 1); - if (packet.size() != (optRDLenPosition + sizeof(uint16_t) + oldRDLen)) { - /* we are supposed to be the last record, do we have some trailing data to remove? */ - uint32_t realPacketLen = getDNSPacketLength(reinterpret_cast(packet.data()), packet.size()); - packet.resize(realPacketLen); - } - - if ((maximumSize - packet.size()) < newECSOption.size()) { - return false; - } - - uint16_t newRDLen = oldRDLen + newECSOption.size(); - packet.at(optRDLenPosition) = newRDLen / 256; - packet.at(optRDLenPosition + 1) = newRDLen % 256; - - packet.insert(packet.end(), newECSOption.begin(), newECSOption.end()); - ecsAdded = true; - - return true; -} - -static bool addEDNSWithECS(PacketBuffer& packet, size_t maximumSize, const string& newECSOption, bool& ednsAdded, bool& ecsAdded) -{ - if (!generateOptRR(newECSOption, packet, maximumSize, g_EdnsUDPPayloadSize, 0, false)) { - return false; - } - - dnsdist::PacketMangling::editDNSHeaderFromPacket(packet, [](dnsheader& header) { - uint16_t arcount = ntohs(header.arcount); - arcount++; - header.arcount = htons(arcount); - return true; - }); - ednsAdded = true; - ecsAdded = true; - - return true; -} - -bool handleEDNSClientSubnet(PacketBuffer& packet, const size_t maximumSize, const size_t qnameWireLength, bool& ednsAdded, bool& ecsAdded, bool overrideExisting, const string& newECSOption) -{ - assert(qnameWireLength <= packet.size()); - - const dnsheader_aligned dh(packet.data()); - - if (ntohs(dh->ancount) != 0 || ntohs(dh->nscount) != 0 || (ntohs(dh->arcount) != 0 && ntohs(dh->arcount) != 1)) { - PacketBuffer newContent; - newContent.reserve(packet.size()); - - if (!slowRewriteEDNSOptionInQueryWithRecords(packet, newContent, ednsAdded, EDNSOptionCode::ECS, ecsAdded, overrideExisting, newECSOption)) { - return false; - } - - if (newContent.size() > maximumSize) { - ednsAdded = false; - ecsAdded = false; - return false; - } - - packet = std::move(newContent); - return true; - } - - uint16_t optRDPosition = 0; - size_t remaining = 0; - - int res = getEDNSOptionsStart(packet, qnameWireLength, &optRDPosition, &remaining); - - if (res != 0) { - /* no EDNS but there might be another record in additional (TSIG?) */ - /* Careful, this code assumes that ANCOUNT == 0 && NSCOUNT == 0 */ - size_t minimumPacketSize = sizeof(dnsheader) + qnameWireLength + sizeof(uint16_t) + sizeof(uint16_t); - if (packet.size() > minimumPacketSize) { - if (ntohs(dh->arcount) == 0) { - /* well now.. */ - packet.resize(minimumPacketSize); - } - else { - uint32_t realPacketLen = getDNSPacketLength(reinterpret_cast(packet.data()), packet.size()); - packet.resize(realPacketLen); - } - } - - return addEDNSWithECS(packet, maximumSize, newECSOption, ednsAdded, ecsAdded); - } - - size_t ecsOptionStartPosition = 0; - size_t ecsOptionSize = 0; - - res = getEDNSOption(reinterpret_cast(&packet.at(optRDPosition)), remaining, EDNSOptionCode::ECS, &ecsOptionStartPosition, &ecsOptionSize); - - if (res == 0) { - /* there is already an ECS value */ - if (!overrideExisting) { - return true; - } - - return replaceEDNSClientSubnetOption(packet, maximumSize, optRDPosition + ecsOptionStartPosition, ecsOptionSize, optRDPosition, newECSOption); - } else { - /* we have an EDNS OPT RR but no existing ECS option */ - return addECSToExistingOPT(packet, maximumSize, newECSOption, optRDPosition, ecsAdded); - } - - return true; -} - -bool handleEDNSClientSubnet(DNSQuestion& dq, bool& ednsAdded, bool& ecsAdded) -{ - string newECSOption; - generateECSOption(dq.ecs ? dq.ecs->getNetwork() : dq.ids.origRemote, newECSOption, dq.ecs ? dq.ecs->getBits() : dq.ecsPrefixLength); - - return handleEDNSClientSubnet(dq.getMutableData(), dq.getMaximumSize(), dq.ids.qname.wirelength(), ednsAdded, ecsAdded, dq.ecsOverride, newECSOption); -} - -static int removeEDNSOptionFromOptions(unsigned char* optionsStart, const uint16_t optionsLen, const uint16_t optionCodeToRemove, uint16_t* newOptionsLen) -{ - unsigned char* p = optionsStart; - size_t pos = 0; - while ((pos + 4) <= optionsLen) { - unsigned char* optionBegin = p; - const uint16_t optionCode = 0x100*p[0] + p[1]; - p += sizeof(optionCode); - pos += sizeof(optionCode); - const uint16_t optionLen = 0x100*p[0] + p[1]; - p += sizeof(optionLen); - pos += sizeof(optionLen); - if ((pos + optionLen) > optionsLen) { - return EINVAL; - } - if (optionCode == optionCodeToRemove) { - if (pos + optionLen < optionsLen) { - /* move remaining options over the removed one, - if any */ - memmove(optionBegin, p + optionLen, optionsLen - (pos + optionLen)); - } - *newOptionsLen = optionsLen - (sizeof(optionCode) + sizeof(optionLen) + optionLen); - return 0; - } - p += optionLen; - pos += optionLen; - } - return ENOENT; -} - -int removeEDNSOptionFromOPT(char* optStart, size_t* optLen, const uint16_t optionCodeToRemove) -{ - if (*optLen < optRecordMinimumSize) { - return EINVAL; - } - const unsigned char* end = (const unsigned char*) optStart + *optLen; - unsigned char* p = (unsigned char*) optStart + 9; - unsigned char* rdLenPtr = p; - uint16_t rdLen = (0x100*p[0] + p[1]); - p += sizeof(rdLen); - if (p + rdLen != end) { - return EINVAL; - } - uint16_t newRdLen = 0; - int res = removeEDNSOptionFromOptions(p, rdLen, optionCodeToRemove, &newRdLen); - if (res != 0) { - return res; - } - *optLen -= (rdLen - newRdLen); - rdLenPtr[0] = newRdLen / 0x100; - rdLenPtr[1] = newRdLen % 0x100; - return 0; -} - -bool isEDNSOptionInOpt(const PacketBuffer& packet, const size_t optStart, const size_t optLen, const uint16_t optionCodeToFind, size_t* optContentStart, uint16_t* optContentLen) -{ - if (optLen < optRecordMinimumSize) { - return false; - } - size_t p = optStart + 9; - uint16_t rdLen = (0x100*static_cast(packet.at(p)) + static_cast(packet.at(p+1))); - p += sizeof(rdLen); - if (rdLen > (optLen - optRecordMinimumSize)) { - return false; - } - - size_t rdEnd = p + rdLen; - while ((p + 4) <= rdEnd) { - const uint16_t optionCode = 0x100*static_cast(packet.at(p)) + static_cast(packet.at(p+1)); - p += sizeof(optionCode); - const uint16_t optionLen = 0x100*static_cast(packet.at(p)) + static_cast(packet.at(p+1)); - p += sizeof(optionLen); - - if ((p + optionLen) > rdEnd) { - return false; - } - - if (optionCode == optionCodeToFind) { - if (optContentStart != nullptr) { - *optContentStart = p; - } - - if (optContentLen != nullptr) { - *optContentLen = optionLen; - } - - return true; - } - p += optionLen; - } - return false; -} - -int rewriteResponseWithoutEDNSOption(const PacketBuffer& initialPacket, const uint16_t optionCodeToSkip, PacketBuffer& newContent) -{ - assert(initialPacket.size() >= sizeof(dnsheader)); - const dnsheader_aligned dh(initialPacket.data()); - - if (ntohs(dh->arcount) == 0) - return ENOENT; - - if (ntohs(dh->qdcount) == 0) - return ENOENT; - - PacketReader pr(std::string_view(reinterpret_cast(initialPacket.data()), initialPacket.size())); - - 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(); - - GenericDNSPacketWriter 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 { - pw.startRecord(rrname, ah.d_type, ah.d_ttl, ah.d_class, DNSResourceRecord::ADDITIONAL, false); - pr.xfrBlob(blob); - uint16_t rdLen = blob.length(); - removeEDNSOptionFromOptions((unsigned char*)blob.c_str(), rdLen, optionCodeToSkip, &rdLen); - /* xfrBlob(string, size) completely ignores size.. */ - if (rdLen > 0) { - blob.resize((size_t)rdLen); - pw.xfrBlob(blob); - } else { - pw.commit(); - } - } - } - pw.commit(); - - return 0; -} - -bool addEDNS(PacketBuffer& packet, size_t maximumSize, bool dnssecOK, uint16_t payloadSize, uint8_t ednsrcode) -{ - if (!generateOptRR(std::string(), packet, maximumSize, payloadSize, ednsrcode, dnssecOK)) { - return false; - } - - dnsdist::PacketMangling::editDNSHeaderFromPacket(packet, [](dnsheader& header) { - header.arcount = htons(ntohs(header.arcount) + 1); - return true; - }); - - 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 (or optionally the authority section for a full cacheable NXDOMAIN/NODATA). -*/ -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 soaInAuthoritySection) -{ - auto& packet = dq.getMutableData(); - auto dh = dq.getHeader(); - if (ntohs(dh->qdcount) != 1) { - return false; - } - - size_t queryPartSize = sizeof(dnsheader) + dq.ids.qname.wirelength() + DNS_TYPE_SIZE + DNS_CLASS_SIZE; - if (packet.size() < queryPartSize) { - /* something is already wrong, don't build on flawed foundations */ - return false; - } - - 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; - bool hadEDNS = false; - bool dnssecOK = false; - - if (g_addEDNSToSelfGeneratedResponses) { - uint16_t payloadSize = 0; - uint16_t z = 0; - hadEDNS = getEDNSUDPPayloadSizeAndZ(reinterpret_cast(packet.data()), packet.size(), &payloadSize, &z); - if (hadEDNS) { - dnssecOK = z & EDNS_HEADER_FLAG_DO; - } - } - - /* chop off everything after the question */ - packet.resize(queryPartSize); - dnsdist::PacketMangling::editDNSHeaderFromPacket(packet, [nxd](dnsheader& header) { - if (nxd) { - header.rcode = RCode::NXDomain; - } - else { - header.rcode = RCode::NoError; - } - header.qr = true; - header.ancount = 0; - header.nscount = 0; - header.arcount = 0; - return true; - }); - - 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(&qtype), sizeof(qtype)); - soa.append(reinterpret_cast(&qclass), sizeof(qclass)); - soa.append(reinterpret_cast(&ttl), sizeof(ttl)); - soa.append(reinterpret_cast(&rdLength), sizeof(rdLength)); - soa.append(mname.toDNSString()); - soa.append(rname.toDNSString()); - soa.append(reinterpret_cast(&serial), sizeof(serial)); - soa.append(reinterpret_cast(&refresh), sizeof(refresh)); - soa.append(reinterpret_cast(&retry), sizeof(retry)); - soa.append(reinterpret_cast(&expire), sizeof(expire)); - soa.append(reinterpret_cast(&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)); - } - - packet.insert(packet.end(), soa.begin(), soa.end()); - - /* We are populating a response with only the query in place, order of sections is QD,AN,NS,AR - NS (authority) is before AR (additional) so we can just decide which section the SOA record is in here - and have EDNS added to AR afterwards */ - dnsdist::PacketMangling::editDNSHeaderFromPacket(packet, [soaInAuthoritySection](dnsheader& header) { - if (soaInAuthoritySection) { - header.nscount = htons(1); - } else { - header.arcount = htons(1); - } - return true; - }); - - if (hadEDNS) { - /* now we need to add a new OPT record */ - return addEDNS(packet, dq.getMaximumSize(), dnssecOK, g_PayloadSizeSelfGenAnswers, dq.ednsRCode); - } - - return true; -} - -bool addEDNSToQueryTurnedResponse(DNSQuestion& dq) -{ - uint16_t optRDPosition; - /* remaining is at least the size of the rdlen + the options if any + the following records if any */ - size_t remaining = 0; - - auto& packet = dq.getMutableData(); - int res = getEDNSOptionsStart(packet, dq.ids.qname.wirelength(), &optRDPosition, &remaining); - - if (res != 0) { - /* if the initial query did not have EDNS0, we are done */ - return true; - } - - const size_t existingOptLen = /* root */ 1 + DNS_TYPE_SIZE + DNS_CLASS_SIZE + EDNS_EXTENDED_RCODE_SIZE + EDNS_VERSION_SIZE + /* Z */ 2 + remaining; - if (existingOptLen >= packet.size()) { - /* something is wrong, bail out */ - return false; - } - - uint8_t* optRDLen = &packet.at(optRDPosition); - uint8_t* optPtr = (optRDLen - (/* root */ 1 + DNS_TYPE_SIZE + DNS_CLASS_SIZE + EDNS_EXTENDED_RCODE_SIZE + EDNS_VERSION_SIZE + /* Z */ 2)); - - const uint8_t* zPtr = optPtr + /* root */ 1 + DNS_TYPE_SIZE + DNS_CLASS_SIZE + EDNS_EXTENDED_RCODE_SIZE + EDNS_VERSION_SIZE; - uint16_t z = 0x100 * (*zPtr) + *(zPtr + 1); - bool dnssecOK = z & EDNS_HEADER_FLAG_DO; - - /* remove the existing OPT record, and everything else that follows (any SIG or TSIG would be useless anyway) */ - packet.resize(packet.size() - existingOptLen); - dnsdist::PacketMangling::editDNSHeaderFromPacket(packet, [](dnsheader& header) { - header.arcount = 0; - return true; - }); - - if (g_addEDNSToSelfGeneratedResponses) { - /* now we need to add a new OPT record */ - return addEDNS(packet, dq.getMaximumSize(), dnssecOK, g_PayloadSizeSelfGenAnswers, dq.ednsRCode); - } - - /* otherwise we are just fine */ - return true; -} - -// goal in life - if you send us a reasonably normal packet, we'll get Z for you, otherwise 0 -int getEDNSZ(const DNSQuestion& dq) -{ - try - { - const auto& dh = dq.getHeader(); - if (ntohs(dh->qdcount) != 1 || dh->ancount != 0 || ntohs(dh->arcount) != 1 || dh->nscount != 0) { - return 0; - } - - if (dq.getData().size() <= sizeof(dnsheader)) { - return 0; - } - - size_t pos = sizeof(dnsheader) + dq.ids.qname.wirelength() + DNS_TYPE_SIZE + DNS_CLASS_SIZE; - - if (dq.getData().size() <= (pos + /* root */ 1 + DNS_TYPE_SIZE + DNS_CLASS_SIZE)) { - return 0; - } - - auto& packet = dq.getData(); - - if (packet.at(pos) != 0) { - /* not root, so not a valid OPT record */ - return 0; - } - - pos++; - - uint16_t qtype = packet.at(pos)*256 + packet.at(pos+1); - pos += DNS_TYPE_SIZE; - pos += DNS_CLASS_SIZE; - - if (qtype != QType::OPT || (pos + EDNS_EXTENDED_RCODE_SIZE + EDNS_VERSION_SIZE + 1) >= packet.size()) { - return 0; - } - - const uint8_t* z = &packet.at(pos + EDNS_EXTENDED_RCODE_SIZE + EDNS_VERSION_SIZE); - return 0x100 * (*z) + *(z+1); - } - catch(...) - { - return 0; - } -} - -bool queryHasEDNS(const DNSQuestion& dq) -{ - uint16_t optRDPosition; - size_t ecsRemaining = 0; - - int res = getEDNSOptionsStart(dq.getData(), dq.ids.qname.wirelength(), &optRDPosition, &ecsRemaining); - if (res == 0) { - return true; - } - - return false; -} - -bool getEDNS0Record(const PacketBuffer& packet, EDNS0Record& edns0) -{ - uint16_t optStart; - size_t optLen = 0; - bool last = false; - int res = locateEDNSOptRR(packet, &optStart, &optLen, &last); - if (res != 0) { - // no EDNS OPT RR - return false; - } - - if (optLen < optRecordMinimumSize) { - return false; - } - - if (optStart < packet.size() && packet.at(optStart) != 0) { - // OPT RR Name != '.' - return false; - } - - static_assert(sizeof(EDNS0Record) == sizeof(uint32_t), "sizeof(EDNS0Record) must match sizeof(uint32_t) AKA RR TTL size"); - // copy out 4-byte "ttl" (really the EDNS0 record), after root label (1) + type (2) + class (2). - memcpy(&edns0, &packet.at(optStart + 5), sizeof edns0); - return true; -} - -bool setEDNSOption(DNSQuestion& dq, uint16_t ednsCode, const std::string& ednsData) -{ - std::string optRData; - generateEDNSOption(ednsCode, ednsData, optRData); - - if (dq.getHeader()->arcount) { - bool ednsAdded = false; - bool optionAdded = false; - PacketBuffer newContent; - newContent.reserve(dq.getData().size()); - - if (!slowRewriteEDNSOptionInQueryWithRecords(dq.getData(), newContent, ednsAdded, ednsCode, optionAdded, true, optRData)) { - return false; - } - - if (newContent.size() > dq.getMaximumSize()) { - return false; - } - - dq.getMutableData() = std::move(newContent); - if (!dq.ids.ednsAdded && ednsAdded) { - dq.ids.ednsAdded = true; - } - - return true; - } - - auto& data = dq.getMutableData(); - if (generateOptRR(optRData, data, dq.getMaximumSize(), g_EdnsUDPPayloadSize, 0, false)) { - dnsdist::PacketMangling::editDNSHeaderFromPacket(dq.getMutableData(), [](dnsheader& header) { - header.arcount = htons(1); - return true; - }); - // make sure that any EDNS sent by the backend is removed before forwarding the response to the client - dq.ids.ednsAdded = true; - } - - return true; -} - -namespace dnsdist { -bool setInternalQueryRCode(InternalQueryState& state, PacketBuffer& buffer, uint8_t rcode, bool clearAnswers) -{ - const auto qnameLength = state.qname.wirelength(); - if (buffer.size() < sizeof(dnsheader) + qnameLength + sizeof(uint16_t) + sizeof(uint16_t)) { - return false; - } - - EDNS0Record edns0; - bool hadEDNS = false; - if (clearAnswers) { - hadEDNS = getEDNS0Record(buffer, edns0); - } - - dnsdist::PacketMangling::editDNSHeaderFromPacket(buffer, [rcode,clearAnswers](dnsheader& header) { - header.rcode = rcode; - header.ad = false; - header.aa = false; - header.ra = header.rd; - header.qr = true; - - if (clearAnswers) { - header.ancount = 0; - header.nscount = 0; - header.arcount = 0; - } - return true; - }); - - if (clearAnswers) { - buffer.resize(sizeof(dnsheader) + qnameLength + sizeof(uint16_t) + sizeof(uint16_t)); - if (hadEDNS) { - DNSQuestion dq(state, buffer); - if (!addEDNS(buffer, dq.getMaximumSize(), edns0.extFlags & htons(EDNS_HEADER_FLAG_DO), g_PayloadSizeSelfGenAnswers, 0)) { - return false; - } - } - } - - return true; -} -} diff --git a/pdns/dnsdist-ecs.hh b/pdns/dnsdist-ecs.hh deleted file mode 100644 index f5d215f1a065..000000000000 --- a/pdns/dnsdist-ecs.hh +++ /dev/null @@ -1,64 +0,0 @@ -/* - * This file is part of PowerDNS or dnsdist. - * Copyright -- PowerDNS.COM B.V. and its contributors - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of version 2 of the GNU General Public License as - * published by the Free Software Foundation. - * - * In addition, for the avoidance of any doubt, permission is granted to - * link this program with OpenSSL and to (re)distribute the binaries - * produced as the result of such linking. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ -#pragma once - -#include - -#include "iputils.hh" -#include "noinitvector.hh" - -struct DNSQuestion; - -// root label (1), type (2), class (2), ttl (4) + rdlen (2) -static const size_t optRecordMinimumSize = 11; - -extern size_t g_EdnsUDPPayloadSize; -extern uint16_t g_PayloadSizeSelfGenAnswers; - -int rewriteResponseWithoutEDNS(const PacketBuffer& initialPacket, PacketBuffer& newContent); -bool slowRewriteEDNSOptionInQueryWithRecords(const PacketBuffer& initialPacket, PacketBuffer& newContent, bool& ednsAdded, uint16_t optionToReplace, bool& optionAdded, bool overrideExisting, const string& newOptionContent); -int locateEDNSOptRR(const PacketBuffer & packet, uint16_t * optStart, size_t * optLen, bool * last); -bool generateOptRR(const std::string& optRData, PacketBuffer& res, size_t maximumSize, uint16_t udpPayloadSize, uint8_t ednsrcode, bool dnssecOK); -void generateECSOption(const ComboAddress& source, string& res, uint16_t ECSPrefixLength); -int removeEDNSOptionFromOPT(char* optStart, size_t* optLen, const uint16_t optionCodeToRemove); -int rewriteResponseWithoutEDNSOption(const PacketBuffer& initialPacket, const uint16_t optionCodeToSkip, PacketBuffer& newContent); -int getEDNSOptionsStart(const PacketBuffer& packet, const size_t offset, uint16_t* optRDPosition, size_t * remaining); -bool isEDNSOptionInOpt(const PacketBuffer& packet, const size_t optStart, const size_t optLen, const uint16_t optionCodeToFind, size_t* optContentStart = nullptr, uint16_t* optContentLen = nullptr); -bool addEDNS(PacketBuffer& packet, size_t maximumSize, 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 soaInAuthoritySection); - -bool handleEDNSClientSubnet(DNSQuestion& dq, bool& ednsAdded, bool& ecsAdded); -bool handleEDNSClientSubnet(PacketBuffer& packet, size_t maximumSize, size_t qnameWireLength, bool& ednsAdded, bool& ecsAdded, bool overrideExisting, const string& newECSOption); - -bool parseEDNSOptions(const DNSQuestion& dq); - -int getEDNSZ(const DNSQuestion& dq); -bool queryHasEDNS(const DNSQuestion& dq); -bool getEDNS0Record(const PacketBuffer& packet, EDNS0Record& edns0); - -bool setEDNSOption(DNSQuestion& dq, uint16_t ednsCode, const std::string& data); - -struct InternalQueryState; -namespace dnsdist { -bool setInternalQueryRCode(InternalQueryState& state, PacketBuffer& buffer, uint8_t rcode, bool clearAnswers); -} diff --git a/pdns/dnsdist-idstate.hh b/pdns/dnsdist-idstate.hh deleted file mode 100644 index 73d5f6e5e32f..000000000000 --- a/pdns/dnsdist-idstate.hh +++ /dev/null @@ -1,261 +0,0 @@ -/* - * This file is part of PowerDNS or dnsdist. - * Copyright -- PowerDNS.COM B.V. and its contributors - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of version 2 of the GNU General Public License as - * published by the Free Software Foundation. - * - * In addition, for the avoidance of any doubt, permission is granted to - * link this program with OpenSSL and to (re)distribute the binaries - * produced as the result of such linking. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ -#pragma once - -#include - -#include "config.h" -#include "dnscrypt.hh" -#include "dnsname.hh" -#include "dnsdist-protocols.hh" -#include "ednsextendederror.hh" -#include "gettime.hh" -#include "iputils.hh" -#include "noinitvector.hh" -#include "uuid-utils.hh" - -struct ClientState; -struct DOHUnitInterface; -struct DOQUnit; -struct DOH3Unit; -class DNSCryptQuery; -class DNSDistPacketCache; - -using QTag = std::unordered_map; -using HeadersMap = std::unordered_map; - -struct StopWatch -{ - StopWatch(bool realTime = false) : - d_needRealTime(realTime) - { - } - - void start() - { - d_start = getCurrentTime(); - } - - void set(const struct timespec& from) - { - d_start = from; - } - - double udiff() const - { - struct timespec now = getCurrentTime(); - return 1000000.0 * (now.tv_sec - d_start.tv_sec) + (now.tv_nsec - d_start.tv_nsec) / 1000.0; - } - - double udiffAndSet() - { - struct timespec now = getCurrentTime(); - auto ret = 1000000.0 * (now.tv_sec - d_start.tv_sec) + (now.tv_nsec - d_start.tv_nsec) / 1000.0; - d_start = now; - return ret; - } - - struct timespec getStartTime() const - { - return d_start; - } - - struct timespec d_start - { - 0, 0 - }; - -private: - struct timespec getCurrentTime() const - { - struct timespec now; - if (gettime(&now, d_needRealTime) < 0) { - unixDie("Getting timestamp"); - } - return now; - } - - bool d_needRealTime; -}; - -class CrossProtocolContext; - -struct InternalQueryState -{ - struct ProtoBufData - { - std::optional uniqueId{std::nullopt}; // 17 - std::string d_deviceName; - std::string d_deviceID; - std::string d_requestorID; - }; - - InternalQueryState() - { - origDest.sin4.sin_family = 0; - } - - InternalQueryState(InternalQueryState&& rhs) = default; - InternalQueryState& operator=(InternalQueryState&& rhs) = default; - - InternalQueryState(const InternalQueryState& orig) = delete; - InternalQueryState& operator=(const InternalQueryState& orig) = delete; - - boost::optional subnet{boost::none}; // 40 - ComboAddress origRemote; // 28 - ComboAddress origDest; // 28 - ComboAddress hopRemote; - ComboAddress hopLocal; - DNSName qname; // 24 - std::string poolName; // 24 - StopWatch queryRealTime{true}; // 24 - std::shared_ptr packetCache{nullptr}; // 16 - std::unique_ptr dnsCryptQuery{nullptr}; // 8 - std::unique_ptr qTag{nullptr}; // 8 - std::unique_ptr d_packet{nullptr}; // Initial packet, so we can restart the query from the response path if needed // 8 - std::unique_ptr d_protoBufData{nullptr}; - std::unique_ptr d_extendedError{nullptr}; - boost::optional tempFailureTTL{boost::none}; // 8 - ClientState* cs{nullptr}; // 8 - std::unique_ptr du; // 8 - size_t d_proxyProtocolPayloadSize{0}; // 8 - int32_t d_streamID{-1}; // 4 - std::unique_ptr doqu{nullptr}; // 8 - std::unique_ptr doh3u{nullptr}; // 8 - uint32_t cacheKey{0}; // 4 - uint32_t cacheKeyNoECS{0}; // 4 - // DoH-only */ - uint32_t cacheKeyUDP{0}; // 4 - uint32_t ttlCap{0}; // cap the TTL _after_ inserting into the packet cache // 4 - int backendFD{-1}; // 4 - int delayMsec{0}; - uint16_t qtype{0}; // 2 - uint16_t qclass{0}; // 2 - // origID is in network-byte order - uint16_t origID{0}; // 2 - uint16_t origFlags{0}; // 2 - uint16_t cacheFlags{0}; // DNS flags as sent to the backend // 2 - uint16_t udpPayloadSize{0}; // Max UDP payload size from the query // 2 - dnsdist::Protocol protocol; // 1 - bool ednsAdded{false}; - bool ecsAdded{false}; - bool skipCache{false}; - bool dnssecOK{false}; - bool useZeroScope{false}; - bool forwardedOverUDP{false}; - bool selfGenerated{false}; -}; - -struct IDState -{ - IDState() - { - } - - IDState(const IDState& orig) = delete; - IDState(IDState&& rhs) noexcept : - internal(std::move(rhs.internal)) - { - inUse.store(rhs.inUse.load()); - age.store(rhs.age.load()); - } - - IDState& operator=(IDState&& rhs) noexcept - { - inUse.store(rhs.inUse.load()); - age.store(rhs.age.load()); - internal = std::move(rhs.internal); - return *this; - } - - bool isInUse() const - { - return inUse; - } - - /* For performance reasons we don't want to use a lock here, but that means - we need to be very careful when modifying this value. Modifications happen - from: - - one of the UDP or DoH 'client' threads receiving a query, selecting a backend - then picking one of the states associated to this backend (via the idOffset). - Most of the time this state should not be in use and usageIndicator is -1, but we - might not yet have received a response for the query previously associated to this - state, meaning that we will 'reuse' this state and erase the existing state. - If we ever receive a response for this state, it will be discarded. This is - mostly fine for UDP except that we still need to be careful in order to miss - the 'outstanding' counters, which should only be increased when we are picking - an empty state, and not when reusing ; - For DoH, though, we have dynamically allocated a DOHUnit object that needs to - be freed, as well as internal objects internals to libh2o. - - one of the UDP receiver threads receiving a response from a backend, picking - the corresponding state and sending the response to the client ; - - the 'healthcheck' thread scanning the states to actively discover timeouts, - mostly to keep some counters like the 'outstanding' one sane. - - We have two flags: - - inUse tells us if there currently is a in-flight query whose state is stored - in this state - - locked tells us whether someone currently owns the state, so no-one else can touch - it - */ - InternalQueryState internal; - std::atomic age{0}; - - class StateGuard - { - public: - StateGuard(IDState& ids) : - d_ids(ids) - { - } - ~StateGuard() - { - d_ids.release(); - } - StateGuard(const StateGuard&) = delete; - StateGuard(StateGuard&&) = delete; - StateGuard& operator=(const StateGuard&) = delete; - StateGuard& operator=(StateGuard&&) = delete; - - private: - IDState& d_ids; - }; - - [[nodiscard]] std::optional acquire() - { - bool expected = false; - if (locked.compare_exchange_strong(expected, true)) { - return std::optional(*this); - } - return std::nullopt; - } - - void release() - { - locked.store(false); - } - - std::atomic inUse{false}; // 1 - -private: - std::atomic locked{false}; // 1 -}; diff --git a/pdns/dnsdist-lbpolicies.hh b/pdns/dnsdist-lbpolicies.hh deleted file mode 100644 index 72443402d10f..000000000000 --- a/pdns/dnsdist-lbpolicies.hh +++ /dev/null @@ -1,114 +0,0 @@ -/* - * This file is part of PowerDNS or dnsdist. - * Copyright -- PowerDNS.COM B.V. and its contributors - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of version 2 of the GNU General Public License as - * published by the Free Software Foundation. - * - * In addition, for the avoidance of any doubt, permission is granted to - * link this program with OpenSSL and to (re)distribute the binaries - * produced as the result of such linking. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ -#pragma once - -struct dnsdist_ffi_servers_list_t; -struct dnsdist_ffi_server_t; -struct dnsdist_ffi_dnsquestion_t; - -struct DownstreamState; - -struct PerThreadPoliciesState; - -class ServerPolicy -{ -public: - template using NumberedVector = std::vector >; - using NumberedServerVector = NumberedVector>; - typedef std::function(const NumberedServerVector& servers, const DNSQuestion*)> policyfunc_t; - typedef std::function ffipolicyfunc_t; - - ServerPolicy(const std::string& name_, policyfunc_t policy_, bool isLua_): d_name(name_), d_policy(std::move(policy_)), d_isLua(isLua_) - { - } - - ServerPolicy(const std::string& name_, ffipolicyfunc_t policy_): d_name(name_), d_ffipolicy(std::move(policy_)), d_isLua(true), d_isFFI(true) - { - } - - /* create a per-thread FFI policy */ - ServerPolicy(const std::string& name_, const std::string& code); - - ServerPolicy() - { - } - - std::shared_ptr getSelectedBackend(const ServerPolicy::NumberedServerVector& servers, DNSQuestion& dq) const; - - const std::string& getName() const - { - return d_name; - } - - std::string toString() const { - return string("ServerPolicy") + (d_isLua ? " (Lua)" : "") + " \"" + d_name + "\""; - } - -private: - struct PerThreadState - { - LuaContext d_luaContext; - std::unordered_map d_policies; - bool d_initialized{false}; - }; - - const ffipolicyfunc_t& getPerThreadPolicy() const; - static thread_local PerThreadState t_perThreadState; - - -public: - std::string d_name; - std::string d_perThreadPolicyCode; - - policyfunc_t d_policy; - ffipolicyfunc_t d_ffipolicy; - - bool d_isLua{false}; - bool d_isFFI{false}; - bool d_isPerThread{false}; -}; - -struct ServerPool; - -using pools_t = map>; -std::shared_ptr getPool(const pools_t& pools, const std::string& poolName); -std::shared_ptr createPoolIfNotExists(pools_t& pools, const string& poolName); -void setPoolPolicy(pools_t& pools, const string& poolName, std::shared_ptr policy); -void addServerToPool(pools_t& pools, const string& poolName, std::shared_ptr server); -void removeServerFromPool(pools_t& pools, const string& poolName, std::shared_ptr server); - -const std::shared_ptr getDownstreamCandidates(const map>& pools, const std::string& poolName); - -std::shared_ptr firstAvailable(const ServerPolicy::NumberedServerVector& servers, const DNSQuestion* dq); - -std::shared_ptr leastOutstanding(const ServerPolicy::NumberedServerVector& servers, const DNSQuestion* dq); -std::shared_ptr wrandom(const ServerPolicy::NumberedServerVector& servers, const DNSQuestion* dq); -std::shared_ptr whashed(const ServerPolicy::NumberedServerVector& servers, const DNSQuestion* dq); -std::shared_ptr whashedFromHash(const ServerPolicy::NumberedServerVector& servers, size_t hash); -std::shared_ptr chashed(const ServerPolicy::NumberedServerVector& servers, const DNSQuestion* dq); -std::shared_ptr chashedFromHash(const ServerPolicy::NumberedServerVector& servers, size_t hash); -std::shared_ptr roundrobin(const ServerPolicy::NumberedServerVector& servers, const DNSQuestion* dq); - -extern double g_consistentHashBalancingFactor; -extern double g_weightedBalancingFactor; -extern uint32_t g_hashperturb; -extern bool g_roundrobinFailOnNoServer; diff --git a/pdns/dnsdist-lua-actions.cc b/pdns/dnsdist-lua-actions.cc deleted file mode 100644 index 94ced07641c6..000000000000 --- a/pdns/dnsdist-lua-actions.cc +++ /dev/null @@ -1,2992 +0,0 @@ -/* - * This file is part of PowerDNS or dnsdist. - * Copyright -- PowerDNS.COM B.V. and its contributors - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of version 2 of the GNU General Public License as - * published by the Free Software Foundation. - * - * In addition, for the avoidance of any doubt, permission is granted to - * link this program with OpenSSL and to (re)distribute the binaries - * produced as the result of such linking. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ -#include "config.h" -#include "threadname.hh" -#include "dnsdist.hh" -#include "dnsdist-async.hh" -#include "dnsdist-dnsparser.hh" -#include "dnsdist-ecs.hh" -#include "dnsdist-edns.hh" -#include "dnsdist-lua.hh" -#include "dnsdist-lua-ffi.hh" -#include "dnsdist-mac-address.hh" -#include "dnsdist-protobuf.hh" -#include "dnsdist-proxy-protocol.hh" -#include "dnsdist-kvs.hh" -#include "dnsdist-svc.hh" - -#include "dnstap.hh" -#include "dnswriter.hh" -#include "ednsoptions.hh" -#include "fstrm_logger.hh" -#include "remote_logger.hh" -#include "svc-records.hh" - -#include - -#include "ipcipher.hh" - -class DropAction : public DNSAction -{ -public: - DNSAction::Action operator()(DNSQuestion* dnsquestion, std::string* ruleresult) const override - { - return Action::Drop; - } - [[nodiscard]] std::string toString() const override - { - return "drop"; - } -}; - -class AllowAction : public DNSAction -{ -public: - DNSAction::Action operator()(DNSQuestion* dnsquestion, std::string* ruleresult) const override - { - return Action::Allow; - } - [[nodiscard]] std::string toString() const override - { - return "allow"; - } -}; - -class NoneAction : public DNSAction -{ -public: - // this action does not stop the processing - DNSAction::Action operator()(DNSQuestion* dnsquestion, std::string* ruleresult) const override - { - return Action::None; - } - [[nodiscard]] std::string toString() const override - { - return "no op"; - } -}; - -class QPSAction : public DNSAction -{ -public: - QPSAction(int limit) : - d_qps(QPSLimiter(limit, limit)) - { - } - DNSAction::Action operator()(DNSQuestion* dnsquestion, std::string* ruleresult) const override - { - if (d_qps.lock()->check()) { - return Action::None; - } - return Action::Drop; - } - [[nodiscard]] std::string toString() const override - { - return "qps limit to " + std::to_string(d_qps.lock()->getRate()); - } - -private: - mutable LockGuarded d_qps; -}; - -class DelayAction : public DNSAction -{ -public: - DelayAction(int msec) : - d_msec(msec) - { - } - DNSAction::Action operator()(DNSQuestion* dnsquestion, std::string* ruleresult) const override - { - *ruleresult = std::to_string(d_msec); - return Action::Delay; - } - [[nodiscard]] std::string toString() const override - { - return "delay by " + std::to_string(d_msec) + " ms"; - } - -private: - int d_msec; -}; - -class TeeAction : public DNSAction -{ -public: - // this action does not stop the processing - TeeAction(const ComboAddress& rca, const boost::optional& lca, bool addECS = false, bool addProxyProtocol = false); - TeeAction(TeeAction& other) = delete; - TeeAction(TeeAction&& other) = delete; - TeeAction& operator=(TeeAction& other) = delete; - TeeAction& operator=(TeeAction&& other) = delete; - ~TeeAction() override; - DNSAction::Action operator()(DNSQuestion* dnsquestion, std::string* ruleresult) const override; - [[nodiscard]] std::string toString() const override; - std::map getStats() const override; - -private: - void worker(); - - ComboAddress d_remote; - std::thread d_worker; - Socket d_socket; - mutable std::atomic d_senderrors{0}; - unsigned long d_recverrors{0}; - mutable std::atomic d_queries{0}; - stat_t d_responses{0}; - stat_t d_nxdomains{0}; - stat_t d_servfails{0}; - stat_t d_refuseds{0}; - stat_t d_formerrs{0}; - stat_t d_notimps{0}; - stat_t d_noerrors{0}; - mutable stat_t d_tcpdrops{0}; - stat_t d_otherrcode{0}; - std::atomic d_pleaseQuit{false}; - bool d_addECS{false}; - bool d_addProxyProtocol{false}; -}; - -TeeAction::TeeAction(const ComboAddress& rca, const boost::optional& lca, bool addECS, bool addProxyProtocol) : - d_remote(rca), d_socket(d_remote.sin4.sin_family, SOCK_DGRAM, 0), d_addECS(addECS), d_addProxyProtocol(addProxyProtocol) -{ - if (lca) { - d_socket.bind(*lca, false); - } - d_socket.connect(d_remote); - d_socket.setNonBlocking(); - d_worker = std::thread([this]() { - worker(); - }); -} - -TeeAction::~TeeAction() -{ - d_pleaseQuit = true; - close(d_socket.releaseHandle()); - d_worker.join(); -} - -DNSAction::Action TeeAction::operator()(DNSQuestion* dnsquestion, std::string* ruleresult) const -{ - if (dnsquestion->overTCP()) { - d_tcpdrops++; - return DNSAction::Action::None; - } - - d_queries++; - - PacketBuffer query; - if (d_addECS) { - query = dnsquestion->getData(); - bool ednsAdded = false; - bool ecsAdded = false; - - std::string newECSOption; - generateECSOption(dnsquestion->ecs ? dnsquestion->ecs->getNetwork() : dnsquestion->ids.origRemote, newECSOption, dnsquestion->ecs ? dnsquestion->ecs->getBits() : dnsquestion->ecsPrefixLength); - - if (!handleEDNSClientSubnet(query, dnsquestion->getMaximumSize(), dnsquestion->ids.qname.wirelength(), ednsAdded, ecsAdded, dnsquestion->ecsOverride, newECSOption)) { - return DNSAction::Action::None; - } - } - - if (d_addProxyProtocol) { - auto proxyPayload = getProxyProtocolPayload(*dnsquestion); - if (query.empty()) { - query = dnsquestion->getData(); - } - if (!addProxyProtocol(query, proxyPayload)) { - return DNSAction::Action::None; - } - } - - { - const PacketBuffer& payload = query.empty() ? dnsquestion->getData() : query; - auto res = send(d_socket.getHandle(), payload.data(), payload.size(), 0); - - if (res <= 0) { - d_senderrors++; - } - } - - return DNSAction::Action::None; -} - -std::string TeeAction::toString() const -{ - return "tee to " + d_remote.toStringWithPort(); -} - -std::map TeeAction::getStats() const -{ - return {{"queries", d_queries}, - {"responses", d_responses}, - {"recv-errors", d_recverrors}, - {"send-errors", d_senderrors}, - {"noerrors", d_noerrors}, - {"nxdomains", d_nxdomains}, - {"refuseds", d_refuseds}, - {"servfails", d_servfails}, - {"other-rcode", d_otherrcode}, - {"tcp-drops", d_tcpdrops}}; -} - -void TeeAction::worker() -{ - setThreadName("dnsdist/TeeWork"); - std::array packet{}; - ssize_t res = 0; - const dnsheader_aligned dnsheader(packet.data()); - for (;;) { - res = waitForData(d_socket.getHandle(), 0, 250000); - if (d_pleaseQuit) { - break; - } - - if (res < 0) { - usleep(250000); - continue; - } - if (res == 0) { - continue; - } - res = recv(d_socket.getHandle(), packet.data(), packet.size(), 0); - if (static_cast(res) <= sizeof(struct dnsheader)) { - d_recverrors++; - } - else { - d_responses++; - } - - // NOLINTNEXTLINE(bugprone-narrowing-conversions,cppcoreguidelines-narrowing-conversions): rcode is unsigned, RCode::rcodes_ as well - if (dnsheader->rcode == RCode::NoError) { - d_noerrors++; - } - // NOLINTNEXTLINE(bugprone-narrowing-conversions,cppcoreguidelines-narrowing-conversions): rcode is unsigned, RCode::rcodes_ as well - else if (dnsheader->rcode == RCode::ServFail) { - d_servfails++; - } - // NOLINTNEXTLINE(bugprone-narrowing-conversions,cppcoreguidelines-narrowing-conversions): rcode is unsigned, RCode::rcodes_ as well - else if (dnsheader->rcode == RCode::NXDomain) { - d_nxdomains++; - } - // NOLINTNEXTLINE(bugprone-narrowing-conversions,cppcoreguidelines-narrowing-conversions): rcode is unsigned, RCode::rcodes_ as well - else if (dnsheader->rcode == RCode::Refused) { - d_refuseds++; - } - // NOLINTNEXTLINE(bugprone-narrowing-conversions,cppcoreguidelines-narrowing-conversions): rcode is unsigned, RCode::rcodes_ as well - else if (dnsheader->rcode == RCode::FormErr) { - d_formerrs++; - } - // NOLINTNEXTLINE(bugprone-narrowing-conversions,cppcoreguidelines-narrowing-conversions): rcode is unsigned, RCode::rcodes_ as well - else if (dnsheader->rcode == RCode::NotImp) { - d_notimps++; - } - } -} - -class PoolAction : public DNSAction -{ -public: - PoolAction(std::string pool, bool stopProcessing) : - d_pool(std::move(pool)), d_stopProcessing(stopProcessing) {} - - DNSAction::Action operator()(DNSQuestion* dnsquestion, std::string* ruleresult) const override - { - if (d_stopProcessing) { - /* we need to do it that way to keep compatiblity with custom Lua actions returning DNSAction.Pool, 'poolname' */ - *ruleresult = d_pool; - return Action::Pool; - } - dnsquestion->ids.poolName = d_pool; - return Action::None; - } - - [[nodiscard]] std::string toString() const override - { - return "to pool " + d_pool; - } - -private: - // NOLINTNEXTLINE(cppcoreguidelines-avoid-const-or-ref-data-members) - const std::string d_pool; - // NOLINTNEXTLINE(cppcoreguidelines-avoid-const-or-ref-data-members) - const bool d_stopProcessing; -}; - -class QPSPoolAction : public DNSAction -{ -public: - QPSPoolAction(unsigned int limit, std::string pool, bool stopProcessing) : - d_qps(QPSLimiter(limit, limit)), d_pool(std::move(pool)), d_stopProcessing(stopProcessing) {} - DNSAction::Action operator()(DNSQuestion* dnsquestion, std::string* ruleresult) const override - { - if (d_qps.lock()->check()) { - if (d_stopProcessing) { - /* we need to do it that way to keep compatiblity with custom Lua actions returning DNSAction.Pool, 'poolname' */ - *ruleresult = d_pool; - return Action::Pool; - } - dnsquestion->ids.poolName = d_pool; - } - return Action::None; - } - [[nodiscard]] std::string toString() const override - { - return "max " + std::to_string(d_qps.lock()->getRate()) + " to pool " + d_pool; - } - -private: - mutable LockGuarded d_qps; - // NOLINTNEXTLINE(cppcoreguidelines-avoid-const-or-ref-data-members) - const std::string d_pool; - // NOLINTNEXTLINE(cppcoreguidelines-avoid-const-or-ref-data-members) - const bool d_stopProcessing; -}; - -class RCodeAction : public DNSAction -{ -public: - RCodeAction(uint8_t rcode) : - d_rcode(rcode) {} - DNSAction::Action operator()(DNSQuestion* dnsquestion, std::string* ruleresult) const override - { - dnsdist::PacketMangling::editDNSHeaderFromPacket(dnsquestion->getMutableData(), [this](dnsheader& header) { - header.rcode = d_rcode; - header.qr = true; // for good measure - setResponseHeadersFromConfig(header, d_responseConfig); - return true; - }); - return Action::HeaderModify; - } - [[nodiscard]] std::string toString() const override - { - return "set rcode " + std::to_string(d_rcode); - } - [[nodiscard]] ResponseConfig& getResponseConfig() - { - return d_responseConfig; - } - -private: - ResponseConfig d_responseConfig; - uint8_t d_rcode; -}; - -class ERCodeAction : public DNSAction -{ -public: - ERCodeAction(uint8_t rcode) : - d_rcode(rcode) {} - DNSAction::Action operator()(DNSQuestion* dnsquestion, std::string* ruleresult) const override - { - dnsdist::PacketMangling::editDNSHeaderFromPacket(dnsquestion->getMutableData(), [this](dnsheader& header) { - header.rcode = (d_rcode & 0xF); - header.qr = true; // for good measure - setResponseHeadersFromConfig(header, d_responseConfig); - return true; - }); - dnsquestion->ednsRCode = ((d_rcode & 0xFFF0) >> 4); - return Action::HeaderModify; - } - [[nodiscard]] std::string toString() const override - { - return "set ercode " + ERCode::to_s(d_rcode); - } - [[nodiscard]] ResponseConfig& getResponseConfig() - { - return d_responseConfig; - } - -private: - ResponseConfig d_responseConfig; - uint8_t d_rcode; -}; - -class SpoofSVCAction : public DNSAction -{ -public: - SpoofSVCAction(const LuaArray& parameters) - { - d_payloads.reserve(parameters.size()); - - for (const auto& param : parameters) { - std::vector payload; - if (!generateSVCPayload(payload, param.second)) { - throw std::runtime_error("Unable to generate a valid SVC record from the supplied parameters"); - } - - d_totalPayloadsSize += payload.size(); - d_payloads.push_back(std::move(payload)); - - for (const auto& hint : param.second.ipv4hints) { - d_additionals4.insert({param.second.target, ComboAddress(hint)}); - } - - for (const auto& hint : param.second.ipv6hints) { - d_additionals6.insert({param.second.target, ComboAddress(hint)}); - } - } - } - - DNSAction::Action operator()(DNSQuestion* dnsquestion, std::string* ruleresult) const override - { - /* it will likely be a bit bigger than that because of additionals */ - auto numberOfRecords = d_payloads.size(); - const auto qnameWireLength = dnsquestion->ids.qname.wirelength(); - if (dnsquestion->getMaximumSize() < (sizeof(dnsheader) + qnameWireLength + 4 + numberOfRecords * 12 /* recordstart */ + d_totalPayloadsSize)) { - return Action::None; - } - - PacketBuffer newPacket; - newPacket.reserve(sizeof(dnsheader) + qnameWireLength + 4 + numberOfRecords * 12 /* recordstart */ + d_totalPayloadsSize); - GenericDNSPacketWriter packetWriter(newPacket, dnsquestion->ids.qname, dnsquestion->ids.qtype); - for (const auto& payload : d_payloads) { - packetWriter.startRecord(dnsquestion->ids.qname, dnsquestion->ids.qtype, d_responseConfig.ttl); - packetWriter.xfrBlob(payload); - packetWriter.commit(); - } - - if (newPacket.size() < dnsquestion->getMaximumSize()) { - for (const auto& additional : d_additionals4) { - packetWriter.startRecord(additional.first.isRoot() ? dnsquestion->ids.qname : additional.first, QType::A, d_responseConfig.ttl, QClass::IN, DNSResourceRecord::ADDITIONAL); - packetWriter.xfrCAWithoutPort(4, additional.second); - packetWriter.commit(); - } - } - - if (newPacket.size() < dnsquestion->getMaximumSize()) { - for (const auto& additional : d_additionals6) { - packetWriter.startRecord(additional.first.isRoot() ? dnsquestion->ids.qname : additional.first, QType::AAAA, d_responseConfig.ttl, QClass::IN, DNSResourceRecord::ADDITIONAL); - packetWriter.xfrCAWithoutPort(6, additional.second); - packetWriter.commit(); - } - } - - if (g_addEDNSToSelfGeneratedResponses && queryHasEDNS(*dnsquestion)) { - bool dnssecOK = ((getEDNSZ(*dnsquestion) & EDNS_HEADER_FLAG_DO) != 0); - packetWriter.addOpt(g_PayloadSizeSelfGenAnswers, 0, dnssecOK ? EDNS_HEADER_FLAG_DO : 0); - packetWriter.commit(); - } - - if (newPacket.size() >= dnsquestion->getMaximumSize()) { - /* sorry! */ - return Action::None; - } - - packetWriter.getHeader()->id = dnsquestion->getHeader()->id; - packetWriter.getHeader()->qr = true; // for good measure - setResponseHeadersFromConfig(*packetWriter.getHeader(), d_responseConfig); - dnsquestion->getMutableData() = std::move(newPacket); - - return Action::HeaderModify; - } - [[nodiscard]] std::string toString() const override - { - return "spoof SVC record "; - } - - [[nodiscard]] ResponseConfig& getResponseConfig() - { - return d_responseConfig; - } - -private: - ResponseConfig d_responseConfig; - std::vector> d_payloads{}; - std::set> d_additionals4{}; - std::set> d_additionals6{}; - size_t d_totalPayloadsSize{0}; -}; - -class TCAction : public DNSAction -{ -public: - DNSAction::Action operator()(DNSQuestion* dnsquestion, std::string* ruleresult) const override - { - return Action::Truncate; - } - [[nodiscard]] std::string toString() const override - { - return "tc=1 answer"; - } -}; - -class TCResponseAction : public DNSResponseAction -{ -public: - DNSResponseAction::Action operator()(DNSResponse* dnsResponse, std::string* ruleresult) const override - { - return Action::Truncate; - } - [[nodiscard]] std::string toString() const override - { - return "tc=1 answer"; - } -}; - -class LuaAction : public DNSAction -{ -public: - using func_t = std::function>(DNSQuestion* dnsquestion)>; - LuaAction(LuaAction::func_t func) : - d_func(std::move(func)) - {} - - DNSAction::Action operator()(DNSQuestion* dnsquestion, std::string* ruleresult) const override - { - try { - DNSAction::Action result{}; - { - auto lock = g_lua.lock(); - auto ret = d_func(dnsquestion); - if (ruleresult != nullptr) { - if (boost::optional rule = std::get<1>(ret)) { - *ruleresult = *rule; - } - else { - // default to empty string - ruleresult->clear(); - } - } - result = static_cast(std::get<0>(ret)); - } - dnsdist::handleQueuedAsynchronousEvents(); - return result; - } - catch (const std::exception& e) { - warnlog("LuaAction failed inside Lua, returning ServFail: %s", e.what()); - } - catch (...) { - warnlog("LuaAction failed inside Lua, returning ServFail: [unknown exception]"); - } - return DNSAction::Action::ServFail; - } - - [[nodiscard]] std::string toString() const override - { - return "Lua script"; - } - -private: - func_t d_func; -}; - -class LuaResponseAction : public DNSResponseAction -{ -public: - using func_t = std::function>(DNSResponse* response)>; - LuaResponseAction(LuaResponseAction::func_t func) : - d_func(std::move(func)) - {} - DNSResponseAction::Action operator()(DNSResponse* response, std::string* ruleresult) const override - { - try { - DNSResponseAction::Action result{}; - { - auto lock = g_lua.lock(); - auto ret = d_func(response); - if (ruleresult != nullptr) { - if (boost::optional rule = std::get<1>(ret)) { - *ruleresult = *rule; - } - else { - // default to empty string - ruleresult->clear(); - } - } - result = static_cast(std::get<0>(ret)); - } - dnsdist::handleQueuedAsynchronousEvents(); - return result; - } - catch (const std::exception& e) { - warnlog("LuaResponseAction failed inside Lua, returning ServFail: %s", e.what()); - } - catch (...) { - warnlog("LuaResponseAction failed inside Lua, returning ServFail: [unknown exception]"); - } - return DNSResponseAction::Action::ServFail; - } - - [[nodiscard]] std::string toString() const override - { - return "Lua response script"; - } - -private: - func_t d_func; -}; - -class LuaFFIAction : public DNSAction -{ -public: - using func_t = std::function; - - LuaFFIAction(LuaFFIAction::func_t func) : - d_func(std::move(func)) - { - } - - DNSAction::Action operator()(DNSQuestion* dnsquestion, std::string* ruleresult) const override - { - dnsdist_ffi_dnsquestion_t dqffi(dnsquestion); - try { - DNSAction::Action result{}; - { - auto lock = g_lua.lock(); - auto ret = d_func(&dqffi); - if (ruleresult != nullptr) { - if (dqffi.result) { - *ruleresult = *dqffi.result; - } - else { - // default to empty string - ruleresult->clear(); - } - } - result = static_cast(ret); - } - dnsdist::handleQueuedAsynchronousEvents(); - return result; - } - catch (const std::exception& e) { - warnlog("LuaFFIAction failed inside Lua, returning ServFail: %s", e.what()); - } - catch (...) { - warnlog("LuaFFIAction failed inside Lua, returning ServFail: [unknown exception]"); - } - return DNSAction::Action::ServFail; - } - - [[nodiscard]] std::string toString() const override - { - return "Lua FFI script"; - } - -private: - func_t d_func; -}; - -class LuaFFIPerThreadAction : public DNSAction -{ -public: - using func_t = std::function; - - LuaFFIPerThreadAction(std::string code) : - d_functionCode(std::move(code)), d_functionID(s_functionsCounter++) - { - } - - DNSAction::Action operator()(DNSQuestion* dnsquestion, std::string* ruleresult) const override - { - try { - auto& state = t_perThreadStates[d_functionID]; - if (!state.d_initialized) { - setupLuaFFIPerThreadContext(state.d_luaContext); - /* mark the state as initialized first so if there is a syntax error - we only try to execute the code once */ - state.d_initialized = true; - state.d_func = state.d_luaContext.executeCode(d_functionCode); - } - - if (!state.d_func) { - /* the function was not properly initialized */ - return DNSAction::Action::None; - } - - dnsdist_ffi_dnsquestion_t dqffi(dnsquestion); - auto ret = state.d_func(&dqffi); - if (ruleresult != nullptr) { - if (dqffi.result) { - *ruleresult = *dqffi.result; - } - else { - // default to empty string - ruleresult->clear(); - } - } - dnsdist::handleQueuedAsynchronousEvents(); - return static_cast(ret); - } - catch (const std::exception& e) { - warnlog("LuaFFIPerThreadAction failed inside Lua, returning ServFail: %s", e.what()); - } - catch (...) { - warnlog("LuaFFIPerthreadAction failed inside Lua, returning ServFail: [unknown exception]"); - } - return DNSAction::Action::ServFail; - } - - [[nodiscard]] std::string toString() const override - { - return "Lua FFI per-thread script"; - } - -private: - struct PerThreadState - { - LuaContext d_luaContext; - func_t d_func; - bool d_initialized{false}; - }; - static std::atomic s_functionsCounter; - static thread_local std::map t_perThreadStates; - // NOLINTNEXTLINE(cppcoreguidelines-avoid-const-or-ref-data-members) - const std::string d_functionCode; - // NOLINTNEXTLINE(cppcoreguidelines-avoid-const-or-ref-data-members) - const uint64_t d_functionID; -}; - -std::atomic LuaFFIPerThreadAction::s_functionsCounter = 0; -thread_local std::map LuaFFIPerThreadAction::t_perThreadStates; - -class LuaFFIResponseAction : public DNSResponseAction -{ -public: - using func_t = std::function; - - LuaFFIResponseAction(LuaFFIResponseAction::func_t func) : - d_func(std::move(func)) - { - } - - DNSResponseAction::Action operator()(DNSResponse* response, std::string* ruleresult) const override - { - dnsdist_ffi_dnsresponse_t ffiResponse(response); - try { - DNSResponseAction::Action result{}; - { - auto lock = g_lua.lock(); - auto ret = d_func(&ffiResponse); - if (ruleresult != nullptr) { - if (ffiResponse.result) { - *ruleresult = *ffiResponse.result; - } - else { - // default to empty string - ruleresult->clear(); - } - } - result = static_cast(ret); - } - dnsdist::handleQueuedAsynchronousEvents(); - return result; - } - catch (const std::exception& e) { - warnlog("LuaFFIResponseAction failed inside Lua, returning ServFail: %s", e.what()); - } - catch (...) { - warnlog("LuaFFIResponseAction failed inside Lua, returning ServFail: [unknown exception]"); - } - return DNSResponseAction::Action::ServFail; - } - - [[nodiscard]] std::string toString() const override - { - return "Lua FFI script"; - } - -private: - func_t d_func; -}; - -class LuaFFIPerThreadResponseAction : public DNSResponseAction -{ -public: - using func_t = std::function; - - LuaFFIPerThreadResponseAction(std::string code) : - d_functionCode(std::move(code)), d_functionID(s_functionsCounter++) - { - } - - DNSResponseAction::Action operator()(DNSResponse* response, std::string* ruleresult) const override - { - try { - auto& state = t_perThreadStates[d_functionID]; - if (!state.d_initialized) { - setupLuaFFIPerThreadContext(state.d_luaContext); - /* mark the state as initialized first so if there is a syntax error - we only try to execute the code once */ - state.d_initialized = true; - state.d_func = state.d_luaContext.executeCode(d_functionCode); - } - - if (!state.d_func) { - /* the function was not properly initialized */ - return DNSResponseAction::Action::None; - } - - dnsdist_ffi_dnsresponse_t ffiResponse(response); - auto ret = state.d_func(&ffiResponse); - if (ruleresult != nullptr) { - if (ffiResponse.result) { - *ruleresult = *ffiResponse.result; - } - else { - // default to empty string - ruleresult->clear(); - } - } - dnsdist::handleQueuedAsynchronousEvents(); - return static_cast(ret); - } - catch (const std::exception& e) { - warnlog("LuaFFIPerThreadResponseAction failed inside Lua, returning ServFail: %s", e.what()); - } - catch (...) { - warnlog("LuaFFIPerthreadResponseAction failed inside Lua, returning ServFail: [unknown exception]"); - } - return DNSResponseAction::Action::ServFail; - } - - [[nodiscard]] std::string toString() const override - { - return "Lua FFI per-thread script"; - } - -private: - struct PerThreadState - { - LuaContext d_luaContext; - func_t d_func; - bool d_initialized{false}; - }; - - static std::atomic s_functionsCounter; - static thread_local std::map t_perThreadStates; - // NOLINTNEXTLINE(cppcoreguidelines-avoid-const-or-ref-data-members) - const std::string d_functionCode; - // NOLINTNEXTLINE(cppcoreguidelines-avoid-const-or-ref-data-members) - const uint64_t d_functionID; -}; - -std::atomic LuaFFIPerThreadResponseAction::s_functionsCounter = 0; -thread_local std::map LuaFFIPerThreadResponseAction::t_perThreadStates; - -thread_local std::default_random_engine SpoofAction::t_randomEngine; - -DNSAction::Action SpoofAction::operator()(DNSQuestion* dnsquestion, std::string* ruleresult) const -{ - uint16_t qtype = dnsquestion->ids.qtype; - // do we even have a response? - if (d_cname.empty() && d_rawResponses.empty() && - // make sure pre-forged response is greater than sizeof(dnsheader) - (d_raw.size() < sizeof(dnsheader)) && d_types.count(qtype) == 0) { - return Action::None; - } - - if (d_raw.size() >= sizeof(dnsheader)) { - auto questionId = dnsquestion->getHeader()->id; - dnsquestion->getMutableData() = d_raw; - dnsdist::PacketMangling::editDNSHeaderFromPacket(dnsquestion->getMutableData(), [questionId](dnsheader& header) { - header.id = questionId; - return true; - }); - return Action::HeaderModify; - } - std::vector addrs = {}; - std::vector rawResponses = {}; - unsigned int totrdatalen = 0; - size_t numberOfRecords = 0; - if (!d_cname.empty()) { - qtype = QType::CNAME; - totrdatalen += d_cname.getStorage().size(); - numberOfRecords = 1; - } - else if (!d_rawResponses.empty()) { - rawResponses.reserve(d_rawResponses.size()); - for (const auto& rawResponse : d_rawResponses) { - totrdatalen += rawResponse.size(); - rawResponses.push_back(rawResponse); - ++numberOfRecords; - } - if (rawResponses.size() > 1) { - shuffle(rawResponses.begin(), rawResponses.end(), t_randomEngine); - } - } - 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))) { - continue; - } - 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; - } - } - - if (addrs.size() > 1) { - shuffle(addrs.begin(), addrs.end(), t_randomEngine); - } - - unsigned int qnameWireLength = 0; - // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) - DNSName ignore(reinterpret_cast(dnsquestion->getData().data()), dnsquestion->getData().size(), sizeof(dnsheader), false, nullptr, nullptr, &qnameWireLength); - - if (dnsquestion->getMaximumSize() < (sizeof(dnsheader) + qnameWireLength + 4 + numberOfRecords * 12 /* recordstart */ + totrdatalen)) { - return Action::None; - } - - bool dnssecOK = false; - bool hadEDNS = false; - if (g_addEDNSToSelfGeneratedResponses && queryHasEDNS(*dnsquestion)) { - hadEDNS = true; - dnssecOK = ((getEDNSZ(*dnsquestion) & EDNS_HEADER_FLAG_DO) != 0); - } - - auto& data = dnsquestion->getMutableData(); - data.resize(sizeof(dnsheader) + qnameWireLength + 4 + numberOfRecords * 12 /* recordstart */ + totrdatalen); // there goes your EDNS - uint8_t* dest = &(data.at(sizeof(dnsheader) + qnameWireLength + 4)); - - dnsdist::PacketMangling::editDNSHeaderFromPacket(dnsquestion->getMutableData(), [this](dnsheader& header) { - header.qr = true; // for good measure - setResponseHeadersFromConfig(header, d_responseConfig); - header.ancount = 0; - header.arcount = 0; // for now, forget about your EDNS, we're marching over it - return true; - }); - - uint32_t ttl = htonl(d_responseConfig.ttl); - uint16_t qclass = htons(dnsquestion->ids.qclass); - std::array recordstart = { - 0xc0, 0x0c, // compressed name - 0, 0, // QTYPE - 0, 0, // QCLASS - 0, 0, 0, 0, // TTL - 0, 0 // rdata length - }; - static_assert(recordstart.size() == 12, "sizeof(recordstart) must be equal to 12, otherwise the above check is invalid"); - memcpy(&recordstart[4], &qclass, sizeof(qclass)); - memcpy(&recordstart[6], &ttl, sizeof(ttl)); - bool raw = false; - - if (qtype == QType::CNAME) { - const auto& wireData = d_cname.getStorage(); // 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.data(), recordstart.size()); - // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic) - dest += recordstart.size(); - memcpy(dest, wireData.c_str(), wireData.length()); - dnsdist::PacketMangling::editDNSHeaderFromPacket(dnsquestion->getMutableData(), [](dnsheader& header) { - header.ancount++; - return true; - }); - } - else if (!rawResponses.empty()) { - if (qtype == QType::ANY && d_rawTypeForAny) { - qtype = *d_rawTypeForAny; - } - qtype = htons(qtype); - for (const auto& rawResponse : rawResponses) { - uint16_t rdataLen = htons(rawResponse.size()); - memcpy(&recordstart[2], &qtype, sizeof(qtype)); - memcpy(&recordstart[10], &rdataLen, sizeof(rdataLen)); - - memcpy(dest, recordstart.data(), sizeof(recordstart)); - // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic) - dest += recordstart.size(); - - memcpy(dest, rawResponse.c_str(), rawResponse.size()); - // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic) - dest += rawResponse.size(); - - dnsdist::PacketMangling::editDNSHeaderFromPacket(dnsquestion->getMutableData(), [](dnsheader& header) { - header.ancount++; - return true; - }); - } - raw = true; - } - else { - for (const auto& addr : addrs) { - 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.data(), recordstart.size()); - // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic) - dest += sizeof(recordstart); - - memcpy(dest, - // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) - addr.sin4.sin_family == AF_INET ? reinterpret_cast(&addr.sin4.sin_addr.s_addr) : reinterpret_cast(&addr.sin6.sin6_addr.s6_addr), - addr.sin4.sin_family == AF_INET ? sizeof(addr.sin4.sin_addr.s_addr) : sizeof(addr.sin6.sin6_addr.s6_addr)); - // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic) - dest += (addr.sin4.sin_family == AF_INET ? sizeof(addr.sin4.sin_addr.s_addr) : sizeof(addr.sin6.sin6_addr.s6_addr)); - dnsdist::PacketMangling::editDNSHeaderFromPacket(dnsquestion->getMutableData(), [](dnsheader& header) { - header.ancount++; - return true; - }); - } - } - - auto finalANCount = dnsquestion->getHeader()->ancount; - dnsdist::PacketMangling::editDNSHeaderFromPacket(dnsquestion->getMutableData(), [finalANCount](dnsheader& header) { - header.ancount = htons(finalANCount); - return true; - }); - - if (hadEDNS && !raw) { - addEDNS(dnsquestion->getMutableData(), dnsquestion->getMaximumSize(), dnssecOK, g_PayloadSizeSelfGenAnswers, 0); - } - - return Action::HeaderModify; -} - -class SetMacAddrAction : public DNSAction -{ -public: - // this action does not stop the processing - SetMacAddrAction(uint16_t code) : - d_code(code) - { - } - - DNSAction::Action operator()(DNSQuestion* dnsquestion, std::string* ruleresult) const override - { - dnsdist::MacAddress mac; - int res = dnsdist::MacAddressesCache::get(dnsquestion->ids.origRemote, mac.data(), mac.size()); - if (res != 0) { - return Action::None; - } - - std::string optRData; - // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) - generateEDNSOption(d_code, reinterpret_cast(mac.data()), optRData); - - if (dnsquestion->getHeader()->arcount > 0) { - bool ednsAdded = false; - bool optionAdded = false; - PacketBuffer newContent; - newContent.reserve(dnsquestion->getData().size()); - - if (!slowRewriteEDNSOptionInQueryWithRecords(dnsquestion->getData(), newContent, ednsAdded, d_code, optionAdded, true, optRData)) { - return Action::None; - } - - if (newContent.size() > dnsquestion->getMaximumSize()) { - return Action::None; - } - - dnsquestion->getMutableData() = std::move(newContent); - if (!dnsquestion->ids.ednsAdded && ednsAdded) { - dnsquestion->ids.ednsAdded = true; - } - - return Action::None; - } - - auto& data = dnsquestion->getMutableData(); - if (generateOptRR(optRData, data, dnsquestion->getMaximumSize(), g_EdnsUDPPayloadSize, 0, false)) { - dnsdist::PacketMangling::editDNSHeaderFromPacket(dnsquestion->getMutableData(), [](dnsheader& header) { - header.arcount = htons(1); - return true; - }); - // make sure that any EDNS sent by the backend is removed before forwarding the response to the client - dnsquestion->ids.ednsAdded = true; - } - - return Action::None; - } - [[nodiscard]] std::string toString() const override - { - return "add EDNS MAC (code=" + std::to_string(d_code) + ")"; - } - -private: - uint16_t d_code{3}; -}; - -class SetEDNSOptionAction : public DNSAction -{ -public: - // this action does not stop the processing - SetEDNSOptionAction(uint16_t code, std::string data) : - d_code(code), d_data(std::move(data)) - { - } - - DNSAction::Action operator()(DNSQuestion* dnsquestion, std::string* ruleresult) const override - { - setEDNSOption(*dnsquestion, d_code, d_data); - return Action::None; - } - - [[nodiscard]] std::string toString() const override - { - return "add EDNS Option (code=" + std::to_string(d_code) + ")"; - } - -private: - uint16_t d_code; - std::string d_data; -}; - -class SetNoRecurseAction : public DNSAction -{ -public: - // this action does not stop the processing - DNSAction::Action operator()(DNSQuestion* dnsquestion, std::string* ruleresult) const override - { - dnsdist::PacketMangling::editDNSHeaderFromPacket(dnsquestion->getMutableData(), [](dnsheader& header) { - header.rd = false; - return true; - }); - return Action::None; - } - [[nodiscard]] std::string toString() const override - { - return "set rd=0"; - } -}; - -class LogAction : public DNSAction, public boost::noncopyable -{ -public: - // this action does not stop the processing - LogAction() = default; - - LogAction(const std::string& str, bool binary = true, bool append = false, bool buffered = true, bool verboseOnly = true, bool includeTimestamp = false) : - d_fname(str), d_binary(binary), d_verboseOnly(verboseOnly), d_includeTimestamp(includeTimestamp), d_append(append), d_buffered(buffered) - { - if (str.empty()) { - return; - } - - if (!reopenLogFile()) { - throw std::runtime_error("Unable to open file '" + str + "' for logging: " + stringerror()); - } - } - - DNSAction::Action operator()(DNSQuestion* dnsquestion, std::string* ruleresult) const override - { - auto filepointer = std::atomic_load_explicit(&d_fp, std::memory_order_acquire); - if (!filepointer) { - if (!d_verboseOnly || g_verbose) { - if (d_includeTimestamp) { - infolog("[%u.%u] Packet from %s for %s %s with id %d", static_cast(dnsquestion->getQueryRealTime().tv_sec), static_cast(dnsquestion->getQueryRealTime().tv_nsec), dnsquestion->ids.origRemote.toStringWithPort(), dnsquestion->ids.qname.toString(), QType(dnsquestion->ids.qtype).toString(), dnsquestion->getHeader()->id); - } - else { - infolog("Packet from %s for %s %s with id %d", dnsquestion->ids.origRemote.toStringWithPort(), dnsquestion->ids.qname.toString(), QType(dnsquestion->ids.qtype).toString(), dnsquestion->getHeader()->id); - } - } - } - else { - if (d_binary) { - const auto& out = dnsquestion->ids.qname.getStorage(); - if (d_includeTimestamp) { - auto tv_sec = static_cast(dnsquestion->getQueryRealTime().tv_sec); - auto tv_nsec = static_cast(dnsquestion->getQueryRealTime().tv_nsec); - fwrite(&tv_sec, sizeof(tv_sec), 1, filepointer.get()); - fwrite(&tv_nsec, sizeof(tv_nsec), 1, filepointer.get()); - } - uint16_t queryId = dnsquestion->getHeader()->id; - fwrite(&queryId, sizeof(queryId), 1, filepointer.get()); - fwrite(out.c_str(), 1, out.size(), filepointer.get()); - fwrite(&dnsquestion->ids.qtype, sizeof(dnsquestion->ids.qtype), 1, filepointer.get()); - fwrite(&dnsquestion->ids.origRemote.sin4.sin_family, sizeof(dnsquestion->ids.origRemote.sin4.sin_family), 1, filepointer.get()); - if (dnsquestion->ids.origRemote.sin4.sin_family == AF_INET) { - fwrite(&dnsquestion->ids.origRemote.sin4.sin_addr.s_addr, sizeof(dnsquestion->ids.origRemote.sin4.sin_addr.s_addr), 1, filepointer.get()); - } - else if (dnsquestion->ids.origRemote.sin4.sin_family == AF_INET6) { - fwrite(&dnsquestion->ids.origRemote.sin6.sin6_addr.s6_addr, sizeof(dnsquestion->ids.origRemote.sin6.sin6_addr.s6_addr), 1, filepointer.get()); - } - fwrite(&dnsquestion->ids.origRemote.sin4.sin_port, sizeof(dnsquestion->ids.origRemote.sin4.sin_port), 1, filepointer.get()); - } - else { - if (d_includeTimestamp) { - fprintf(filepointer.get(), "[%llu.%lu] Packet from %s for %s %s with id %u\n", static_cast(dnsquestion->getQueryRealTime().tv_sec), static_cast(dnsquestion->getQueryRealTime().tv_nsec), dnsquestion->ids.origRemote.toStringWithPort().c_str(), dnsquestion->ids.qname.toString().c_str(), QType(dnsquestion->ids.qtype).toString().c_str(), dnsquestion->getHeader()->id); - } - else { - fprintf(filepointer.get(), "Packet from %s for %s %s with id %u\n", dnsquestion->ids.origRemote.toStringWithPort().c_str(), dnsquestion->ids.qname.toString().c_str(), QType(dnsquestion->ids.qtype).toString().c_str(), dnsquestion->getHeader()->id); - } - } - } - return Action::None; - } - - [[nodiscard]] std::string toString() const override - { - if (!d_fname.empty()) { - return "log to " + d_fname; - } - return "log"; - } - - void reload() override - { - if (!reopenLogFile()) { - warnlog("Unable to open file '%s' for logging: %s", d_fname, stringerror()); - } - } - -private: - bool reopenLogFile() - { - // we are using a naked pointer here because we don't want fclose to be called - // with a nullptr, which would happen if we constructor a shared_ptr with fclose - // as a custom deleter and nullptr as a FILE* - // NOLINTNEXTLINE(cppcoreguidelines-owning-memory) - auto* nfp = fopen(d_fname.c_str(), d_append ? "a+" : "w"); - if (nfp == nullptr) { - /* don't fall on our sword when reopening */ - return false; - } - - auto filepointer = std::shared_ptr(nfp, fclose); - nfp = nullptr; - - if (!d_buffered) { - setbuf(filepointer.get(), nullptr); - } - - std::atomic_store_explicit(&d_fp, std::move(filepointer), std::memory_order_release); - return true; - } - - std::string d_fname; - std::shared_ptr d_fp{nullptr}; - bool d_binary{true}; - bool d_verboseOnly{true}; - bool d_includeTimestamp{false}; - bool d_append{false}; - bool d_buffered{true}; -}; - -class LogResponseAction : public DNSResponseAction, public boost::noncopyable -{ -public: - LogResponseAction() = default; - - LogResponseAction(const std::string& str, bool append = false, bool buffered = true, bool verboseOnly = true, bool includeTimestamp = false) : - d_fname(str), d_verboseOnly(verboseOnly), d_includeTimestamp(includeTimestamp), d_append(append), d_buffered(buffered) - { - if (str.empty()) { - return; - } - - if (!reopenLogFile()) { - throw std::runtime_error("Unable to open file '" + str + "' for logging: " + stringerror()); - } - } - - DNSResponseAction::Action operator()(DNSResponse* response, std::string* ruleresult) const override - { - auto filepointer = std::atomic_load_explicit(&d_fp, std::memory_order_acquire); - if (!filepointer) { - if (!d_verboseOnly || g_verbose) { - if (d_includeTimestamp) { - infolog("[%u.%u] Answer to %s for %s %s (%s) with id %u", static_cast(response->getQueryRealTime().tv_sec), static_cast(response->getQueryRealTime().tv_nsec), response->ids.origRemote.toStringWithPort(), response->ids.qname.toString(), QType(response->ids.qtype).toString(), RCode::to_s(response->getHeader()->rcode), response->getHeader()->id); - } - else { - infolog("Answer to %s for %s %s (%s) with id %u", response->ids.origRemote.toStringWithPort(), response->ids.qname.toString(), QType(response->ids.qtype).toString(), RCode::to_s(response->getHeader()->rcode), response->getHeader()->id); - } - } - } - else { - if (d_includeTimestamp) { - fprintf(filepointer.get(), "[%llu.%lu] Answer to %s for %s %s (%s) with id %u\n", static_cast(response->getQueryRealTime().tv_sec), static_cast(response->getQueryRealTime().tv_nsec), response->ids.origRemote.toStringWithPort().c_str(), response->ids.qname.toString().c_str(), QType(response->ids.qtype).toString().c_str(), RCode::to_s(response->getHeader()->rcode).c_str(), response->getHeader()->id); - } - else { - fprintf(filepointer.get(), "Answer to %s for %s %s (%s) with id %u\n", response->ids.origRemote.toStringWithPort().c_str(), response->ids.qname.toString().c_str(), QType(response->ids.qtype).toString().c_str(), RCode::to_s(response->getHeader()->rcode).c_str(), response->getHeader()->id); - } - } - return Action::None; - } - - [[nodiscard]] std::string toString() const override - { - if (!d_fname.empty()) { - return "log to " + d_fname; - } - return "log"; - } - - void reload() override - { - if (!reopenLogFile()) { - warnlog("Unable to open file '%s' for logging: %s", d_fname, stringerror()); - } - } - -private: - bool reopenLogFile() - { - // we are using a naked pointer here because we don't want fclose to be called - // with a nullptr, which would happen if we constructor a shared_ptr with fclose - // as a custom deleter and nullptr as a FILE* - // NOLINTNEXTLINE(cppcoreguidelines-owning-memory) - auto* nfp = fopen(d_fname.c_str(), d_append ? "a+" : "w"); - if (nfp == nullptr) { - /* don't fall on our sword when reopening */ - return false; - } - - auto filepointer = std::shared_ptr(nfp, fclose); - nfp = nullptr; - - if (!d_buffered) { - setbuf(filepointer.get(), nullptr); - } - - std::atomic_store_explicit(&d_fp, std::move(filepointer), std::memory_order_release); - return true; - } - - std::string d_fname; - std::shared_ptr d_fp{nullptr}; - bool d_verboseOnly{true}; - bool d_includeTimestamp{false}; - bool d_append{false}; - bool d_buffered{true}; -}; - -class SetDisableValidationAction : public DNSAction -{ -public: - // this action does not stop the processing - DNSAction::Action operator()(DNSQuestion* dnsquestion, std::string* ruleresult) const override - { - dnsdist::PacketMangling::editDNSHeaderFromPacket(dnsquestion->getMutableData(), [](dnsheader& header) { - header.cd = true; - return true; - }); - return Action::None; - } - [[nodiscard]] std::string toString() const override - { - return "set cd=1"; - } -}; - -class SetSkipCacheAction : public DNSAction -{ -public: - // this action does not stop the processing - DNSAction::Action operator()(DNSQuestion* dnsquestion, std::string* ruleresult) const override - { - dnsquestion->ids.skipCache = true; - return Action::None; - } - [[nodiscard]] std::string toString() const override - { - return "skip cache"; - } -}; - -class SetSkipCacheResponseAction : public DNSResponseAction -{ -public: - DNSResponseAction::Action operator()(DNSResponse* response, std::string* ruleresult) const override - { - response->ids.skipCache = true; - return Action::None; - } - [[nodiscard]] std::string toString() const override - { - return "skip cache"; - } -}; - -class SetTempFailureCacheTTLAction : public DNSAction -{ -public: - // this action does not stop the processing - SetTempFailureCacheTTLAction(uint32_t ttl) : - d_ttl(ttl) - { - } - DNSAction::Action operator()(DNSQuestion* dnsquestion, std::string* ruleresult) const override - { - dnsquestion->ids.tempFailureTTL = d_ttl; - return Action::None; - } - [[nodiscard]] std::string toString() const override - { - return "set tempfailure cache ttl to " + std::to_string(d_ttl); - } - -private: - uint32_t d_ttl; -}; - -class SetECSPrefixLengthAction : public DNSAction -{ -public: - // this action does not stop the processing - SetECSPrefixLengthAction(uint16_t v4Length, uint16_t v6Length) : - d_v4PrefixLength(v4Length), d_v6PrefixLength(v6Length) - { - } - DNSAction::Action operator()(DNSQuestion* dnsquestion, std::string* ruleresult) const override - { - dnsquestion->ecsPrefixLength = dnsquestion->ids.origRemote.sin4.sin_family == AF_INET ? d_v4PrefixLength : d_v6PrefixLength; - return Action::None; - } - [[nodiscard]] std::string toString() const override - { - return "set ECS prefix length to " + std::to_string(d_v4PrefixLength) + "/" + std::to_string(d_v6PrefixLength); - } - -private: - uint16_t d_v4PrefixLength; - uint16_t d_v6PrefixLength; -}; - -class SetECSOverrideAction : public DNSAction -{ -public: - // this action does not stop the processing - SetECSOverrideAction(bool ecsOverride) : - d_ecsOverride(ecsOverride) - { - } - DNSAction::Action operator()(DNSQuestion* dnsquestion, std::string* ruleresult) const override - { - dnsquestion->ecsOverride = d_ecsOverride; - return Action::None; - } - [[nodiscard]] std::string toString() const override - { - return "set ECS override to " + std::to_string(static_cast(d_ecsOverride)); - } - -private: - bool d_ecsOverride; -}; - -class SetDisableECSAction : public DNSAction -{ -public: - // this action does not stop the processing - DNSAction::Action operator()(DNSQuestion* dnsquestion, std::string* ruleresult) const override - { - dnsquestion->useECS = false; - return Action::None; - } - [[nodiscard]] std::string toString() const override - { - return "disable ECS"; - } -}; - -class SetECSAction : public DNSAction -{ -public: - // this action does not stop the processing - SetECSAction(const Netmask& v4Netmask) : - d_v4(v4Netmask), d_hasV6(false) - { - } - - SetECSAction(const Netmask& v4Netmask, const Netmask& v6Netmask) : - d_v4(v4Netmask), d_v6(v6Netmask), d_hasV6(true) - { - } - - DNSAction::Action operator()(DNSQuestion* dnsquestion, std::string* ruleresult) const override - { - if (d_hasV6) { - dnsquestion->ecs = std::make_unique(dnsquestion->ids.origRemote.isIPv4() ? d_v4 : d_v6); - } - else { - dnsquestion->ecs = std::make_unique(d_v4); - } - - return Action::None; - } - - [[nodiscard]] std::string toString() const override - { - std::string result = "set ECS to " + d_v4.toString(); - if (d_hasV6) { - result += " / " + d_v6.toString(); - } - return result; - } - -private: - Netmask d_v4; - Netmask d_v6; - bool d_hasV6; -}; - -#ifndef DISABLE_PROTOBUF -static DnstapMessage::ProtocolType ProtocolToDNSTap(dnsdist::Protocol protocol) -{ - if (protocol == dnsdist::Protocol::DoUDP) { - return DnstapMessage::ProtocolType::DoUDP; - } - if (protocol == dnsdist::Protocol::DoTCP) { - return DnstapMessage::ProtocolType::DoTCP; - } - if (protocol == dnsdist::Protocol::DoT) { - return DnstapMessage::ProtocolType::DoT; - } - if (protocol == dnsdist::Protocol::DoH || protocol == dnsdist::Protocol::DoH3) { - return DnstapMessage::ProtocolType::DoH; - } - if (protocol == dnsdist::Protocol::DNSCryptUDP) { - return DnstapMessage::ProtocolType::DNSCryptUDP; - } - if (protocol == dnsdist::Protocol::DNSCryptTCP) { - return DnstapMessage::ProtocolType::DNSCryptTCP; - } - if (protocol == dnsdist::Protocol::DoQ) { - return DnstapMessage::ProtocolType::DoQ; - } - throw std::runtime_error("Unhandled protocol for dnstap: " + protocol.toPrettyString()); -} - -static void remoteLoggerQueueData(RemoteLoggerInterface& remoteLogger, const std::string& data) -{ - auto ret = remoteLogger.queueData(data); - - switch (ret) { - case RemoteLoggerInterface::Result::Queued: - break; - case RemoteLoggerInterface::Result::PipeFull: { - vinfolog("%s: %s", remoteLogger.name(), RemoteLoggerInterface::toErrorString(ret)); - break; - } - case RemoteLoggerInterface::Result::TooLarge: { - warnlog("%s: %s", remoteLogger.name(), RemoteLoggerInterface::toErrorString(ret)); - break; - } - case RemoteLoggerInterface::Result::OtherError: - warnlog("%s: %s", remoteLogger.name(), RemoteLoggerInterface::toErrorString(ret)); - } -} - -class DnstapLogAction : public DNSAction, public boost::noncopyable -{ -public: - // this action does not stop the processing - DnstapLogAction(std::string identity, std::shared_ptr& logger, boost::optional> alterFunc) : - d_identity(std::move(identity)), d_logger(logger), d_alterFunc(std::move(alterFunc)) - { - } - DNSAction::Action operator()(DNSQuestion* dnsquestion, std::string* ruleresult) const override - { - static thread_local std::string data; - data.clear(); - - DnstapMessage::ProtocolType protocol = ProtocolToDNSTap(dnsquestion->getProtocol()); - // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) - DnstapMessage message(std::move(data), !dnsquestion->getHeader()->qr ? DnstapMessage::MessageType::client_query : DnstapMessage::MessageType::client_response, d_identity, &dnsquestion->ids.origRemote, &dnsquestion->ids.origDest, protocol, reinterpret_cast(dnsquestion->getData().data()), dnsquestion->getData().size(), &dnsquestion->getQueryRealTime(), nullptr); - { - if (d_alterFunc) { - auto lock = g_lua.lock(); - (*d_alterFunc)(dnsquestion, &message); - } - } - - data = message.getBuffer(); - remoteLoggerQueueData(*d_logger, data); - - return Action::None; - } - [[nodiscard]] std::string toString() const override - { - return "remote log as dnstap to " + (d_logger ? d_logger->toString() : ""); - } - -private: - std::string d_identity; - std::shared_ptr d_logger; - boost::optional> d_alterFunc; -}; - -namespace -{ -void addMetaDataToProtobuf(DNSDistProtoBufMessage& message, const DNSQuestion& dnsquestion, const std::vector>& metas) -{ - for (const auto& [name, meta] : metas) { - message.addMeta(name, meta.getValues(dnsquestion), {}); - } -} - -void addTagsToProtobuf(DNSDistProtoBufMessage& message, const DNSQuestion& dnsquestion, const std::unordered_set& allowed) -{ - if (!dnsquestion.ids.qTag) { - return; - } - - for (const auto& [key, value] : *dnsquestion.ids.qTag) { - if (!allowed.empty() && allowed.count(key) == 0) { - continue; - } - - if (value.empty()) { - message.addTag(key); - } - else { - auto tag = key; - tag.append(":"); - tag.append(value); - message.addTag(tag); - } - } -} - -void addExtendedDNSErrorToProtobuf(DNSDistProtoBufMessage& message, const DNSResponse& response, const std::string& metaKey) -{ - auto [infoCode, extraText] = dnsdist::edns::getExtendedDNSError(response.getData()); - if (!infoCode) { - return; - } - - if (extraText) { - message.addMeta(metaKey, {*extraText}, {*infoCode}); - } - else { - message.addMeta(metaKey, {}, {*infoCode}); - } -} -} - -struct RemoteLogActionConfiguration -{ - std::vector> metas; - std::optional> tagsToExport{std::nullopt}; - boost::optional> alterQueryFunc{boost::none}; - boost::optional> alterResponseFunc{boost::none}; - std::shared_ptr logger; - std::string serverID; - std::string ipEncryptKey; - std::optional exportExtendedErrorsToMeta{std::nullopt}; - bool includeCNAME{false}; -}; - -class RemoteLogAction : public DNSAction, public boost::noncopyable -{ -public: - // this action does not stop the processing - RemoteLogAction(RemoteLogActionConfiguration& config) : - d_tagsToExport(std::move(config.tagsToExport)), d_metas(std::move(config.metas)), d_logger(config.logger), d_alterFunc(std::move(config.alterQueryFunc)), d_serverID(config.serverID), d_ipEncryptKey(config.ipEncryptKey) - { - } - - DNSAction::Action operator()(DNSQuestion* dnsquestion, std::string* ruleresult) const override - { - if (!dnsquestion->ids.d_protoBufData) { - dnsquestion->ids.d_protoBufData = std::make_unique(); - } - if (!dnsquestion->ids.d_protoBufData->uniqueId) { - dnsquestion->ids.d_protoBufData->uniqueId = getUniqueID(); - } - - DNSDistProtoBufMessage message(*dnsquestion); - if (!d_serverID.empty()) { - message.setServerIdentity(d_serverID); - } - -#if HAVE_IPCIPHER - if (!d_ipEncryptKey.empty()) { - message.setRequestor(encryptCA(dnsquestion->ids.origRemote, d_ipEncryptKey)); - } -#endif /* HAVE_IPCIPHER */ - - if (d_tagsToExport) { - addTagsToProtobuf(message, *dnsquestion, *d_tagsToExport); - } - - addMetaDataToProtobuf(message, *dnsquestion, d_metas); - - if (d_alterFunc) { - auto lock = g_lua.lock(); - (*d_alterFunc)(dnsquestion, &message); - } - - static thread_local std::string data; - data.clear(); - message.serialize(data); - remoteLoggerQueueData(*d_logger, data); - - return Action::None; - } - [[nodiscard]] std::string toString() const override - { - return "remote log to " + (d_logger ? d_logger->toString() : ""); - } - -private: - std::optional> d_tagsToExport; - std::vector> d_metas; - std::shared_ptr d_logger; - boost::optional> d_alterFunc; - std::string d_serverID; - std::string d_ipEncryptKey; -}; - -#endif /* DISABLE_PROTOBUF */ - -class SNMPTrapAction : public DNSAction -{ -public: - // this action does not stop the processing - SNMPTrapAction(std::string reason) : - d_reason(std::move(reason)) - { - } - DNSAction::Action operator()(DNSQuestion* dnsquestion, std::string* ruleresult) const override - { - if (g_snmpAgent != nullptr && g_snmpTrapsEnabled) { - g_snmpAgent->sendDNSTrap(*dnsquestion, d_reason); - } - - return Action::None; - } - [[nodiscard]] std::string toString() const override - { - return "send SNMP trap"; - } - -private: - std::string d_reason; -}; - -class SetTagAction : public DNSAction -{ -public: - // this action does not stop the processing - SetTagAction(std::string tag, std::string value) : - d_tag(std::move(tag)), d_value(std::move(value)) - { - } - DNSAction::Action operator()(DNSQuestion* dnsquestion, std::string* ruleresult) const override - { - dnsquestion->setTag(d_tag, d_value); - - return Action::None; - } - [[nodiscard]] std::string toString() const override - { - return "set tag '" + d_tag + "' to value '" + d_value + "'"; - } - -private: - std::string d_tag; - std::string d_value; -}; - -#ifndef DISABLE_PROTOBUF -class DnstapLogResponseAction : public DNSResponseAction, public boost::noncopyable -{ -public: - // this action does not stop the processing - DnstapLogResponseAction(std::string identity, std::shared_ptr& logger, boost::optional> alterFunc) : - d_identity(std::move(identity)), d_logger(logger), d_alterFunc(std::move(alterFunc)) - { - } - DNSResponseAction::Action operator()(DNSResponse* response, std::string* ruleresult) const override - { - static thread_local std::string data; - struct timespec now = {}; - gettime(&now, true); - data.clear(); - - DnstapMessage::ProtocolType protocol = ProtocolToDNSTap(response->getProtocol()); - // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) - DnstapMessage message(std::move(data), DnstapMessage::MessageType::client_response, d_identity, &response->ids.origRemote, &response->ids.origDest, protocol, reinterpret_cast(response->getData().data()), response->getData().size(), &response->getQueryRealTime(), &now); - { - if (d_alterFunc) { - auto lock = g_lua.lock(); - (*d_alterFunc)(response, &message); - } - } - - data = message.getBuffer(); - remoteLoggerQueueData(*d_logger, data); - - return Action::None; - } - [[nodiscard]] std::string toString() const override - { - return "log response as dnstap to " + (d_logger ? d_logger->toString() : ""); - } - -private: - std::string d_identity; - std::shared_ptr d_logger; - boost::optional> d_alterFunc; -}; - -class RemoteLogResponseAction : public DNSResponseAction, public boost::noncopyable -{ -public: - // this action does not stop the processing - RemoteLogResponseAction(RemoteLogActionConfiguration& config) : - d_tagsToExport(std::move(config.tagsToExport)), d_metas(std::move(config.metas)), d_logger(config.logger), d_alterFunc(std::move(config.alterResponseFunc)), d_serverID(config.serverID), d_ipEncryptKey(config.ipEncryptKey), d_exportExtendedErrorsToMeta(std::move(config.exportExtendedErrorsToMeta)), d_includeCNAME(config.includeCNAME) - { - } - DNSResponseAction::Action operator()(DNSResponse* response, std::string* ruleresult) const override - { - if (!response->ids.d_protoBufData) { - response->ids.d_protoBufData = std::make_unique(); - } - if (!response->ids.d_protoBufData->uniqueId) { - response->ids.d_protoBufData->uniqueId = getUniqueID(); - } - - DNSDistProtoBufMessage message(*response, d_includeCNAME); - if (!d_serverID.empty()) { - message.setServerIdentity(d_serverID); - } - -#if HAVE_IPCIPHER - if (!d_ipEncryptKey.empty()) { - message.setRequestor(encryptCA(response->ids.origRemote, d_ipEncryptKey)); - } -#endif /* HAVE_IPCIPHER */ - - if (d_tagsToExport) { - addTagsToProtobuf(message, *response, *d_tagsToExport); - } - - addMetaDataToProtobuf(message, *response, d_metas); - - if (d_exportExtendedErrorsToMeta) { - addExtendedDNSErrorToProtobuf(message, *response, *d_exportExtendedErrorsToMeta); - } - - if (d_alterFunc) { - auto lock = g_lua.lock(); - (*d_alterFunc)(response, &message); - } - - static thread_local std::string data; - data.clear(); - message.serialize(data); - d_logger->queueData(data); - - return Action::None; - } - [[nodiscard]] std::string toString() const override - { - return "remote log response to " + (d_logger ? d_logger->toString() : ""); - } - -private: - std::optional> d_tagsToExport; - std::vector> d_metas; - std::shared_ptr d_logger; - boost::optional> d_alterFunc; - std::string d_serverID; - std::string d_ipEncryptKey; - std::optional d_exportExtendedErrorsToMeta{std::nullopt}; - bool d_includeCNAME; -}; - -#endif /* DISABLE_PROTOBUF */ - -class DropResponseAction : public DNSResponseAction -{ -public: - DNSResponseAction::Action operator()(DNSResponse* response, std::string* ruleresult) const override - { - return Action::Drop; - } - [[nodiscard]] std::string toString() const override - { - return "drop"; - } -}; - -class AllowResponseAction : public DNSResponseAction -{ -public: - DNSResponseAction::Action operator()(DNSResponse* response, std::string* ruleresult) const override - { - return Action::Allow; - } - [[nodiscard]] std::string toString() const override - { - return "allow"; - } -}; - -class DelayResponseAction : public DNSResponseAction -{ -public: - DelayResponseAction(int msec) : - d_msec(msec) - { - } - DNSResponseAction::Action operator()(DNSResponse* response, std::string* ruleresult) const override - { - *ruleresult = std::to_string(d_msec); - return Action::Delay; - } - [[nodiscard]] std::string toString() const override - { - return "delay by " + std::to_string(d_msec) + " ms"; - } - -private: - int d_msec; -}; - -#ifdef HAVE_NET_SNMP -class SNMPTrapResponseAction : public DNSResponseAction -{ -public: - // this action does not stop the processing - SNMPTrapResponseAction(std::string reason) : - d_reason(std::move(reason)) - { - } - DNSResponseAction::Action operator()(DNSResponse* response, std::string* ruleresult) const override - { - if (g_snmpAgent && g_snmpTrapsEnabled) { - g_snmpAgent->sendDNSTrap(*response, d_reason); - } - - return Action::None; - } - [[nodiscard]] std::string toString() const override - { - return "send SNMP trap"; - } - -private: - std::string d_reason; -}; -#endif /* HAVE_NET_SNMP */ - -class SetTagResponseAction : public DNSResponseAction -{ -public: - // this action does not stop the processing - SetTagResponseAction(std::string tag, std::string value) : - d_tag(std::move(tag)), d_value(std::move(value)) - { - } - DNSResponseAction::Action operator()(DNSResponse* response, std::string* ruleresult) const override - { - response->setTag(d_tag, d_value); - - return Action::None; - } - [[nodiscard]] std::string toString() const override - { - return "set tag '" + d_tag + "' to value '" + d_value + "'"; - } - -private: - std::string d_tag; - std::string d_value; -}; - -class ClearRecordTypesResponseAction : public DNSResponseAction, public boost::noncopyable -{ -public: - ClearRecordTypesResponseAction(std::unordered_set qtypes) : - d_qtypes(std::move(qtypes)) - { - } - - DNSResponseAction::Action operator()(DNSResponse* response, std::string* ruleresult) const override - { - if (!d_qtypes.empty()) { - clearDNSPacketRecordTypes(response->getMutableData(), d_qtypes); - } - return DNSResponseAction::Action::None; - } - - [[nodiscard]] std::string toString() const override - { - return "clear record types"; - } - -private: - std::unordered_set d_qtypes{}; -}; - -class ContinueAction : public DNSAction -{ -public: - // this action does not stop the processing - ContinueAction(std::shared_ptr& action) : - d_action(action) - { - } - - DNSAction::Action operator()(DNSQuestion* dnsquestion, std::string* ruleresult) const override - { - if (d_action) { - /* call the action */ - auto action = (*d_action)(dnsquestion, ruleresult); - bool drop = false; - /* apply the changes if needed (pool selection, flags, etc */ - processRulesResult(action, *dnsquestion, *ruleresult, drop); - } - - /* but ignore the resulting action no matter what */ - return Action::None; - } - - [[nodiscard]] std::string toString() const override - { - if (d_action) { - return "continue after: " + (d_action ? d_action->toString() : ""); - } - return "no op"; - } - -private: - std::shared_ptr d_action; -}; - -#ifdef HAVE_DNS_OVER_HTTPS -class HTTPStatusAction : public DNSAction -{ -public: - HTTPStatusAction(int code, PacketBuffer body, std::string contentType) : - d_body(std::move(body)), d_contentType(std::move(contentType)), d_code(code) - { - } - - DNSAction::Action operator()(DNSQuestion* dnsquestion, std::string* ruleresult) const override - { - if (!dnsquestion->ids.du) { - return Action::None; - } - - dnsquestion->ids.du->setHTTPResponse(d_code, PacketBuffer(d_body), d_contentType); - dnsdist::PacketMangling::editDNSHeaderFromPacket(dnsquestion->getMutableData(), [this](dnsheader& header) { - header.qr = true; // for good measure - setResponseHeadersFromConfig(header, d_responseConfig); - return true; - }); - return Action::HeaderModify; - } - - [[nodiscard]] std::string toString() const override - { - return "return an HTTP status of " + std::to_string(d_code); - } - - [[nodiscard]] ResponseConfig& getResponseConfig() - { - return d_responseConfig; - } - -private: - ResponseConfig d_responseConfig; - PacketBuffer d_body; - std::string d_contentType; - int d_code; -}; -#endif /* HAVE_DNS_OVER_HTTPS */ - -#if defined(HAVE_LMDB) || defined(HAVE_CDB) -class KeyValueStoreLookupAction : public DNSAction -{ -public: - // this action does not stop the processing - KeyValueStoreLookupAction(std::shared_ptr& kvs, std::shared_ptr& lookupKey, std::string destinationTag) : - d_kvs(kvs), d_key(lookupKey), d_tag(std::move(destinationTag)) - { - } - - DNSAction::Action operator()(DNSQuestion* dnsquestion, std::string* ruleresult) const override - { - std::vector keys = d_key->getKeys(*dnsquestion); - std::string result; - for (const auto& key : keys) { - if (d_kvs->getValue(key, result)) { - break; - } - } - - dnsquestion->setTag(d_tag, std::move(result)); - - return Action::None; - } - - [[nodiscard]] std::string toString() const override - { - return "lookup key-value store based on '" + d_key->toString() + "' and set the result in tag '" + d_tag + "'"; - } - -private: - std::shared_ptr d_kvs; - std::shared_ptr d_key; - std::string d_tag; -}; - -class KeyValueStoreRangeLookupAction : public DNSAction -{ -public: - // this action does not stop the processing - KeyValueStoreRangeLookupAction(std::shared_ptr& kvs, std::shared_ptr& lookupKey, std::string destinationTag) : - d_kvs(kvs), d_key(lookupKey), d_tag(std::move(destinationTag)) - { - } - - DNSAction::Action operator()(DNSQuestion* dnsquestion, std::string* ruleresult) const override - { - std::vector keys = d_key->getKeys(*dnsquestion); - std::string result; - for (const auto& key : keys) { - if (d_kvs->getRangeValue(key, result)) { - break; - } - } - - dnsquestion->setTag(d_tag, std::move(result)); - - return Action::None; - } - - [[nodiscard]] std::string toString() const override - { - return "do a range-based lookup in key-value store based on '" + d_key->toString() + "' and set the result in tag '" + d_tag + "'"; - } - -private: - std::shared_ptr d_kvs; - std::shared_ptr d_key; - std::string d_tag; -}; -#endif /* defined(HAVE_LMDB) || defined(HAVE_CDB) */ - -class MaxReturnedTTLAction : public DNSAction -{ -public: - MaxReturnedTTLAction(uint32_t cap) : - d_cap(cap) - { - } - - DNSAction::Action operator()(DNSQuestion* dnsquestion, std::string* ruleresult) const override - { - dnsquestion->ids.ttlCap = d_cap; - return DNSAction::Action::None; - } - - [[nodiscard]] std::string toString() const override - { - return "cap the TTL of the returned response to " + std::to_string(d_cap); - } - -private: - uint32_t d_cap; -}; - -class MaxReturnedTTLResponseAction : public DNSResponseAction -{ -public: - MaxReturnedTTLResponseAction(uint32_t cap) : - d_cap(cap) - { - } - - DNSResponseAction::Action operator()(DNSResponse* response, std::string* ruleresult) const override - { - response->ids.ttlCap = d_cap; - return DNSResponseAction::Action::None; - } - - [[nodiscard]] std::string toString() const override - { - return "cap the TTL of the returned response to " + std::to_string(d_cap); - } - -private: - uint32_t d_cap; -}; - -class NegativeAndSOAAction : public DNSAction -{ -public: - struct SOAParams - { - uint32_t serial; - uint32_t refresh; - uint32_t retry; - uint32_t expire; - uint32_t minimum; - }; - - NegativeAndSOAAction(bool nxd, DNSName zone, uint32_t ttl, DNSName mname, DNSName rname, SOAParams params, bool soaInAuthoritySection) : - d_zone(std::move(zone)), d_mname(std::move(mname)), d_rname(std::move(rname)), d_ttl(ttl), d_params(params), d_nxd(nxd), d_soaInAuthoritySection(soaInAuthoritySection) - { - } - - DNSAction::Action operator()(DNSQuestion* dnsquestion, std::string* ruleresult) const override - { - if (!setNegativeAndAdditionalSOA(*dnsquestion, d_nxd, d_zone, d_ttl, d_mname, d_rname, d_params.serial, d_params.refresh, d_params.retry, d_params.expire, d_params.minimum, d_soaInAuthoritySection)) { - return Action::None; - } - - dnsdist::PacketMangling::editDNSHeaderFromPacket(dnsquestion->getMutableData(), [this](dnsheader& header) { - setResponseHeadersFromConfig(header, d_responseConfig); - return true; - }); - - return Action::Allow; - } - - [[nodiscard]] std::string toString() const override - { - return std::string(d_nxd ? "NXD " : "NODATA") + " with SOA"; - } - [[nodiscard]] ResponseConfig& getResponseConfig() - { - return d_responseConfig; - } - -private: - ResponseConfig d_responseConfig; - - DNSName d_zone; - DNSName d_mname; - DNSName d_rname; - uint32_t d_ttl; - SOAParams d_params; - bool d_nxd; - bool d_soaInAuthoritySection; -}; - -class SetProxyProtocolValuesAction : public DNSAction -{ -public: - // this action does not stop the processing - SetProxyProtocolValuesAction(const std::vector>& values) - { - d_values.reserve(values.size()); - for (const auto& value : values) { - d_values.push_back({value.second, value.first}); - } - } - - DNSAction::Action operator()(DNSQuestion* dnsquestion, std::string* ruleresult) const override - { - if (!dnsquestion->proxyProtocolValues) { - dnsquestion->proxyProtocolValues = make_unique>(); - } - - *(dnsquestion->proxyProtocolValues) = d_values; - - return Action::None; - } - - [[nodiscard]] std::string toString() const override - { - return "set Proxy-Protocol values"; - } - -private: - std::vector d_values; -}; - -class SetAdditionalProxyProtocolValueAction : public DNSAction -{ -public: - // this action does not stop the processing - SetAdditionalProxyProtocolValueAction(uint8_t type, std::string value) : - d_value(std::move(value)), d_type(type) - { - } - - DNSAction::Action operator()(DNSQuestion* dnsquestion, std::string* ruleresult) const override - { - if (!dnsquestion->proxyProtocolValues) { - dnsquestion->proxyProtocolValues = make_unique>(); - } - - dnsquestion->proxyProtocolValues->push_back({d_value, d_type}); - - return Action::None; - } - - [[nodiscard]] std::string toString() const override - { - return "add a Proxy-Protocol value of type " + std::to_string(d_type); - } - -private: - std::string d_value; - uint8_t d_type; -}; - -class SetReducedTTLResponseAction : public DNSResponseAction, public boost::noncopyable -{ -public: - // this action does not stop the processing - SetReducedTTLResponseAction(uint8_t percentage) : - d_ratio(percentage / 100.0) - { - } - - DNSResponseAction::Action operator()(DNSResponse* response, std::string* ruleresult) const override - { - // NOLINTNEXTLINE(bugprone-easily-swappable-parameters) - auto visitor = [&](uint8_t section, uint16_t qclass, uint16_t qtype, uint32_t ttl) { - return ttl * d_ratio; - }; - // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) - editDNSPacketTTL(reinterpret_cast(response->getMutableData().data()), response->getData().size(), visitor); - return DNSResponseAction::Action::None; - } - - [[nodiscard]] std::string toString() const override - { - return "reduce ttl to " + std::to_string(d_ratio * 100) + " percent of its value"; - } - -private: - double d_ratio{1.0}; -}; - -class SetExtendedDNSErrorAction : public DNSAction -{ -public: - // this action does not stop the processing - SetExtendedDNSErrorAction(uint16_t infoCode, const std::string& extraText) - { - d_ede.infoCode = infoCode; - d_ede.extraText = extraText; - } - - DNSAction::Action operator()(DNSQuestion* dnsQuestion, std::string* ruleresult) const override - { - dnsQuestion->ids.d_extendedError = std::make_unique(d_ede); - - return DNSAction::Action::None; - } - - [[nodiscard]] std::string toString() const override - { - return "set EDNS Extended DNS Error to " + std::to_string(d_ede.infoCode) + (d_ede.extraText.empty() ? std::string() : std::string(": \"") + d_ede.extraText + std::string("\"")); - } - -private: - EDNSExtendedError d_ede; -}; - -class SetExtendedDNSErrorResponseAction : public DNSResponseAction -{ -public: - // this action does not stop the processing - SetExtendedDNSErrorResponseAction(uint16_t infoCode, const std::string& extraText) - { - d_ede.infoCode = infoCode; - d_ede.extraText = extraText; - } - - DNSResponseAction::Action operator()(DNSResponse* dnsResponse, std::string* ruleresult) const override - { - dnsResponse->ids.d_extendedError = std::make_unique(d_ede); - - return DNSResponseAction::Action::None; - } - - [[nodiscard]] std::string toString() const override - { - return "set EDNS Extended DNS Error to " + std::to_string(d_ede.infoCode) + (d_ede.extraText.empty() ? std::string() : std::string(": \"") + d_ede.extraText + std::string("\"")); - } - -private: - EDNSExtendedError d_ede; -}; - -template -static void addAction(GlobalStateHolder>* someRuleActions, const luadnsrule_t& var, const std::shared_ptr& action, boost::optional& params) -{ - setLuaSideEffect(); - - std::string name; - boost::uuids::uuid uuid{}; - uint64_t creationOrder = 0; - parseRuleParams(params, uuid, name, creationOrder); - checkAllParametersConsumed("addAction", params); - - auto rule = makeRule(var, "addAction"); - someRuleActions->modify([&rule, &action, &uuid, creationOrder, &name](vector& ruleactions) { - ruleactions.push_back({std::move(rule), std::move(action), std::move(name), uuid, creationOrder}); - }); -} - -using responseParams_t = std::unordered_map>; - -static void parseResponseConfig(boost::optional& vars, ResponseConfig& config) -{ - getOptionalValue(vars, "ttl", config.ttl); - getOptionalValue(vars, "aa", config.setAA); - getOptionalValue(vars, "ad", config.setAD); - getOptionalValue(vars, "ra", config.setRA); -} - -void setResponseHeadersFromConfig(dnsheader& dnsheader, const ResponseConfig& config) -{ - if (config.setAA) { - dnsheader.aa = *config.setAA; - } - if (config.setAD) { - dnsheader.ad = *config.setAD; - } - else { - dnsheader.ad = false; - } - if (config.setRA) { - dnsheader.ra = *config.setRA; - } - else { - dnsheader.ra = dnsheader.rd; // for good measure - } -} - -// NOLINTNEXTLINE(readability-function-cognitive-complexity): this function declares Lua bindings, even with a good refactoring it will likely blow up the threshold -void setupLuaActions(LuaContext& luaCtx) -{ - luaCtx.writeFunction("newRuleAction", [](const luadnsrule_t& dnsrule, std::shared_ptr action, boost::optional params) { - boost::uuids::uuid uuid{}; - uint64_t creationOrder = 0; - std::string name; - parseRuleParams(params, uuid, name, creationOrder); - checkAllParametersConsumed("newRuleAction", params); - - auto rule = makeRule(dnsrule, "newRuleAction"); - DNSDistRuleAction ruleaction({std::move(rule), std::move(action), std::move(name), uuid, creationOrder}); - return std::make_shared(ruleaction); - }); - - luaCtx.writeFunction("addAction", [](const luadnsrule_t& var, boost::variant, std::shared_ptr> era, boost::optional params) { - if (era.type() != typeid(std::shared_ptr)) { - throw std::runtime_error("addAction() can only be called with query-related actions, not response-related ones. Are you looking for addResponseAction()?"); - } - - addAction(&g_ruleactions, var, boost::get>(era), params); - }); - - luaCtx.writeFunction("addResponseAction", [](const luadnsrule_t& var, boost::variant, std::shared_ptr> era, boost::optional params) { - if (era.type() != typeid(std::shared_ptr)) { - throw std::runtime_error("addResponseAction() can only be called with response-related actions, not query-related ones. Are you looking for addAction()?"); - } - - addAction(&g_respruleactions, var, boost::get>(era), params); - }); - - luaCtx.writeFunction("addCacheHitResponseAction", [](const luadnsrule_t& var, boost::variant, std::shared_ptr> era, boost::optional params) { - if (era.type() != typeid(std::shared_ptr)) { - throw std::runtime_error("addCacheHitResponseAction() can only be called with response-related actions, not query-related ones. Are you looking for addAction()?"); - } - - addAction(&g_cachehitrespruleactions, var, boost::get>(era), params); - }); - - luaCtx.writeFunction("addCacheInsertedResponseAction", [](const luadnsrule_t& var, boost::variant, std::shared_ptr> era, boost::optional params) { - if (era.type() != typeid(std::shared_ptr)) { - throw std::runtime_error("addCacheInsertedResponseAction() can only be called with response-related actions, not query-related ones. Are you looking for addAction()?"); - } - - addAction(&g_cacheInsertedRespRuleActions, var, boost::get>(era), params); - }); - - luaCtx.writeFunction("addSelfAnsweredResponseAction", [](const luadnsrule_t& var, boost::variant, std::shared_ptr> era, boost::optional params) { - if (era.type() != typeid(std::shared_ptr)) { - throw std::runtime_error("addSelfAnsweredResponseAction() can only be called with response-related actions, not query-related ones. Are you looking for addAction()?"); - } - - addAction(&g_selfansweredrespruleactions, var, boost::get>(era), params); - }); - - luaCtx.registerFunction("printStats", [](const DNSAction& action) { - setLuaNoSideEffect(); - auto stats = action.getStats(); - for (const auto& stat : stats) { - g_outputBuffer += stat.first + "\t"; - double integral = 0; - if (std::modf(stat.second, &integral) == 0.0 && stat.second < static_cast(std::numeric_limits::max())) { - g_outputBuffer += std::to_string(static_cast(stat.second)) + "\n"; - } - else { - g_outputBuffer += std::to_string(stat.second) + "\n"; - } - } - }); - - luaCtx.writeFunction("getAction", [](unsigned int num) { - setLuaNoSideEffect(); - boost::optional> ret; - auto ruleactions = g_ruleactions.getCopy(); - if (num < ruleactions.size()) { - ret = ruleactions[num].d_action; - } - return ret; - }); - - luaCtx.registerFunction("getStats", &DNSAction::getStats); - luaCtx.registerFunction("reload", &DNSAction::reload); - luaCtx.registerFunction("reload", &DNSResponseAction::reload); - - luaCtx.writeFunction("LuaAction", [](LuaAction::func_t func) { - setLuaSideEffect(); - return std::shared_ptr(new LuaAction(std::move(func))); - }); - - luaCtx.writeFunction("LuaFFIAction", [](LuaFFIAction::func_t func) { - setLuaSideEffect(); - return std::shared_ptr(new LuaFFIAction(std::move(func))); - }); - - luaCtx.writeFunction("LuaFFIPerThreadAction", [](const std::string& code) { - setLuaSideEffect(); - return std::shared_ptr(new LuaFFIPerThreadAction(code)); - }); - - luaCtx.writeFunction("SetNoRecurseAction", []() { - return std::shared_ptr(new SetNoRecurseAction); - }); - - luaCtx.writeFunction("SetMacAddrAction", [](int code) { - return std::shared_ptr(new SetMacAddrAction(code)); - }); - - luaCtx.writeFunction("SetEDNSOptionAction", [](int code, const std::string& data) { - return std::shared_ptr(new SetEDNSOptionAction(code, data)); - }); - - luaCtx.writeFunction("PoolAction", [](const std::string& poolname, boost::optional stopProcessing) { - return std::shared_ptr(new PoolAction(poolname, stopProcessing ? *stopProcessing : true)); - }); - - luaCtx.writeFunction("QPSAction", [](int limit) { - return std::shared_ptr(new QPSAction(limit)); - }); - - luaCtx.writeFunction("QPSPoolAction", [](int limit, const std::string& poolname, boost::optional stopProcessing) { - return std::shared_ptr(new QPSPoolAction(limit, poolname, stopProcessing ? *stopProcessing : true)); - }); - - luaCtx.writeFunction("SpoofAction", [](LuaTypeOrArrayOf inp, boost::optional vars) { - vector addrs; - if (auto* ipaddr = boost::get(&inp)) { - addrs.emplace_back(*ipaddr); - } - else { - const auto& ipsArray = boost::get>(inp); - for (const auto& ipAddr : ipsArray) { - addrs.emplace_back(ipAddr.second); - } - } - - auto ret = std::shared_ptr(new SpoofAction(addrs)); - auto spoofaction = std::dynamic_pointer_cast(ret); - parseResponseConfig(vars, spoofaction->getResponseConfig()); - checkAllParametersConsumed("SpoofAction", vars); - return ret; - }); - - luaCtx.writeFunction("SpoofSVCAction", [](const LuaArray& parameters, boost::optional vars) { - auto ret = std::shared_ptr(new SpoofSVCAction(parameters)); - auto spoofaction = std::dynamic_pointer_cast(ret); - parseResponseConfig(vars, spoofaction->getResponseConfig()); - return ret; - }); - - luaCtx.writeFunction("SpoofCNAMEAction", [](const std::string& cname, boost::optional vars) { - auto ret = std::shared_ptr(new SpoofAction(DNSName(cname))); - auto spoofaction = std::dynamic_pointer_cast(ret); - parseResponseConfig(vars, spoofaction->getResponseConfig()); - checkAllParametersConsumed("SpoofCNAMEAction", vars); - return ret; - }); - - luaCtx.writeFunction("SpoofRawAction", [](LuaTypeOrArrayOf inp, boost::optional vars) { - vector raws; - if (const auto* str = boost::get(&inp)) { - raws.push_back(*str); - } - else { - const auto& vect = boost::get>(inp); - for (const auto& raw : vect) { - raws.push_back(raw.second); - } - } - uint32_t qtypeForAny{0}; - getOptionalValue(vars, "typeForAny", qtypeForAny); - if (qtypeForAny > std::numeric_limits::max()) { - qtypeForAny = 0; - } - std::optional qtypeForAnyParam; - if (qtypeForAny > 0) { - qtypeForAnyParam = static_cast(qtypeForAny); - } - auto ret = std::shared_ptr(new SpoofAction(raws, qtypeForAnyParam)); - auto spoofaction = std::dynamic_pointer_cast(ret); - parseResponseConfig(vars, spoofaction->getResponseConfig()); - checkAllParametersConsumed("SpoofRawAction", vars); - return ret; - }); - - luaCtx.writeFunction("SpoofPacketAction", [](const std::string& response, size_t len) { - if (len < sizeof(dnsheader)) { - throw std::runtime_error(std::string("SpoofPacketAction: given packet len is too small")); - } - auto ret = std::shared_ptr(new SpoofAction(response.c_str(), len)); - return ret; - }); - - luaCtx.writeFunction("DropAction", []() { - return std::shared_ptr(new DropAction); - }); - - luaCtx.writeFunction("AllowAction", []() { - return std::shared_ptr(new AllowAction); - }); - - luaCtx.writeFunction("NoneAction", []() { - return std::shared_ptr(new NoneAction); - }); - - luaCtx.writeFunction("DelayAction", [](int msec) { - return std::shared_ptr(new DelayAction(msec)); - }); - - luaCtx.writeFunction("TCAction", []() { - return std::shared_ptr(new TCAction); - }); - - luaCtx.writeFunction("TCResponseAction", []() { - return std::shared_ptr(new TCResponseAction); - }); - - luaCtx.writeFunction("SetDisableValidationAction", []() { - return std::shared_ptr(new SetDisableValidationAction); - }); - - luaCtx.writeFunction("LogAction", [](boost::optional fname, boost::optional binary, boost::optional append, boost::optional buffered, boost::optional verboseOnly, boost::optional includeTimestamp) { - return std::shared_ptr(new LogAction(fname ? *fname : "", binary ? *binary : true, append ? *append : false, buffered ? *buffered : false, verboseOnly ? *verboseOnly : true, includeTimestamp ? *includeTimestamp : false)); - }); - - luaCtx.writeFunction("LogResponseAction", [](boost::optional fname, boost::optional append, boost::optional buffered, boost::optional verboseOnly, boost::optional includeTimestamp) { - return std::shared_ptr(new LogResponseAction(fname ? *fname : "", append ? *append : false, buffered ? *buffered : false, verboseOnly ? *verboseOnly : true, includeTimestamp ? *includeTimestamp : false)); - }); - - luaCtx.writeFunction("LimitTTLResponseAction", [](uint32_t min, uint32_t max, boost::optional> types) { - std::unordered_set capTypes; - if (types) { - capTypes.reserve(types->size()); - for (const auto& [idx, type] : *types) { - capTypes.insert(QType(type)); - } - } - return std::shared_ptr(new LimitTTLResponseAction(min, max, capTypes)); - }); - - luaCtx.writeFunction("SetMinTTLResponseAction", [](uint32_t min) { - return std::shared_ptr(new LimitTTLResponseAction(min)); - }); - - luaCtx.writeFunction("SetMaxTTLResponseAction", [](uint32_t max) { - return std::shared_ptr(new LimitTTLResponseAction(0, max)); - }); - - luaCtx.writeFunction("SetMaxReturnedTTLAction", [](uint32_t max) { - return std::shared_ptr(new MaxReturnedTTLAction(max)); - }); - - luaCtx.writeFunction("SetMaxReturnedTTLResponseAction", [](uint32_t max) { - return std::shared_ptr(new MaxReturnedTTLResponseAction(max)); - }); - - luaCtx.writeFunction("SetReducedTTLResponseAction", [](uint8_t percentage) { - if (percentage > 100) { - throw std::runtime_error(std::string("SetReducedTTLResponseAction takes a percentage between 0 and 100.")); - } - return std::shared_ptr(new SetReducedTTLResponseAction(percentage)); - }); - - luaCtx.writeFunction("ClearRecordTypesResponseAction", [](LuaTypeOrArrayOf types) { - std::unordered_set qtypes{}; - if (types.type() == typeid(int)) { - qtypes.insert(boost::get(types)); - } - else if (types.type() == typeid(LuaArray)) { - const auto& typesArray = boost::get>(types); - for (const auto& tpair : typesArray) { - qtypes.insert(tpair.second); - } - } - return std::shared_ptr(new ClearRecordTypesResponseAction(std::move(qtypes))); - }); - - luaCtx.writeFunction("RCodeAction", [](uint8_t rcode, boost::optional vars) { - auto ret = std::shared_ptr(new RCodeAction(rcode)); - auto rca = std::dynamic_pointer_cast(ret); - parseResponseConfig(vars, rca->getResponseConfig()); - checkAllParametersConsumed("RCodeAction", vars); - return ret; - }); - - luaCtx.writeFunction("ERCodeAction", [](uint8_t rcode, boost::optional vars) { - auto ret = std::shared_ptr(new ERCodeAction(rcode)); - auto erca = std::dynamic_pointer_cast(ret); - parseResponseConfig(vars, erca->getResponseConfig()); - checkAllParametersConsumed("ERCodeAction", vars); - return ret; - }); - - luaCtx.writeFunction("SetSkipCacheAction", []() { - return std::shared_ptr(new SetSkipCacheAction); - }); - - luaCtx.writeFunction("SetSkipCacheResponseAction", []() { - return std::shared_ptr(new SetSkipCacheResponseAction); - }); - - luaCtx.writeFunction("SetTempFailureCacheTTLAction", [](int maxTTL) { - return std::shared_ptr(new SetTempFailureCacheTTLAction(maxTTL)); - }); - - luaCtx.writeFunction("DropResponseAction", []() { - return std::shared_ptr(new DropResponseAction); - }); - - luaCtx.writeFunction("AllowResponseAction", []() { - return std::shared_ptr(new AllowResponseAction); - }); - - luaCtx.writeFunction("DelayResponseAction", [](int msec) { - return std::shared_ptr(new DelayResponseAction(msec)); - }); - - luaCtx.writeFunction("LuaResponseAction", [](LuaResponseAction::func_t func) { - setLuaSideEffect(); - return std::shared_ptr(new LuaResponseAction(std::move(func))); - }); - - luaCtx.writeFunction("LuaFFIResponseAction", [](LuaFFIResponseAction::func_t func) { - setLuaSideEffect(); - return std::shared_ptr(new LuaFFIResponseAction(std::move(func))); - }); - - luaCtx.writeFunction("LuaFFIPerThreadResponseAction", [](const std::string& code) { - setLuaSideEffect(); - return std::shared_ptr(new LuaFFIPerThreadResponseAction(code)); - }); - -#ifndef DISABLE_PROTOBUF - luaCtx.writeFunction("RemoteLogAction", [](std::shared_ptr logger, boost::optional> alterFunc, boost::optional> vars, boost::optional> metas) { - if (logger) { - // avoids potentially-evaluated-expression warning with clang. - RemoteLoggerInterface& remoteLoggerRef = *logger; - if (typeid(remoteLoggerRef) != typeid(RemoteLogger)) { - // We could let the user do what he wants, but wrapping PowerDNS Protobuf inside a FrameStream tagged as dnstap is logically wrong. - throw std::runtime_error(std::string("RemoteLogAction only takes RemoteLogger. For other types, please look at DnstapLogAction.")); - } - } - - std::string tags; - RemoteLogActionConfiguration config; - config.logger = std::move(logger); - config.alterQueryFunc = std::move(alterFunc); - getOptionalValue(vars, "serverID", config.serverID); - getOptionalValue(vars, "ipEncryptKey", config.ipEncryptKey); - getOptionalValue(vars, "exportTags", tags); - - if (metas) { - for (const auto& [key, value] : *metas) { - config.metas.emplace_back(key, ProtoBufMetaKey(value)); - } - } - - if (!tags.empty()) { - config.tagsToExport = std::unordered_set(); - if (tags != "*") { - std::vector tokens; - stringtok(tokens, tags, ","); - for (auto& token : tokens) { - config.tagsToExport->insert(std::move(token)); - } - } - } - - checkAllParametersConsumed("RemoteLogAction", vars); - - return std::shared_ptr(new RemoteLogAction(config)); - }); - - luaCtx.writeFunction("RemoteLogResponseAction", [](std::shared_ptr logger, boost::optional> alterFunc, boost::optional includeCNAME, boost::optional> vars, boost::optional> metas) { - if (logger) { - // avoids potentially-evaluated-expression warning with clang. - RemoteLoggerInterface& remoteLoggerRef = *logger; - if (typeid(remoteLoggerRef) != typeid(RemoteLogger)) { - // We could let the user do what he wants, but wrapping PowerDNS Protobuf inside a FrameStream tagged as dnstap is logically wrong. - throw std::runtime_error("RemoteLogResponseAction only takes RemoteLogger. For other types, please look at DnstapLogResponseAction."); - } - } - - std::string tags; - RemoteLogActionConfiguration config; - config.logger = std::move(logger); - config.alterResponseFunc = std::move(alterFunc); - config.includeCNAME = includeCNAME ? *includeCNAME : false; - getOptionalValue(vars, "serverID", config.serverID); - getOptionalValue(vars, "ipEncryptKey", config.ipEncryptKey); - getOptionalValue(vars, "exportTags", tags); - getOptionalValue(vars, "exportExtendedErrorsToMeta", config.exportExtendedErrorsToMeta); - - if (metas) { - for (const auto& [key, value] : *metas) { - config.metas.emplace_back(key, ProtoBufMetaKey(value)); - } - } - - if (!tags.empty()) { - config.tagsToExport = std::unordered_set(); - if (tags != "*") { - std::vector tokens; - stringtok(tokens, tags, ","); - for (auto& token : tokens) { - config.tagsToExport->insert(std::move(token)); - } - } - } - - checkAllParametersConsumed("RemoteLogResponseAction", vars); - - return std::shared_ptr(new RemoteLogResponseAction(config)); - }); - - luaCtx.writeFunction("DnstapLogAction", [](const std::string& identity, std::shared_ptr logger, boost::optional> alterFunc) { - return std::shared_ptr(new DnstapLogAction(identity, logger, std::move(alterFunc))); - }); - - luaCtx.writeFunction("DnstapLogResponseAction", [](const std::string& identity, std::shared_ptr logger, boost::optional> alterFunc) { - return std::shared_ptr(new DnstapLogResponseAction(identity, logger, std::move(alterFunc))); - }); -#endif /* DISABLE_PROTOBUF */ - - luaCtx.writeFunction("TeeAction", [](const std::string& remote, boost::optional addECS, boost::optional local, boost::optional addProxyProtocol) { - boost::optional localAddr{boost::none}; - if (local) { - localAddr = ComboAddress(*local, 0); - } - - return std::shared_ptr(new TeeAction(ComboAddress(remote, 53), localAddr, addECS ? *addECS : false, addProxyProtocol ? *addProxyProtocol : false)); - }); - - luaCtx.writeFunction("SetECSPrefixLengthAction", [](uint16_t v4PrefixLength, uint16_t v6PrefixLength) { - return std::shared_ptr(new SetECSPrefixLengthAction(v4PrefixLength, v6PrefixLength)); - }); - - luaCtx.writeFunction("SetECSOverrideAction", [](bool ecsOverride) { - return std::shared_ptr(new SetECSOverrideAction(ecsOverride)); - }); - - luaCtx.writeFunction("SetDisableECSAction", []() { - return std::shared_ptr(new SetDisableECSAction()); - }); - - luaCtx.writeFunction("SetECSAction", [](const std::string& v4Netmask, boost::optional v6Netmask) { - if (v6Netmask) { - return std::shared_ptr(new SetECSAction(Netmask(v4Netmask), Netmask(*v6Netmask))); - } - return std::shared_ptr(new SetECSAction(Netmask(v4Netmask))); - }); - -#ifdef HAVE_NET_SNMP - luaCtx.writeFunction("SNMPTrapAction", [](boost::optional reason) { - return std::shared_ptr(new SNMPTrapAction(reason ? *reason : "")); - }); - - luaCtx.writeFunction("SNMPTrapResponseAction", [](boost::optional reason) { - return std::shared_ptr(new SNMPTrapResponseAction(reason ? *reason : "")); - }); -#endif /* HAVE_NET_SNMP */ - - luaCtx.writeFunction("SetTagAction", [](const std::string& tag, const std::string& value) { - return std::shared_ptr(new SetTagAction(tag, value)); - }); - - luaCtx.writeFunction("SetTagResponseAction", [](const std::string& tag, const std::string& value) { - return std::shared_ptr(new SetTagResponseAction(tag, value)); - }); - - luaCtx.writeFunction("ContinueAction", [](std::shared_ptr action) { - return std::shared_ptr(new ContinueAction(action)); - }); - -#ifdef HAVE_DNS_OVER_HTTPS - luaCtx.writeFunction("HTTPStatusAction", [](uint16_t status, std::string body, boost::optional contentType, boost::optional vars) { - auto ret = std::shared_ptr(new HTTPStatusAction(status, PacketBuffer(body.begin(), body.end()), contentType ? *contentType : "")); - auto hsa = std::dynamic_pointer_cast(ret); - parseResponseConfig(vars, hsa->getResponseConfig()); - checkAllParametersConsumed("HTTPStatusAction", vars); - return ret; - }); -#endif /* HAVE_DNS_OVER_HTTPS */ - -#if defined(HAVE_LMDB) || defined(HAVE_CDB) - luaCtx.writeFunction("KeyValueStoreLookupAction", [](std::shared_ptr& kvs, std::shared_ptr& lookupKey, const std::string& destinationTag) { - return std::shared_ptr(new KeyValueStoreLookupAction(kvs, lookupKey, destinationTag)); - }); - - luaCtx.writeFunction("KeyValueStoreRangeLookupAction", [](std::shared_ptr& kvs, std::shared_ptr& lookupKey, const std::string& destinationTag) { - return std::shared_ptr(new KeyValueStoreRangeLookupAction(kvs, lookupKey, destinationTag)); - }); -#endif /* defined(HAVE_LMDB) || defined(HAVE_CDB) */ - - luaCtx.writeFunction("NegativeAndSOAAction", [](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 vars) { - bool soaInAuthoritySection = false; - getOptionalValue(vars, "soaInAuthoritySection", soaInAuthoritySection); - NegativeAndSOAAction::SOAParams params{ - .serial = serial, - .refresh = refresh, - .retry = retry, - .expire = expire, - .minimum = minimum}; - auto ret = std::shared_ptr(new NegativeAndSOAAction(nxd, DNSName(zone), ttl, DNSName(mname), DNSName(rname), params, soaInAuthoritySection)); - auto action = std::dynamic_pointer_cast(ret); - parseResponseConfig(vars, action->getResponseConfig()); - checkAllParametersConsumed("NegativeAndSOAAction", vars); - return ret; - }); - - luaCtx.writeFunction("SetProxyProtocolValuesAction", [](const std::vector>& values) { - return std::shared_ptr(new SetProxyProtocolValuesAction(values)); - }); - - luaCtx.writeFunction("SetAdditionalProxyProtocolValueAction", [](uint8_t type, const std::string& value) { - return std::shared_ptr(new SetAdditionalProxyProtocolValueAction(type, value)); - }); - - luaCtx.writeFunction("SetExtendedDNSErrorAction", [](uint16_t infoCode, boost::optional extraText) { - return std::shared_ptr(new SetExtendedDNSErrorAction(infoCode, extraText ? *extraText : "")); - }); - - luaCtx.writeFunction("SetExtendedDNSErrorResponseAction", [](uint16_t infoCode, boost::optional extraText) { - return std::shared_ptr(new SetExtendedDNSErrorResponseAction(infoCode, extraText ? *extraText : "")); - }); -} diff --git a/pdns/dnsdist-lua-bindings-dnsquestion.cc b/pdns/dnsdist-lua-bindings-dnsquestion.cc deleted file mode 100644 index 4512fc5ef4f8..000000000000 --- a/pdns/dnsdist-lua-bindings-dnsquestion.cc +++ /dev/null @@ -1,554 +0,0 @@ -/* - * This file is part of PowerDNS or dnsdist. - * Copyright -- PowerDNS.COM B.V. and its contributors - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of version 2 of the GNU General Public License as - * published by the Free Software Foundation. - * - * In addition, for the avoidance of any doubt, permission is granted to - * link this program with OpenSSL and to (re)distribute the binaries - * produced as the result of such linking. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ -#include "dnsdist.hh" -#include "dnsdist-async.hh" -#include "dnsdist-dnsparser.hh" -#include "dnsdist-ecs.hh" -#include "dnsdist-internal-queries.hh" -#include "dnsdist-lua.hh" -#include "dnsparser.hh" - -// NOLINTNEXTLINE(readability-function-cognitive-complexity): this function declares Lua bindings, even with a good refactoring it will likely blow up the threshold -void setupLuaBindingsDNSQuestion(LuaContext& luaCtx) -{ -#ifndef DISABLE_NON_FFI_DQ_BINDINGS - /* DNSQuestion */ - /* PowerDNS DNSQuestion compat */ - luaCtx.registerMember("localaddr", [](const DNSQuestion& dq) -> const ComboAddress { return dq.ids.origDest; }, [](DNSQuestion& dq, const ComboAddress newLocal) { (void) newLocal; }); - luaCtx.registerMember("qname", [](const DNSQuestion& dq) -> const DNSName { return dq.ids.qname; }, [](DNSQuestion& dq, const DNSName& newName) { (void) newName; }); - luaCtx.registerMember("qtype", [](const DNSQuestion& dq) -> uint16_t { return dq.ids.qtype; }, [](DNSQuestion& dq, uint16_t newType) { (void) newType; }); - luaCtx.registerMember("qclass", [](const DNSQuestion& dq) -> uint16_t { return dq.ids.qclass; }, [](DNSQuestion& dq, uint16_t newClass) { (void) newClass; }); - luaCtx.registerMember("rcode", [](const DNSQuestion& dq) -> int { return static_cast(dq.getHeader()->rcode); }, [](DNSQuestion& dq, int newRCode) { - dnsdist::PacketMangling::editDNSHeaderFromPacket(dq.getMutableData(), [newRCode](dnsheader& header) { - header.rcode = static_cast(newRCode); - return true; - }); - }); - luaCtx.registerMember("remoteaddr", [](const DNSQuestion& dq) -> const ComboAddress { return dq.ids.origRemote; }, [](DNSQuestion& dq, const ComboAddress newRemote) { (void) newRemote; }); - /* DNSDist DNSQuestion */ - luaCtx.registerMember("dh", [](const DNSQuestion& dq) -> dnsheader* { return dq.getMutableHeader(); }, [](DNSQuestion& dq, const dnsheader* dh) { - dnsdist::PacketMangling::editDNSHeaderFromPacket(dq.getMutableData(), [&dh](dnsheader& header) { - header = *dh; - return true; - }); - }); - luaCtx.registerMember("len", [](const DNSQuestion& dq) -> uint16_t { return dq.getData().size(); }, [](DNSQuestion& dq, uint16_t newlen) { dq.getMutableData().resize(newlen); }); - luaCtx.registerMember("opcode", [](const DNSQuestion& dq) -> uint8_t { return dq.getHeader()->opcode; }, [](DNSQuestion& dq, uint8_t newOpcode) { (void) newOpcode; }); - luaCtx.registerMember("tcp", [](const DNSQuestion& dq) -> bool { return dq.overTCP(); }, [](DNSQuestion& dq, bool newTcp) { (void) newTcp; }); - luaCtx.registerMember("skipCache", [](const DNSQuestion& dq) -> bool { return dq.ids.skipCache; }, [](DNSQuestion& dq, bool newSkipCache) { dq.ids.skipCache = newSkipCache; }); - luaCtx.registerMember("pool", [](const DNSQuestion& dq) -> std::string { return dq.ids.poolName; }, [](DNSQuestion& dq, const std::string& newPoolName) { dq.ids.poolName = newPoolName; }); - luaCtx.registerMember("useECS", [](const DNSQuestion& dq) -> bool { return dq.useECS; }, [](DNSQuestion& dq, bool useECS) { dq.useECS = useECS; }); - luaCtx.registerMember("ecsOverride", [](const DNSQuestion& dq) -> bool { return dq.ecsOverride; }, [](DNSQuestion& dq, bool ecsOverride) { dq.ecsOverride = ecsOverride; }); - luaCtx.registerMember("ecsPrefixLength", [](const DNSQuestion& dq) -> uint16_t { return dq.ecsPrefixLength; }, [](DNSQuestion& dq, uint16_t newPrefixLength) { dq.ecsPrefixLength = newPrefixLength; }); - luaCtx.registerMember (DNSQuestion::*)>("tempFailureTTL", - [](const DNSQuestion& dq) -> boost::optional { - return dq.ids.tempFailureTTL; - }, - [](DNSQuestion& dq, boost::optional newValue) { - dq.ids.tempFailureTTL = newValue; - } - ); - luaCtx.registerMember("deviceID", [](const DNSQuestion& dq) -> std::string { - if (dq.ids.d_protoBufData) { - return dq.ids.d_protoBufData->d_deviceID; - } - return std::string(); - }, [](DNSQuestion& dq, const std::string& newValue) { - if (!dq.ids.d_protoBufData) { - dq.ids.d_protoBufData = std::make_unique(); - } - dq.ids.d_protoBufData->d_deviceID = newValue; - }); - luaCtx.registerMember("deviceName", [](const DNSQuestion& dq) -> std::string { - if (dq.ids.d_protoBufData) { - return dq.ids.d_protoBufData->d_deviceName; - } - return std::string(); - }, [](DNSQuestion& dq, const std::string& newValue) { - if (!dq.ids.d_protoBufData) { - dq.ids.d_protoBufData = std::make_unique(); - } - dq.ids.d_protoBufData->d_deviceName = newValue; - }); - luaCtx.registerMember("requestorID", [](const DNSQuestion& dq) -> std::string { - if (dq.ids.d_protoBufData) { - return dq.ids.d_protoBufData->d_requestorID; - } - return std::string(); - }, [](DNSQuestion& dq, const std::string& newValue) { - if (!dq.ids.d_protoBufData) { - dq.ids.d_protoBufData = std::make_unique(); - } - dq.ids.d_protoBufData->d_requestorID = newValue; - }); - luaCtx.registerFunction("getDO", [](const DNSQuestion& dq) { - return getEDNSZ(dq) & EDNS_HEADER_FLAG_DO; - }); - luaCtx.registerFunction("getContent", [](const DNSQuestion& dq) { - return std::string(reinterpret_cast(dq.getData().data()), dq.getData().size()); - }); - luaCtx.registerFunction("setContent", [](DNSQuestion& dq, const std::string& raw) { - uint16_t oldID = dq.getHeader()->id; - auto& buffer = dq.getMutableData(); - buffer.clear(); - buffer.insert(buffer.begin(), raw.begin(), raw.end()); - - dnsdist::PacketMangling::editDNSHeaderFromPacket(buffer, [oldID](dnsheader& header) { - header.id = oldID; - return true; - }); - }); - luaCtx.registerFunction(DNSQuestion::*)()const>("getEDNSOptions", [](const DNSQuestion& dq) { - if (dq.ednsOptions == nullptr) { - parseEDNSOptions(dq); - if (dq.ednsOptions == nullptr) { - throw std::runtime_error("parseEDNSOptions should have populated the EDNS options"); - } - } - - return *dq.ednsOptions; - }); - luaCtx.registerFunction("getTrailingData", [](const DNSQuestion& dq) { - return dq.getTrailingData(); - }); - luaCtx.registerFunction("setTrailingData", [](DNSQuestion& dq, const std::string& tail) { - return dq.setTrailingData(tail); - }); - - luaCtx.registerFunction("getServerNameIndication", [](const DNSQuestion& dq) { - return dq.sni; - }); - - luaCtx.registerFunction("getProtocol", [](const DNSQuestion& dq) { - return dq.getProtocol().toPrettyString(); - }); - - luaCtx.registerFunction("getQueryTime", [](const DNSQuestion& dq) { - return dq.ids.queryRealTime.getStartTime(); - }); - - luaCtx.registerFunction("sendTrap", [](const DNSQuestion& dq, boost::optional reason) { -#ifdef HAVE_NET_SNMP - if (g_snmpAgent && g_snmpTrapsEnabled) { - g_snmpAgent->sendDNSTrap(dq, reason ? *reason : ""); - } -#endif /* HAVE_NET_SNMP */ - }); - - luaCtx.registerFunction("setTag", [](DNSQuestion& dq, const std::string& strLabel, const std::string& strValue) { - dq.setTag(strLabel, strValue); - }); - luaCtx.registerFunction)>("setTagArray", [](DNSQuestion& dq, const LuaAssociativeTable&tags) { - for (const auto& tag : tags) { - dq.setTag(tag.first, tag.second); - } - }); - luaCtx.registerFunction("getTag", [](const DNSQuestion& dq, const std::string& strLabel) { - if (!dq.ids.qTag) { - return string(); - } - - std::string strValue; - const auto it = dq.ids.qTag->find(strLabel); - if (it == dq.ids.qTag->cend()) { - return string(); - } - return it->second; - }); - luaCtx.registerFunction("getTagArray", [](const DNSQuestion& dq) { - if (!dq.ids.qTag) { - QTag empty; - return empty; - } - - return *dq.ids.qTag; - }); - - luaCtx.registerFunction)>("setProxyProtocolValues", [](DNSQuestion& dq, const LuaArray& values) { - if (!dq.proxyProtocolValues) { - dq.proxyProtocolValues = make_unique>(); - } - - dq.proxyProtocolValues->clear(); - dq.proxyProtocolValues->reserve(values.size()); - for (const auto& value : values) { - checkParameterBound("setProxyProtocolValues", value.first, std::numeric_limits::max()); - dq.proxyProtocolValues->push_back({value.second, static_cast(value.first)}); - } - }); - - luaCtx.registerFunction("addProxyProtocolValue", [](DNSQuestion& dq, uint64_t type, std::string value) { - checkParameterBound("addProxyProtocolValue", type, std::numeric_limits::max()); - if (!dq.proxyProtocolValues) { - dq.proxyProtocolValues = make_unique>(); - } - - dq.proxyProtocolValues->push_back({std::move(value), static_cast(type)}); - }); - - luaCtx.registerFunction(DNSQuestion::*)()>("getProxyProtocolValues", [](const DNSQuestion& dq) { - LuaArray result; - if (!dq.proxyProtocolValues) { - return result; - } - - result.resize(dq.proxyProtocolValues->size()); - for (const auto& value : *dq.proxyProtocolValues) { - result.push_back({ value.type, value.content }); - } - - return result; - }); - - luaCtx.registerFunction("changeName", [](DNSQuestion& dq, const DNSName& newName) -> bool { - if (!dnsdist::changeNameInDNSPacket(dq.getMutableData(), dq.ids.qname, newName)) { - return false; - } - dq.ids.qname = newName; - return true; - }); - - luaCtx.registerFunction, LuaArray>&, boost::optional)>("spoof", [](DNSQuestion& dnsQuestion, const boost::variant, LuaArray>& response, boost::optional typeForAny) { - if (response.type() == typeid(LuaArray)) { - std::vector data; - auto responses = boost::get>(response); - data.reserve(responses.size()); - for (const auto& resp : responses) { - data.push_back(resp.second); - } - std::string result; - SpoofAction tempSpoofAction(data); - tempSpoofAction(&dnsQuestion, &result); - return; - } - if (response.type() == typeid(LuaArray)) { - std::vector data; - auto responses = boost::get>(response); - data.reserve(responses.size()); - for (const auto& resp : responses) { - data.push_back(resp.second); - } - std::string result; - SpoofAction tempSpoofAction(data, typeForAny ? *typeForAny : std::optional()); - tempSpoofAction(&dnsQuestion, &result); - return; - } - }); - - luaCtx.registerFunction("setEDNSOption", [](DNSQuestion& dq, uint16_t code, const std::string& data) { - setEDNSOption(dq, code, data); - }); - - luaCtx.registerFunction& extraText)>("setExtendedDNSError", [](DNSQuestion& dnsQuestion, uint16_t infoCode, const boost::optional& extraText) { - EDNSExtendedError ede; - ede.infoCode = infoCode; - if (extraText) { - ede.extraText = *extraText; - } - dnsQuestion.ids.d_extendedError = std::make_unique(ede); - }); - - luaCtx.registerFunction("suspend", [](DNSQuestion& dq, uint16_t asyncID, uint16_t queryID, uint32_t timeoutMs) { - dq.asynchronous = true; - return dnsdist::suspendQuery(dq, asyncID, queryID, timeoutMs); - }); - - luaCtx.registerFunction("setRestartable", [](DNSQuestion& dq) { - dq.ids.d_packet = std::make_unique(dq.getData()); - return true; - }); - -class AsynchronousObject -{ -public: - AsynchronousObject(std::unique_ptr&& obj_): object(std::move(obj_)) - { - } - - DNSQuestion getDQ() const - { - return object->getDQ(); - } - - DNSResponse getDR() const - { - return object->getDR(); - } - - bool resume() - { - return dnsdist::queueQueryResumptionEvent(std::move(object)); - } - - bool drop() - { - auto sender = object->getTCPQuerySender(); - if (!sender) { - return false; - } - - struct timeval now; - gettimeofday(&now, nullptr); - sender->notifyIOError(now, TCPResponse(std::move(object->query))); - return true; - } - - bool setRCode(uint8_t rcode, bool clearAnswers) - { - return dnsdist::setInternalQueryRCode(object->query.d_idstate, object->query.d_buffer, rcode, clearAnswers); - } - -private: - std::unique_ptr object; -}; - - luaCtx.registerFunction("getDQ", [](const AsynchronousObject& obj) { - return obj.getDQ(); - }); - - luaCtx.registerFunction("getDR", [](const AsynchronousObject& obj) { - return obj.getDR(); - }); - - luaCtx.registerFunction("resume", [](AsynchronousObject& obj) { - return obj.resume(); - }); - - luaCtx.registerFunction("drop", [](AsynchronousObject& obj) { - return obj.drop(); - }); - - luaCtx.registerFunction("setRCode", [](AsynchronousObject& obj, uint8_t rcode, bool clearAnswers) { - return obj.setRCode(rcode, clearAnswers); - }); - - luaCtx.writeFunction("getAsynchronousObject", [](uint16_t asyncID, uint16_t queryID) -> AsynchronousObject { - if (!dnsdist::g_asyncHolder) { - throw std::runtime_error("Unable to resume, no asynchronous holder"); - } - auto query = dnsdist::g_asyncHolder->get(asyncID, queryID); - if (!query) { - throw std::runtime_error("Unable to find asynchronous object"); - } - return AsynchronousObject(std::move(query)); - }); - - /* LuaWrapper doesn't support inheritance */ - luaCtx.registerMember("localaddr", [](const DNSResponse& dq) -> const ComboAddress { return dq.ids.origDest; }, [](DNSResponse& dq, const ComboAddress newLocal) { (void) newLocal; }); - luaCtx.registerMember("qname", [](const DNSResponse& dq) -> const DNSName { return dq.ids.qname; }, [](DNSResponse& dq, const DNSName& newName) { (void) newName; }); - luaCtx.registerMember("qtype", [](const DNSResponse& dq) -> uint16_t { return dq.ids.qtype; }, [](DNSResponse& dq, uint16_t newType) { (void) newType; }); - luaCtx.registerMember("qclass", [](const DNSResponse& dq) -> uint16_t { return dq.ids.qclass; }, [](DNSResponse& dq, uint16_t newClass) { (void) newClass; }); - luaCtx.registerMember("rcode", [](const DNSResponse& dq) -> int { return static_cast(dq.getHeader()->rcode); }, [](DNSResponse& dq, int newRCode) { - dnsdist::PacketMangling::editDNSHeaderFromPacket(dq.getMutableData(), [newRCode](dnsheader& header) { - header.rcode = static_cast(newRCode); - return true; - }); - }); - luaCtx.registerMember("remoteaddr", [](const DNSResponse& dq) -> const ComboAddress { return dq.ids.origRemote; }, [](DNSResponse& dq, const ComboAddress newRemote) { (void) newRemote; }); - luaCtx.registerMember("dh", [](const DNSResponse& dr) -> dnsheader* { return dr.getMutableHeader(); }, [](DNSResponse& dr, const dnsheader* dh) { - dnsdist::PacketMangling::editDNSHeaderFromPacket(dr.getMutableData(), [&dh](dnsheader& header) { - header = *dh; - return true; - }); - }); - luaCtx.registerMember("len", [](const DNSResponse& dq) -> uint16_t { return dq.getData().size(); }, [](DNSResponse& dq, uint16_t newlen) { dq.getMutableData().resize(newlen); }); - luaCtx.registerMember("opcode", [](const DNSResponse& dq) -> uint8_t { return dq.getHeader()->opcode; }, [](DNSResponse& dq, uint8_t newOpcode) { (void) newOpcode; }); - luaCtx.registerMember("tcp", [](const DNSResponse& dq) -> bool { return dq.overTCP(); }, [](DNSResponse& dq, bool newTcp) { (void) newTcp; }); - luaCtx.registerMember("skipCache", [](const DNSResponse& dq) -> bool { return dq.ids.skipCache; }, [](DNSResponse& dq, bool newSkipCache) { dq.ids.skipCache = newSkipCache; }); - luaCtx.registerMember("pool", [](const DNSResponse& dq) -> std::string { return dq.ids.poolName; }, [](DNSResponse& dq, const std::string& newPoolName) { dq.ids.poolName = newPoolName; }); - luaCtx.registerFunction editFunc)>("editTTLs", [](DNSResponse& dr, std::function editFunc) { - editDNSPacketTTL(reinterpret_cast(dr.getMutableData().data()), dr.getData().size(), editFunc); - }); - luaCtx.registerFunction("getDO", [](const DNSResponse& dq) { - return getEDNSZ(dq) & EDNS_HEADER_FLAG_DO; - }); - luaCtx.registerFunction("getContent", [](const DNSResponse& dq) { - return std::string(reinterpret_cast(dq.getData().data()), dq.getData().size()); - }); - luaCtx.registerFunction("setContent", [](DNSResponse& dr, const std::string& raw) { - uint16_t oldID = dr.getHeader()->id; - auto& buffer = dr.getMutableData(); - buffer.clear(); - buffer.insert(buffer.begin(), raw.begin(), raw.end()); - dnsdist::PacketMangling::editDNSHeaderFromPacket(buffer, [oldID](dnsheader& header) { - header.id = oldID; - return true; - }); - }); - - luaCtx.registerFunction(DNSResponse::*)()const>("getEDNSOptions", [](const DNSResponse& dq) { - if (dq.ednsOptions == nullptr) { - parseEDNSOptions(dq); - if (dq.ednsOptions == nullptr) { - throw std::runtime_error("parseEDNSOptions should have populated the EDNS options"); - } - } - - return *dq.ednsOptions; - }); - luaCtx.registerFunction("getTrailingData", [](const DNSResponse& dq) { - return dq.getTrailingData(); - }); - luaCtx.registerFunction("setTrailingData", [](DNSResponse& dq, const std::string& tail) { - return dq.setTrailingData(tail); - }); - - luaCtx.registerFunction("setTag", [](DNSResponse& dr, const std::string& strLabel, const std::string& strValue) { - dr.setTag(strLabel, strValue); - }); - - luaCtx.registerFunction)>("setTagArray", [](DNSResponse& dr, const LuaAssociativeTable&tags) { - for (const auto& tag : tags) { - dr.setTag(tag.first, tag.second); - } - }); - luaCtx.registerFunction("getTag", [](const DNSResponse& dr, const std::string& strLabel) { - if (!dr.ids.qTag) { - return string(); - } - - std::string strValue; - const auto it = dr.ids.qTag->find(strLabel); - if (it == dr.ids.qTag->cend()) { - return string(); - } - return it->second; - }); - luaCtx.registerFunction("getTagArray", [](const DNSResponse& dr) { - if (!dr.ids.qTag) { - QTag empty; - return empty; - } - - return *dr.ids.qTag; - }); - - luaCtx.registerFunction("getProtocol", [](const DNSResponse& dr) { - return dr.getProtocol().toPrettyString(); - }); - - luaCtx.registerFunction("getQueryTime", [](const DNSResponse& dr) { - return dr.ids.queryRealTime.getStartTime(); - }); - - luaCtx.registerFunction("sendTrap", [](const DNSResponse& dr, boost::optional reason) { -#ifdef HAVE_NET_SNMP - if (g_snmpAgent && g_snmpTrapsEnabled) { - g_snmpAgent->sendDNSTrap(dr, reason ? *reason : ""); - } -#endif /* HAVE_NET_SNMP */ - }); - -#ifdef HAVE_DNS_OVER_HTTPS - luaCtx.registerFunction("getHTTPPath", [](const DNSQuestion& dq) { - if (dq.ids.du == nullptr) { - return std::string(); - } - return dq.ids.du->getHTTPPath(); - }); - - luaCtx.registerFunction("getHTTPQueryString", [](const DNSQuestion& dq) { - if (dq.ids.du == nullptr) { - return std::string(); - } - return dq.ids.du->getHTTPQueryString(); - }); - - luaCtx.registerFunction("getHTTPHost", [](const DNSQuestion& dq) { - if (dq.ids.du == nullptr) { - return std::string(); - } - return dq.ids.du->getHTTPHost(); - }); - - luaCtx.registerFunction("getHTTPScheme", [](const DNSQuestion& dq) { - if (dq.ids.du == nullptr) { - return std::string(); - } - return dq.ids.du->getHTTPScheme(); - }); - - luaCtx.registerFunction(DNSQuestion::*)(void)const>("getHTTPHeaders", [](const DNSQuestion& dq) { - if (dq.ids.du == nullptr) { - return LuaAssociativeTable(); - } - return dq.ids.du->getHTTPHeaders(); - }); - - luaCtx.registerFunction contentType)>("setHTTPResponse", [](DNSQuestion& dq, uint64_t statusCode, const std::string& body, const boost::optional contentType) { - if (dq.ids.du == nullptr) { - return; - } - checkParameterBound("DNSQuestion::setHTTPResponse", statusCode, std::numeric_limits::max()); - PacketBuffer vect(body.begin(), body.end()); - dq.ids.du->setHTTPResponse(statusCode, std::move(vect), contentType ? *contentType : ""); - }); -#endif /* HAVE_DNS_OVER_HTTPS */ - - luaCtx.registerFunction("setNegativeAndAdditionalSOA", [](DNSQuestion& dq, bool nxd, const std::string& zone, uint64_t ttl, const std::string& mname, const std::string& rname, uint64_t serial, uint64_t refresh, uint64_t retry, uint64_t expire, uint64_t minimum) { - checkParameterBound("setNegativeAndAdditionalSOA", ttl, std::numeric_limits::max()); - checkParameterBound("setNegativeAndAdditionalSOA", serial, std::numeric_limits::max()); - checkParameterBound("setNegativeAndAdditionalSOA", refresh, std::numeric_limits::max()); - checkParameterBound("setNegativeAndAdditionalSOA", retry, std::numeric_limits::max()); - checkParameterBound("setNegativeAndAdditionalSOA", expire, std::numeric_limits::max()); - checkParameterBound("setNegativeAndAdditionalSOA", minimum, std::numeric_limits::max()); - - return setNegativeAndAdditionalSOA(dq, nxd, DNSName(zone), ttl, DNSName(mname), DNSName(rname), serial, refresh, retry, expire, minimum, false); - }); - - luaCtx.registerFunction& extraText)>("setExtendedDNSError", [](DNSResponse& dnsResponse, uint16_t infoCode, const boost::optional& extraText) { - EDNSExtendedError ede; - ede.infoCode = infoCode; - if (extraText) { - ede.extraText = *extraText; - } - dnsResponse.ids.d_extendedError = std::make_unique(ede); - }); - - luaCtx.registerFunction("suspend", [](DNSResponse& dr, uint16_t asyncID, uint16_t queryID, uint32_t timeoutMs) { - dr.asynchronous = true; - return dnsdist::suspendResponse(dr, asyncID, queryID, timeoutMs); - }); - - luaCtx.registerFunction("changeName", [](DNSResponse& dr, const DNSName& newName) -> bool { - if (!dnsdist::changeNameInDNSPacket(dr.getMutableData(), dr.ids.qname, newName)) { - return false; - } - dr.ids.qname = newName; - return true; - }); - - luaCtx.registerFunction("restart", [](DNSResponse& dr) { - if (!dr.ids.d_packet) { - return false; - } - dr.asynchronous = true; - dr.getMutableData() = *dr.ids.d_packet; - auto query = dnsdist::getInternalQueryFromDQ(dr, false); - return dnsdist::queueQueryResumptionEvent(std::move(query)); - }); - - luaCtx.registerFunction(DNSResponse::*)(void)const>("getSelectedBackend", [](const DNSResponse& dr) { - return dr.d_downstream; - }); -#endif /* DISABLE_NON_FFI_DQ_BINDINGS */ -} diff --git a/pdns/dnsdist-lua-bindings.cc b/pdns/dnsdist-lua-bindings.cc deleted file mode 100644 index 79ba4ec57b33..000000000000 --- a/pdns/dnsdist-lua-bindings.cc +++ /dev/null @@ -1,816 +0,0 @@ -/* - * This file is part of PowerDNS or dnsdist. - * Copyright -- PowerDNS.COM B.V. and its contributors - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of version 2 of the GNU General Public License as - * published by the Free Software Foundation. - * - * In addition, for the avoidance of any doubt, permission is granted to - * link this program with OpenSSL and to (re)distribute the binaries - * produced as the result of such linking. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ -#include "bpf-filter.hh" -#include "config.h" -#include "dnsdist.hh" -#include "dnsdist-async.hh" -#include "dnsdist-lua.hh" -#include "dnsdist-resolver.hh" -#include "dnsdist-svc.hh" - -#include "dolog.hh" - -// NOLINTNEXTLINE(readability-function-cognitive-complexity): this function declares Lua bindings, even with a good refactoring it will likely blow up the threshold -void setupLuaBindings(LuaContext& luaCtx, bool client, bool configCheck) -{ - luaCtx.writeFunction("vinfolog", [](const string& arg) { - vinfolog("%s", arg); - }); - luaCtx.writeFunction("infolog", [](const string& arg) { - infolog("%s", arg); - }); - luaCtx.writeFunction("errlog", [](const string& arg) { - errlog("%s", arg); - }); - luaCtx.writeFunction("warnlog", [](const string& arg) { - warnlog("%s", arg); - }); - luaCtx.writeFunction("show", [](const string& arg) { - g_outputBuffer+=arg; - g_outputBuffer+="\n"; - }); - - /* Exceptions */ - luaCtx.registerFunction("__tostring", [](const std::exception_ptr& eptr) -> std::string { - try { - if (eptr) { - std::rethrow_exception(eptr); - } - } catch(const std::exception& e) { - return string(e.what()); - } catch(const PDNSException& e) { - return e.reason; - } catch(...) { - return string("Unknown exception"); - } - return string("No exception"); - }); -#ifndef DISABLE_POLICIES_BINDINGS - /* ServerPolicy */ - luaCtx.writeFunction("newServerPolicy", [](string name, ServerPolicy::policyfunc_t policy) { return std::make_shared(name, policy, true);}); - luaCtx.registerMember("name", &ServerPolicy::d_name); - luaCtx.registerMember("policy", &ServerPolicy::d_policy); - luaCtx.registerMember("ffipolicy", &ServerPolicy::d_ffipolicy); - luaCtx.registerMember("isLua", &ServerPolicy::d_isLua); - luaCtx.registerMember("isFFI", &ServerPolicy::d_isFFI); - luaCtx.registerMember("isPerThread", &ServerPolicy::d_isPerThread); - luaCtx.registerFunction("toString", &ServerPolicy::toString); - luaCtx.registerFunction("__tostring", &ServerPolicy::toString); - - ServerPolicy policies[] = { - ServerPolicy{"firstAvailable", firstAvailable, false}, - ServerPolicy{"roundrobin", roundrobin, false}, - ServerPolicy{"wrandom", wrandom, false}, - ServerPolicy{"whashed", whashed, false}, - ServerPolicy{"chashed", chashed, false}, - ServerPolicy{"leastOutstanding", leastOutstanding, false} - }; - for (auto& policy : policies) { - luaCtx.writeVariable(policy.d_name, policy); - } - -#endif /* DISABLE_POLICIES_BINDINGS */ - - /* ServerPool */ - luaCtx.registerFunction::*)(std::shared_ptr)>("setCache", [](std::shared_ptr pool, std::shared_ptr cache) { - if (pool) { - pool->packetCache = std::move(cache); - } - }); - luaCtx.registerFunction("getCache", &ServerPool::getCache); - luaCtx.registerFunction::*)()>("unsetCache", [](std::shared_ptr pool) { - if (pool) { - pool->packetCache = nullptr; - } - }); - luaCtx.registerFunction("getECS", &ServerPool::getECS); - luaCtx.registerFunction("setECS", &ServerPool::setECS); - -#ifndef DISABLE_DOWNSTREAM_BINDINGS - /* DownstreamState */ - luaCtx.registerFunction("setQPS", [](DownstreamState& state, int lim) { state.qps = lim > 0 ? QPSLimiter(lim, lim) : QPSLimiter(); }); - luaCtx.registerFunction::*)(string)>("addPool", [](const std::shared_ptr& state, const string& pool) { - auto localPools = g_pools.getCopy(); - addServerToPool(localPools, pool, state); - g_pools.setState(localPools); - state->d_config.pools.insert(pool); - }); - luaCtx.registerFunction::*)(string)>("rmPool", [](const std::shared_ptr& state, const string& pool) { - auto localPools = g_pools.getCopy(); - removeServerFromPool(localPools, pool, state); - g_pools.setState(localPools); - state->d_config.pools.erase(pool); - }); - luaCtx.registerFunction("getOutstanding", [](const DownstreamState& state) { return state.outstanding.load(); }); - luaCtx.registerFunction("getDrops", [](const DownstreamState& state) { return state.reuseds.load(); }); - luaCtx.registerFunction("getLatency", [](const DownstreamState& state) { return state.getRelevantLatencyUsec(); }); - luaCtx.registerFunction("isUp", &DownstreamState::isUp); - luaCtx.registerFunction("setDown", &DownstreamState::setDown); - luaCtx.registerFunction("setUp", &DownstreamState::setUp); - luaCtx.registerFunction newStatus)>("setAuto", [](DownstreamState& state, boost::optional newStatus) { - if (newStatus) { - state.setUpStatus(*newStatus); - } - state.setAuto(); - }); - luaCtx.registerFunction newStatus)>("setLazyAuto", [](DownstreamState& state, boost::optional newStatus) { - if (newStatus) { - state.setUpStatus(*newStatus); - } - state.setLazyAuto(); - }); - luaCtx.registerFunction("getName", [](const DownstreamState& state) -> const std::string& { return state.getName(); }); - luaCtx.registerFunction("getNameWithAddr", [](const DownstreamState& state) -> const std::string& { return state.getNameWithAddr(); }); - luaCtx.registerMember("upStatus", &DownstreamState::upStatus); - luaCtx.registerMember("weight", - [](const DownstreamState& state) -> int {return state.d_config.d_weight;}, - [](DownstreamState& state, int newWeight) { state.setWeight(newWeight); } - ); - luaCtx.registerMember("order", - [](const DownstreamState& state) -> int {return state.d_config.order; }, - [](DownstreamState& state, int newOrder) { state.d_config.order = newOrder; } - ); - luaCtx.registerMember("name", [](const DownstreamState& backend) -> const std::string { return backend.getName(); }, [](DownstreamState& backend, const std::string& newName) { backend.setName(newName); }); - luaCtx.registerFunction("getID", [](const DownstreamState& state) { return boost::uuids::to_string(*state.d_config.id); }); -#endif /* DISABLE_DOWNSTREAM_BINDINGS */ - -#ifndef DISABLE_DNSHEADER_BINDINGS - /* dnsheader */ - luaCtx.registerFunction("setRD", [](dnsheader& dh, bool v) { - dh.rd=v; - }); - - luaCtx.registerFunction("getRD", [](const dnsheader& dh) { - return (bool)dh.rd; - }); - - luaCtx.registerFunction("setRA", [](dnsheader& dh, bool v) { - dh.ra=v; - }); - - luaCtx.registerFunction("getRA", [](const dnsheader& dh) { - return (bool)dh.ra; - }); - - luaCtx.registerFunction("setAD", [](dnsheader& dh, bool v) { - dh.ad=v; - }); - - luaCtx.registerFunction("getAD", [](const dnsheader& dh) { - return (bool)dh.ad; - }); - - luaCtx.registerFunction("setAA", [](dnsheader& dh, bool v) { - dh.aa=v; - }); - - luaCtx.registerFunction("getAA", [](const dnsheader& dh) { - return (bool)dh.aa; - }); - - luaCtx.registerFunction("setCD", [](dnsheader& dh, bool v) { - dh.cd=v; - }); - - luaCtx.registerFunction("getCD", [](const dnsheader& dh) { - return (bool)dh.cd; - }); - - luaCtx.registerFunction("getID", [](const dnsheader& dh) { - return ntohs(dh.id); - }); - - luaCtx.registerFunction("getTC", [](const dnsheader& dh) { - return (bool)dh.tc; - }); - - luaCtx.registerFunction("setTC", [](dnsheader& dh, bool v) { - dh.tc=v; - if(v) dh.ra = dh.rd; // you'll always need this, otherwise TC=1 gets ignored - }); - - luaCtx.registerFunction("setQR", [](dnsheader& dh, bool v) { - dh.qr=v; - }); -#endif /* DISABLE_DNSHEADER_BINDINGS */ - -#ifndef DISABLE_COMBO_ADDR_BINDINGS - /* ComboAddress */ - luaCtx.writeFunction("newCA", [](const std::string& name) { return ComboAddress(name); }); - luaCtx.writeFunction("newCAFromRaw", [](const std::string& raw, boost::optional port) { - if (raw.size() == 4) { - struct sockaddr_in sin4; - memset(&sin4, 0, sizeof(sin4)); - sin4.sin_family = AF_INET; - memcpy(&sin4.sin_addr.s_addr, raw.c_str(), raw.size()); - if (port) { - sin4.sin_port = htons(*port); - } - return ComboAddress(&sin4); - } - else if (raw.size() == 16) { - struct sockaddr_in6 sin6; - memset(&sin6, 0, sizeof(sin6)); - sin6.sin6_family = AF_INET6; - memcpy(&sin6.sin6_addr.s6_addr, raw.c_str(), raw.size()); - if (port) { - sin6.sin6_port = htons(*port); - } - return ComboAddress(&sin6); - } - return ComboAddress(); - }); - luaCtx.registerFunction("tostring", [](const ComboAddress& ca) { return ca.toString(); }); - luaCtx.registerFunction("tostringWithPort", [](const ComboAddress& ca) { return ca.toStringWithPort(); }); - luaCtx.registerFunction("__tostring", [](const ComboAddress& ca) { return ca.toString(); }); - luaCtx.registerFunction("toString", [](const ComboAddress& ca) { return ca.toString(); }); - luaCtx.registerFunction("toStringWithPort", [](const ComboAddress& ca) { return ca.toStringWithPort(); }); - luaCtx.registerFunction("getPort", [](const ComboAddress& ca) { return ntohs(ca.sin4.sin_port); } ); - luaCtx.registerFunction("truncate", [](ComboAddress& ca, unsigned int bits) { ca.truncate(bits); }); - luaCtx.registerFunction("isIPv4", [](const ComboAddress& ca) { return ca.sin4.sin_family == AF_INET; }); - luaCtx.registerFunction("isIPv6", [](const ComboAddress& ca) { return ca.sin4.sin_family == AF_INET6; }); - luaCtx.registerFunction("isMappedIPv4", [](const ComboAddress& ca) { return ca.isMappedIPv4(); }); - luaCtx.registerFunction("mapToIPv4", [](const ComboAddress& ca) { return ca.mapToIPv4(); }); - luaCtx.registerFunction("match", [](nmts_t& s, const ComboAddress& ca) { return s.match(ca); }); -#endif /* DISABLE_COMBO_ADDR_BINDINGS */ - -#ifndef DISABLE_DNSNAME_BINDINGS - /* DNSName */ - luaCtx.registerFunction("isPartOf", &DNSName::isPartOf); - luaCtx.registerFunction("chopOff", [](DNSName&dn ) { return dn.chopOff(); }); - luaCtx.registerFunction("countLabels", [](const DNSName& name) { return name.countLabels(); }); - luaCtx.registerFunction("hash", [](const DNSName& name) { return name.hash(); }); - luaCtx.registerFunction("wirelength", [](const DNSName& name) { return name.wirelength(); }); - luaCtx.registerFunction("tostring", [](const DNSName&dn ) { return dn.toString(); }); - luaCtx.registerFunction("toString", [](const DNSName&dn ) { return dn.toString(); }); - luaCtx.registerFunction("toStringNoDot", [](const DNSName&dn ) { return dn.toStringNoDot(); }); - luaCtx.registerFunction("__tostring", [](const DNSName&dn ) { return dn.toString(); }); - luaCtx.registerFunction("toDNSString", [](const DNSName&dn ) { return dn.toDNSString(); }); - luaCtx.registerFunction("makeRelative", [](const DNSName& dn, const DNSName& to) { return dn.makeRelative(to); }); - luaCtx.writeFunction("newDNSName", [](const std::string& name) { return DNSName(name); }); - luaCtx.writeFunction("newDNSNameFromRaw", [](const std::string& name) { return DNSName(name.c_str(), name.size(), 0, false); }); - luaCtx.writeFunction("newSuffixMatchNode", []() { return SuffixMatchNode(); }); - luaCtx.writeFunction("newDNSNameSet", []() { return DNSNameSet(); }); - - /* DNSNameSet */ - luaCtx.registerFunction("toString", [](const DNSNameSet&dns ) { return dns.toString(); }); - luaCtx.registerFunction("__tostring", [](const DNSNameSet&dns ) { return dns.toString(); }); - luaCtx.registerFunction("add", [](DNSNameSet& dns, DNSName& dn) { dns.insert(dn); }); - luaCtx.registerFunction("check", [](DNSNameSet& dns, DNSName& dn) { return dns.find(dn) != dns.end(); }); - luaCtx.registerFunction("delete",(size_t (DNSNameSet::*)(const DNSName&)) &DNSNameSet::erase); - luaCtx.registerFunction("size",(size_t (DNSNameSet::*)() const) &DNSNameSet::size); - luaCtx.registerFunction("clear",(void (DNSNameSet::*)()) &DNSNameSet::clear); - luaCtx.registerFunction("empty",(bool (DNSNameSet::*)() const) &DNSNameSet::empty); -#endif /* DISABLE_DNSNAME_BINDINGS */ - -#ifndef DISABLE_SUFFIX_MATCH_BINDINGS - /* SuffixMatchNode */ - luaCtx.registerFunction, LuaArray> &name)>("add", [](SuffixMatchNode &smn, const boost::variant, LuaArray> &name) { - if (name.type() == typeid(DNSName)) { - const auto& actualName = boost::get(name); - smn.add(actualName); - return; - } - if (name.type() == typeid(std::string)) { - const auto& actualName = boost::get(name); - smn.add(actualName); - return; - } - if (name.type() == typeid(LuaArray)) { - const auto& names = boost::get>(name); - for (const auto& actualName : names) { - smn.add(actualName.second); - } - return; - } - if (name.type() == typeid(LuaArray)) { - const auto& names = boost::get>(name); - for (const auto& actualName : names) { - smn.add(actualName.second); - } - return; - } - }); - luaCtx.registerFunction, LuaArray> &name)>("remove", [](SuffixMatchNode &smn, const boost::variant, LuaArray> &name) { - if (name.type() == typeid(DNSName)) { - const auto& actualName = boost::get(name); - smn.remove(actualName); - return; - } - if (name.type() == typeid(string)) { - const auto& actualName = boost::get(name); - DNSName dnsName(actualName); - smn.remove(dnsName); - return; - } - if (name.type() == typeid(LuaArray)) { - const auto& names = boost::get>(name); - for (const auto& actualName : names) { - smn.remove(actualName.second); - } - return; - } - if (name.type() == typeid(LuaArray)) { - const auto& names = boost::get>(name); - for (const auto& actualName : names) { - DNSName dnsName(actualName.second); - smn.remove(dnsName); - } - return; - } - }); - - luaCtx.registerFunction("check", (bool (SuffixMatchNode::*)(const DNSName&) const) &SuffixMatchNode::check); - luaCtx.registerFunction (SuffixMatchNode::*)(const DNSName&) const>("getBestMatch", [](const SuffixMatchNode& smn, const DNSName& needle) { - boost::optional result{boost::none}; - auto res = smn.getBestMatch(needle); - if (res) { - result = *res; - } - return result; - }); -#endif /* DISABLE_SUFFIX_MATCH_BINDINGS */ - -#ifndef DISABLE_NETMASK_BINDINGS - /* Netmask */ - luaCtx.writeFunction("newNetmask", [](boost::variant addrOrStr, boost::optional bits) { - if (addrOrStr.type() == typeid(ComboAddress)) { - const auto& comboAddr = boost::get(addrOrStr); - if (bits) { - return Netmask(comboAddr, *bits); - } - return Netmask(comboAddr); - } - if (addrOrStr.type() == typeid(std::string)) { - const auto& str = boost::get(addrOrStr); - return Netmask(str); - } - throw std::runtime_error("Invalid parameter passed to 'newNetmask()'"); - }); - luaCtx.registerFunction("empty", &Netmask::empty); - luaCtx.registerFunction("getBits", &Netmask::getBits); - luaCtx.registerFunction("getNetwork", [](const Netmask& nm) { return nm.getNetwork(); } ); // const reference makes this necessary - luaCtx.registerFunction("getMaskedNetwork", [](const Netmask& nm) { return nm.getMaskedNetwork(); } ); - luaCtx.registerFunction("isIpv4", &Netmask::isIPv4); - luaCtx.registerFunction("isIPv4", &Netmask::isIPv4); - luaCtx.registerFunction("isIpv6", &Netmask::isIPv6); - luaCtx.registerFunction("isIPv6", &Netmask::isIPv6); - luaCtx.registerFunction("match", (bool (Netmask::*)(const string&) const)&Netmask::match); - luaCtx.registerFunction("toString", &Netmask::toString); - luaCtx.registerFunction("__tostring", &Netmask::toString); - luaCtx.registerEqFunction(&Netmask::operator==); - luaCtx.registerToStringFunction(&Netmask::toString); - - /* NetmaskGroup */ - luaCtx.writeFunction("newNMG", []() { return NetmaskGroup(); }); - luaCtx.registerFunction("addMask", [](NetmaskGroup& nmg, const std::string& mask) - { - nmg.addMask(mask); - }); - luaCtx.registerFunction("addNMG", [](NetmaskGroup& nmg, const NetmaskGroup& otherNMG) { - /* this is not going to be very efficient, sorry */ - auto entries = otherNMG.toStringVector(); - for (const auto& entry : entries) { - nmg.addMask(entry); - } - }); - luaCtx.registerFunction& map)>("addMasks", [](NetmaskGroup&nmg, const std::map& map) - { - for (const auto& entry : map) { - nmg.addMask(Netmask(entry.first)); - } - }); - - luaCtx.registerFunction("match", (bool (NetmaskGroup::*)(const ComboAddress&) const)&NetmaskGroup::match); - luaCtx.registerFunction("size", &NetmaskGroup::size); - luaCtx.registerFunction("clear", &NetmaskGroup::clear); - luaCtx.registerFunction("toString", [](const NetmaskGroup& nmg ) { return "NetmaskGroup " + nmg.toString(); }); - luaCtx.registerFunction("__tostring", [](const NetmaskGroup& nmg ) { return "NetmaskGroup " + nmg.toString(); }); -#endif /* DISABLE_NETMASK_BINDINGS */ - -#ifndef DISABLE_QPS_LIMITER_BINDINGS - /* QPSLimiter */ - luaCtx.writeFunction("newQPSLimiter", [](int rate, int burst) { return QPSLimiter(rate, burst); }); - luaCtx.registerFunction("check", &QPSLimiter::check); -#endif /* DISABLE_QPS_LIMITER_BINDINGS */ - -#ifndef DISABLE_CLIENT_STATE_BINDINGS - /* ClientState */ - luaCtx.registerFunction("toString", [](const ClientState& fe) { - setLuaNoSideEffect(); - return fe.local.toStringWithPort(); - }); - luaCtx.registerFunction("__tostring", [](const ClientState& fe) { - setLuaNoSideEffect(); - return fe.local.toStringWithPort(); - }); - luaCtx.registerFunction("getType", [](const ClientState& fe) { - setLuaNoSideEffect(); - return fe.getType(); - }); - luaCtx.registerFunction("getConfiguredTLSProvider", [](const ClientState& fe) { - setLuaNoSideEffect(); - if (fe.tlsFrontend != nullptr) { - return fe.tlsFrontend->getRequestedProvider(); - } - else if (fe.dohFrontend != nullptr) { - return std::string("openssl"); - } - return std::string(); - }); - luaCtx.registerFunction("getEffectiveTLSProvider", [](const ClientState& fe) { - setLuaNoSideEffect(); - if (fe.tlsFrontend != nullptr) { - return fe.tlsFrontend->getEffectiveProvider(); - } - else if (fe.dohFrontend != nullptr) { - return std::string("openssl"); - } - return std::string(); - }); - luaCtx.registerMember("muted", &ClientState::muted); -#ifdef HAVE_EBPF - luaCtx.registerFunction)>("attachFilter", [](ClientState& frontend, std::shared_ptr bpf) { - if (bpf) { - frontend.attachFilter(bpf, frontend.getSocket()); - } - }); - luaCtx.registerFunction("detachFilter", [](ClientState& frontend) { - frontend.detachFilter(frontend.getSocket()); - }); -#endif /* HAVE_EBPF */ -#endif /* DISABLE_CLIENT_STATE_BINDINGS */ - - /* BPF Filter */ -#ifdef HAVE_EBPF - using bpfopts_t = LuaAssociativeTable>; - luaCtx.writeFunction("newBPFFilter", [client](bpfopts_t opts) { - if (client) { - return std::shared_ptr(nullptr); - } - std::unordered_map mapsConfig; - - const auto convertParamsToConfig = [&](const std::string& name, BPFFilter::MapType type) { - BPFFilter::MapConfiguration config; - config.d_type = type; - if (const string key = name + "MaxItems"; opts.count(key)) { - const auto& tmp = opts.at(key); - if (tmp.type() != typeid(uint32_t)) { - throw std::runtime_error("params is invalid"); - } - const auto& params = boost::get(tmp); - config.d_maxItems = params; - } - - if (const string key = name + "PinnedPath"; opts.count(key)) { - auto& tmp = opts.at(key); - if (tmp.type() != typeid(string)) { - throw std::runtime_error("params is invalid"); - } - auto& params = boost::get(tmp); - config.d_pinnedPath = std::move(params); - } - mapsConfig[name] = std::move(config); - }; - - convertParamsToConfig("ipv4", BPFFilter::MapType::IPv4); - convertParamsToConfig("ipv6", BPFFilter::MapType::IPv6); - convertParamsToConfig("qnames", BPFFilter::MapType::QNames); - convertParamsToConfig("cidr4", BPFFilter::MapType::CIDR4); - convertParamsToConfig("cidr6", BPFFilter::MapType::CIDR6); - - BPFFilter::MapFormat format = BPFFilter::MapFormat::Legacy; - bool external = false; - if (opts.count("external")) { - const auto& tmp = opts.at("external"); - if (tmp.type() != typeid(bool)) { - throw std::runtime_error("params is invalid"); - } - if ((external = boost::get(tmp))) { - format = BPFFilter::MapFormat::WithActions; - } - } - - return std::make_shared(mapsConfig, format, external); - }); - - luaCtx.registerFunction::*)(const ComboAddress& ca, boost::optional action)>("block", [](std::shared_ptr bpf, const ComboAddress& ca, boost::optional action) { - if (bpf) { - if (!action) { - return bpf->block(ca, BPFFilter::MatchAction::Drop); - } - else { - BPFFilter::MatchAction match; - - switch (*action) { - case 0: - match = BPFFilter::MatchAction::Pass; - break; - case 1: - match = BPFFilter::MatchAction::Drop; - break; - case 2: - match = BPFFilter::MatchAction::Truncate; - break; - default: - throw std::runtime_error("Unsupported action for BPFFilter::block"); - } - return bpf->block(ca, match); - } - } - }); - luaCtx.registerFunction::*)(const string& range, uint32_t action, boost::optional force)>("addRangeRule", [](std::shared_ptr bpf, const string& range, uint32_t action, boost::optional force) { - if (!bpf) { - return; - } - BPFFilter::MatchAction match; - switch (action) { - case 0: - match = BPFFilter::MatchAction::Pass; - break; - case 1: - match = BPFFilter::MatchAction::Drop; - break; - case 2: - match = BPFFilter::MatchAction::Truncate; - break; - default: - throw std::runtime_error("Unsupported action for BPFFilter::block"); - } - return bpf->addRangeRule(Netmask(range), force ? *force : false, match); - }); - luaCtx.registerFunction::*)(const DNSName& qname, boost::optional qtype, boost::optional action)>("blockQName", [](std::shared_ptr bpf, const DNSName& qname, boost::optional qtype, boost::optional action) { - if (bpf) { - if (!action) { - return bpf->block(qname, BPFFilter::MatchAction::Drop, qtype ? *qtype : 255); - } - else { - BPFFilter::MatchAction match; - - switch (*action) { - case 0: - match = BPFFilter::MatchAction::Pass; - break; - case 1: - match = BPFFilter::MatchAction::Drop; - break; - case 2: - match = BPFFilter::MatchAction::Truncate; - break; - default: - throw std::runtime_error("Unsupported action for BPFFilter::blockQName"); - } - return bpf->block(qname, match, qtype ? *qtype : 255); - } - } - }); - - luaCtx.registerFunction::*)(const ComboAddress& ca)>("unblock", [](std::shared_ptr bpf, const ComboAddress& ca) { - if (bpf) { - return bpf->unblock(ca); - } - }); - luaCtx.registerFunction::*)(const string& range)>("rmRangeRule", [](std::shared_ptr bpf, const string& range) { - if (!bpf) { - return; - } - bpf->rmRangeRule(Netmask(range)); - }); - luaCtx.registerFunction::*)() const>("lsRangeRule", [](const std::shared_ptr bpf) { - setLuaNoSideEffect(); - std::string res; - if (!bpf) { - return res; - } - const auto rangeStat = bpf->getRangeRule(); - for (const auto& value : rangeStat) { - if (value.first.isIPv4()) { - res += BPFFilter::toString(value.second.action) + "\t " + value.first.toString() + "\n"; - } - else if (value.first.isIPv6()) { - res += BPFFilter::toString(value.second.action) + "\t[" + value.first.toString() + "]\n"; - } - } - return res; - }); - luaCtx.registerFunction::*)(const DNSName& qname, boost::optional qtype)>("unblockQName", [](std::shared_ptr bpf, const DNSName& qname, boost::optional qtype) { - if (bpf) { - return bpf->unblock(qname, qtype ? *qtype : 255); - } - }); - - luaCtx.registerFunction::*)()const>("getStats", [](const std::shared_ptr bpf) { - setLuaNoSideEffect(); - std::string res; - if (bpf) { - auto stats = bpf->getAddrStats(); - for (const auto& value : stats) { - if (value.first.sin4.sin_family == AF_INET) { - res += value.first.toString() + ": " + std::to_string(value.second) + "\n"; - } - else if (value.first.sin4.sin_family == AF_INET6) { - res += "[" + value.first.toString() + "]: " + std::to_string(value.second) + "\n"; - } - } - const auto rangeStat = bpf->getRangeRule(); - for (const auto& value : rangeStat) { - if (value.first.isIPv4()) { - res += BPFFilter::toString(value.second.action) + "\t " + value.first.toString() + ": " + std::to_string(value.second.counter) + "\n"; - } - else if (value.first.isIPv6()) { - res += BPFFilter::toString(value.second.action) + "\t[" + value.first.toString() + "]: " + std::to_string(value.second.counter) + "\n"; - } - } - auto qstats = bpf->getQNameStats(); - for (const auto& value : qstats) { - res += std::get<0>(value).toString() + " " + std::to_string(std::get<1>(value)) + ": " + std::to_string(std::get<2>(value)) + "\n"; - } - } - return res; - }); - - luaCtx.registerFunction::*)()>("attachToAllBinds", [](std::shared_ptr bpf) { - std::string res; - if (!g_configurationDone) { - throw std::runtime_error("attachToAllBinds() cannot be used at configuration time!"); - return; - } - if (bpf) { - for (const auto& frontend : g_frontends) { - frontend->attachFilter(bpf, frontend->getSocket()); - } - } - }); - - luaCtx.writeFunction("newDynBPFFilter", [client](std::shared_ptr bpf) { - if (client) { - return std::shared_ptr(nullptr); - } - return std::make_shared(bpf); - }); - - luaCtx.registerFunction::*)(const ComboAddress& addr, boost::optional seconds)>("block", [](std::shared_ptr dbpf, const ComboAddress& addr, boost::optional seconds) { - if (dbpf) { - struct timespec until; - clock_gettime(CLOCK_MONOTONIC, &until); - until.tv_sec += seconds ? *seconds : 10; - dbpf->block(addr, until); - } - }); - - luaCtx.registerFunction::*)()>("purgeExpired", [](std::shared_ptr dbpf) { - if (dbpf) { - struct timespec now; - clock_gettime(CLOCK_MONOTONIC, &now); - dbpf->purgeExpired(now); - } - }); - - luaCtx.registerFunction::*)(LuaTypeOrArrayOf)>("excludeRange", [](std::shared_ptr dbpf, LuaTypeOrArrayOf ranges) { - if (!dbpf) { - return; - } - - if (ranges.type() == typeid(LuaArray)) { - for (const auto& range : *boost::get>(&ranges)) { - dbpf->excludeRange(Netmask(range.second)); - } - } - else { - dbpf->excludeRange(Netmask(*boost::get(&ranges))); - } - }); - - luaCtx.registerFunction::*)(LuaTypeOrArrayOf)>("includeRange", [](std::shared_ptr dbpf, LuaTypeOrArrayOf ranges) { - if (!dbpf) { - return; - } - - if (ranges.type() == typeid(LuaArray)) { - for (const auto& range : *boost::get>(&ranges)) { - dbpf->includeRange(Netmask(range.second)); - } - } - else { - dbpf->includeRange(Netmask(*boost::get(&ranges))); - } - }); -#endif /* HAVE_EBPF */ - - /* EDNSOptionView */ - luaCtx.registerFunction("count", [](const EDNSOptionView& option) { - return option.values.size(); - }); - luaCtx.registerFunction(EDNSOptionView::*)()const>("getValues", [] (const EDNSOptionView& option) { - std::vector values; - for (const auto& value : option.values) { - values.push_back(std::string(value.content, value.size)); - } - return values; - }); - - luaCtx.writeFunction("newDOHResponseMapEntry", [](const std::string& regex, uint64_t status, const std::string& content, boost::optional> customHeaders) { - checkParameterBound("newDOHResponseMapEntry", status, std::numeric_limits::max()); - boost::optional> headers{boost::none}; - if (customHeaders) { - headers = LuaAssociativeTable(); - for (const auto& header : *customHeaders) { - (*headers)[boost::to_lower_copy(header.first)] = header.second; - } - } - return std::make_shared(regex, status, PacketBuffer(content.begin(), content.end()), headers); - }); - - luaCtx.writeFunction("newSVCRecordParameters", [](uint64_t priority, const std::string& target, boost::optional additionalParameters) - { - checkParameterBound("newSVCRecordParameters", priority, std::numeric_limits::max()); - SVCRecordParameters parameters; - if (additionalParameters) { - parameters = parseSVCParameters(*additionalParameters); - } - parameters.priority = priority; - parameters.target = DNSName(target); - - return parameters; - }); - - luaCtx.writeFunction("getListOfNetworkInterfaces", []() { - LuaArray result; - auto itfs = getListOfNetworkInterfaces(); - int counter = 1; - for (const auto& itf : itfs) { - result.push_back({counter++, itf}); - } - return result; - }); - - luaCtx.writeFunction("getListOfAddressesOfNetworkInterface", [](const std::string& itf) { - LuaArray result; - auto addrs = getListOfAddressesOfNetworkInterface(itf); - int counter = 1; - for (const auto& addr : addrs) { - result.push_back({counter++, addr.toString()}); - } - return result; - }); - - luaCtx.writeFunction("getListOfRangesOfNetworkInterface", [](const std::string& itf) { - LuaArray result; - auto addrs = getListOfRangesOfNetworkInterface(itf); - int counter = 1; - for (const auto& addr : addrs) { - result.push_back({counter++, addr.toString()}); - } - return result; - }); - - luaCtx.writeFunction("getMACAddress", [](const std::string& ip) { - return getMACAddress(ComboAddress(ip)); - }); - - luaCtx.writeFunction("getCurrentTime", []() -> timespec { - timespec now; - if (gettime(&now, true) < 0) { - unixDie("Getting timestamp"); - } - return now; - }); - - luaCtx.writeFunction("getAddressInfo", [client, configCheck](std::string hostname, std::function& ips)> callback) { - if (client || configCheck) { - return; - } - std::thread newThread(dnsdist::resolver::asynchronousResolver, std::move(hostname), [callback=std::move(callback)](const std::string& resolvedHostname, std::vector& ips) { - LuaArray result; - result.reserve(ips.size()); - for (const auto& entry : ips) { - result.emplace_back(result.size() + 1, entry); - } - { - auto lua = g_lua.lock(); - callback(resolvedHostname, result); - dnsdist::handleQueuedAsynchronousEvents(); - } - }); - newThread.detach(); - }); -} diff --git a/pdns/dnsdist-lua-inspection.cc b/pdns/dnsdist-lua-inspection.cc deleted file mode 100644 index 35b5c8b9b344..000000000000 --- a/pdns/dnsdist-lua-inspection.cc +++ /dev/null @@ -1,965 +0,0 @@ -/* - * This file is part of PowerDNS or dnsdist. - * Copyright -- PowerDNS.COM B.V. and its contributors - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of version 2 of the GNU General Public License as - * published by the Free Software Foundation. - * - * In addition, for the avoidance of any doubt, permission is granted to - * link this program with OpenSSL and to (re)distribute the binaries - * produced as the result of such linking. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ -#include - -#include "dnsdist.hh" -#include "dnsdist-lua.hh" -#include "dnsdist-dynblocks.hh" -#include "dnsdist-nghttp2.hh" -#include "dnsdist-rings.hh" -#include "dnsdist-tcp.hh" - -#include "statnode.hh" - -#ifndef DISABLE_TOP_N_BINDINGS -static LuaArray>> getGenResponses(uint64_t top, boost::optional labels, std::function pred) -{ - setLuaNoSideEffect(); - map counts; - unsigned int total=0; - { - for (const auto& shard : g_rings.d_shards) { - auto rl = shard->respRing.lock(); - if (!labels) { - for(const auto& a : *rl) { - if(!pred(a)) - continue; - counts[a.name]++; - total++; - } - } - else { - unsigned int lab = *labels; - for(const auto& a : *rl) { - if(!pred(a)) - continue; - - DNSName temp(a.name); - temp.trimToLabels(lab); - counts[temp]++; - total++; - } - } - } - } - // cout<<"Looked at "<> rcounts; - rcounts.reserve(counts.size()); - for (const auto& c : counts) - rcounts.emplace_back(c.second, c.first.makeLowerCase()); - - sort(rcounts.begin(), rcounts.end(), [](const decltype(rcounts)::value_type& a, - const decltype(rcounts)::value_type& b) { - return b.first < a.first; - }); - - LuaArray>> ret; - ret.reserve(std::min(rcounts.size(), static_cast(top + 1U))); - int count = 1; - unsigned int rest = 0; - for (const auto& rc : rcounts) { - if (count == static_cast(top + 1)) { - rest+=rc.first; - } - else { - ret.push_back({count++, {rc.second.toString(), rc.first, 100.0*rc.first/total}}); - } - } - - if (total > 0) { - ret.push_back({count, {"Rest", rest, 100.0*rest/total}}); - } - else { - ret.push_back({count, {"Rest", rest, 100.0 }}); - } - - return ret; -} -#endif /* DISABLE_TOP_N_BINDINGS */ - -#ifndef DISABLE_DYNBLOCKS -#ifndef DISABLE_DEPRECATED_DYNBLOCK - -typedef std::unordered_map counts_t; - -static counts_t filterScore(const counts_t& counts, - double delta, unsigned int rate) -{ - counts_t ret; - - double lim = delta*rate; - for(const auto& c : counts) { - if (c.second > lim) { - ret[c.first] = c.second; - } - } - - return ret; -} - -using statvisitor_t = std::function; - -static void statNodeRespRing(statvisitor_t visitor, uint64_t seconds) -{ - struct timespec cutoff, now; - gettime(&now); - cutoff = now; - cutoff.tv_sec -= seconds; - - StatNode root; - for (const auto& shard : g_rings.d_shards) { - auto rl = shard->respRing.lock(); - - for(const auto& c : *rl) { - if (now < c.when){ - continue; - } - - if (seconds && c.when < cutoff) { - continue; - } - - const bool hit = c.isACacheHit(); - root.submit(c.name, ((c.dh.rcode == 0 && c.usec == std::numeric_limits::max()) ? -1 : c.dh.rcode), c.size, hit, boost::none); - } - } - - StatNode::Stat node; - root.visit([visitor = std::move(visitor)](const StatNode* node_, const StatNode::Stat& self, const StatNode::Stat& children) { - visitor(*node_, self, children);}, node); -} - -static LuaArray> getRespRing(boost::optional rcode) -{ - typedef LuaAssociativeTable entry_t; - LuaArray ret; - - for (const auto& shard : g_rings.d_shards) { - auto rl = shard->respRing.lock(); - - int count = 1; - for (const auto& c : *rl) { - if (rcode && (rcode.get() != c.dh.rcode)) { - continue; - } - entry_t e; - e["qname"] = c.name.toString(); - e["rcode"] = std::to_string(c.dh.rcode); - ret.emplace_back(count, std::move(e)); - count++; - } - } - - return ret; -} - -static counts_t exceedRespGen(unsigned int rate, int seconds, std::function T) -{ - counts_t counts; - struct timespec cutoff, mintime, now; - gettime(&now); - cutoff = mintime = now; - cutoff.tv_sec -= seconds; - - counts.reserve(g_rings.getNumberOfResponseEntries()); - - for (const auto& shard : g_rings.d_shards) { - auto rl = shard->respRing.lock(); - for(const auto& c : *rl) { - - if(seconds && c.when < cutoff) - continue; - if(now < c.when) - continue; - - T(counts, c); - if(c.when < mintime) - mintime = c.when; - } - } - - double delta = seconds ? seconds : DiffTime(now, mintime); - return filterScore(counts, delta, rate); -} - -static counts_t exceedQueryGen(unsigned int rate, int seconds, std::function T) -{ - counts_t counts; - struct timespec cutoff, mintime, now; - gettime(&now); - cutoff = mintime = now; - cutoff.tv_sec -= seconds; - - counts.reserve(g_rings.getNumberOfQueryEntries()); - - for (const auto& shard : g_rings.d_shards) { - auto rl = shard->queryRing.lock(); - for(const auto& c : *rl) { - if(seconds && c.when < cutoff) - continue; - if(now < c.when) - continue; - T(counts, c); - if(c.when < mintime) - mintime = c.when; - } - } - - double delta = seconds ? seconds : DiffTime(now, mintime); - return filterScore(counts, delta, rate); -} - - -static counts_t exceedRCode(unsigned int rate, int seconds, int rcode) -{ - return exceedRespGen(rate, seconds, [rcode](counts_t& counts, const Rings::Response& r) - { - if(r.dh.rcode == rcode) - counts[r.requestor]++; - }); -} - -static counts_t exceedRespByterate(unsigned int rate, int seconds) -{ - return exceedRespGen(rate, seconds, [](counts_t& counts, const Rings::Response& r) - { - counts[r.requestor]+=r.size; - }); -} - -#endif /* DISABLE_DEPRECATED_DYNBLOCK */ -#endif /* DISABLE_DYNBLOCKS */ -// NOLINTNEXTLINE(readability-function-cognitive-complexity): this function declares Lua bindings, even with a good refactoring it will likely blow up the threshold -void setupLuaInspection(LuaContext& luaCtx) -{ -#ifndef DISABLE_TOP_N_BINDINGS - luaCtx.writeFunction("topClients", [](boost::optional top_) { - setLuaNoSideEffect(); - uint64_t top = top_ ? *top_ : 10U; - map counts; - unsigned int total=0; - { - for (const auto& shard : g_rings.d_shards) { - auto rl = shard->queryRing.lock(); - for(const auto& c : *rl) { - counts[c.requestor]++; - total++; - } - } - } - vector> rcounts; - rcounts.reserve(counts.size()); - for(const auto& c : counts) - rcounts.emplace_back(c.second, c.first); - - sort(rcounts.begin(), rcounts.end(), [](const decltype(rcounts)::value_type& a, - const decltype(rcounts)::value_type& b) { - return b.first < a.first; - }); - unsigned int count=1, rest=0; - boost::format fmt("%4d %-40s %4d %4.1f%%\n"); - for(const auto& rc : rcounts) { - if(count==top+1) - rest+=rc.first; - else - g_outputBuffer += (fmt % (count++) % rc.second.toString() % rc.first % (100.0*rc.first/total)).str(); - } - g_outputBuffer += (fmt % (count) % "Rest" % rest % (total > 0 ? 100.0*rest/total : 100.0)).str(); - }); - - luaCtx.writeFunction("getTopQueries", [](uint64_t top, boost::optional labels) { - setLuaNoSideEffect(); - map counts; - unsigned int total=0; - if(!labels) { - for (const auto& shard : g_rings.d_shards) { - auto rl = shard->queryRing.lock(); - for(const auto& a : *rl) { - counts[a.name]++; - total++; - } - } - } - else { - unsigned int lab = *labels; - for (const auto& shard : g_rings.d_shards) { - auto rl = shard->queryRing.lock(); - // coverity[auto_causes_copy] - for (auto a : *rl) { - a.name.trimToLabels(lab); - counts[a.name]++; - total++; - } - } - } - // cout<<"Looked at "<> rcounts; - rcounts.reserve(counts.size()); - for(const auto& c : counts) - rcounts.emplace_back(c.second, c.first.makeLowerCase()); - - sort(rcounts.begin(), rcounts.end(), [](const decltype(rcounts)::value_type& a, - const decltype(rcounts)::value_type& b) { - return b.first < a.first; - }); - - std::unordered_map>> ret; - unsigned int count=1, rest=0; - for(const auto& rc : rcounts) { - if(count==top+1) - rest+=rc.first; - else - ret.insert({count++, {rc.second.toString(), rc.first, 100.0*rc.first/total}}); - } - - if (total > 0) { - ret.insert({count, {"Rest", rest, 100.0*rest/total}}); - } - else { - ret.insert({count, {"Rest", rest, 100.0}}); - } - - return ret; - - }); - - luaCtx.executeCode(R"(function topQueries(top, labels) top = top or 10; for k,v in ipairs(getTopQueries(top,labels)) do show(string.format("%4d %-40s %4d %4.1f%%",k,v[1],v[2], v[3])) end end)"); - - luaCtx.writeFunction("getResponseRing", []() { - setLuaNoSideEffect(); - size_t totalEntries = 0; - std::vector> rings; - rings.reserve(g_rings.getNumberOfShards()); - for (const auto& shard : g_rings.d_shards) { - { - auto rl = shard->respRing.lock(); - rings.push_back(*rl); - } - totalEntries += rings.back().size(); - } - vector > > ret; - ret.reserve(totalEntries); - decltype(ret)::value_type item; - for (size_t idx = 0; idx < rings.size(); idx++) { - for(const auto& r : rings[idx]) { - item["name"]=r.name.toString(); - item["qtype"]=r.qtype; - item["rcode"]=r.dh.rcode; - item["usec"]=r.usec; - ret.push_back(item); - } - } - return ret; - }); - - luaCtx.writeFunction("getTopResponses", [](uint64_t top, uint64_t kind, boost::optional labels) { - return getGenResponses(top, labels, [kind](const Rings::Response& r) { return r.dh.rcode == kind; }); - }); - - luaCtx.executeCode(R"(function topResponses(top, kind, labels) top = top or 10; kind = kind or 0; for k,v in ipairs(getTopResponses(top, kind, labels)) do show(string.format("%4d %-40s %4d %4.1f%%",k,v[1],v[2],v[3])) end end)"); - - - luaCtx.writeFunction("getSlowResponses", [](uint64_t top, uint64_t msec, boost::optional labels) { - return getGenResponses(top, labels, [msec](const Rings::Response& r) { return r.usec > msec*1000; }); - }); - - - luaCtx.executeCode(R"(function topSlow(top, msec, labels) top = top or 10; msec = msec or 500; for k,v in ipairs(getSlowResponses(top, msec, labels)) do show(string.format("%4d %-40s %4d %4.1f%%",k,v[1],v[2],v[3])) end end)"); - - luaCtx.writeFunction("getTopBandwidth", [](uint64_t top) { - setLuaNoSideEffect(); - return g_rings.getTopBandwidth(top); - }); - - luaCtx.executeCode(R"(function topBandwidth(top) top = top or 10; for k,v in ipairs(getTopBandwidth(top)) do show(string.format("%4d %-40s %4d %4.1f%%",k,v[1],v[2],v[3])) end end)"); -#endif /* DISABLE_TOP_N_BINDINGS */ - - luaCtx.writeFunction("delta", []() { - setLuaNoSideEffect(); - // we hold the lua lock already! - for(const auto& d : g_confDelta) { - struct tm tm; - localtime_r(&d.first.tv_sec, &tm); - char date[80]; - strftime(date, sizeof(date)-1, "-- %a %b %d %Y %H:%M:%S %Z\n", &tm); - g_outputBuffer += date; - g_outputBuffer += d.second + "\n"; - } - }); - - luaCtx.writeFunction("grepq", [](LuaTypeOrArrayOf inp, boost::optional limit, boost::optional> options) { - setLuaNoSideEffect(); - boost::optional nm; - boost::optional dn; - int msec = -1; - std::unique_ptr outputFile{nullptr, fclose}; - - if (options) { - std::string outputFileName; - if (getOptionalValue(options, "outputFile", outputFileName) > 0) { - int fd = open(outputFileName.c_str(), O_CREAT | O_EXCL | O_WRONLY, 0600); - if (fd < 0) { - g_outputBuffer = "Error opening dump file for writing: " + stringerror() + "\n"; - return; - } - outputFile = std::unique_ptr(fdopen(fd, "w"), fclose); - if (outputFile == nullptr) { - g_outputBuffer = "Error opening dump file for writing: " + stringerror() + "\n"; - close(fd); - return; - } - } - checkAllParametersConsumed("grepq", options); - } - - vector vec; - auto str = boost::get(&inp); - if (str) { - vec.push_back(*str); - } - else { - auto v = boost::get>(inp); - for (const auto& a: v) { - vec.push_back(a.second); - } - } - - for (const auto& s : vec) { - try { - nm = Netmask(s); - } - catch (...) { - if (boost::ends_with(s,"ms") && sscanf(s.c_str(), "%ums", &msec)) { - ; - } - else { - try { - dn = DNSName(s); - } - catch (...) { - g_outputBuffer = "Could not parse '"+s+"' as domain name or netmask"; - return; - } - } - } - } - - std::vector qr; - std::vector rr; - qr.reserve(g_rings.getNumberOfQueryEntries()); - rr.reserve(g_rings.getNumberOfResponseEntries()); - for (const auto& shard : g_rings.d_shards) { - { - auto rl = shard->queryRing.lock(); - for (const auto& entry : *rl) { - qr.push_back(entry); - } - } - { - auto rl = shard->respRing.lock(); - for (const auto& entry : *rl) { - rr.push_back(entry); - } - } - } - - sort(qr.begin(), qr.end(), [](const decltype(qr)::value_type& a, const decltype(qr)::value_type& b) { - return b.when < a.when; - }); - - sort(rr.begin(), rr.end(), [](const decltype(rr)::value_type& a, const decltype(rr)::value_type& b) { - return b.when < a.when; - }); - - unsigned int num=0; - struct timespec now; - gettime(&now); - - std::multimap out; - - boost::format fmt("%-7.1f %-47s %-12s %-12s %-5d %-25s %-5s %-6.1f %-2s %-2s %-2s %-s\n"); - const auto headLine = (fmt % "Time" % "Client" % "Protocol" % "Server" % "ID" % "Name" % "Type" % "Lat." % "TC" % "RD" % "AA" % "Rcode").str(); - if (!outputFile) { - g_outputBuffer += headLine; - } - else { - fprintf(outputFile.get(), "%s", headLine.c_str()); - } - - if (msec == -1) { - for (const auto& c : qr) { - bool nmmatch = true; - bool dnmatch = true; - if (nm) { - nmmatch = nm->match(c.requestor); - } - if (dn) { - if (c.name.empty()) { - dnmatch = false; - } - else { - dnmatch = c.name.isPartOf(*dn); - } - } - if (nmmatch && dnmatch) { - QType qt(c.qtype); - std::string extra; - if (c.dh.opcode != 0) { - extra = " (" + Opcode::to_s(c.dh.opcode) + ")"; - } - out.emplace(c.when, (fmt % DiffTime(now, c.when) % c.requestor.toStringWithPort() % dnsdist::Protocol(c.protocol).toString() % "" % htons(c.dh.id) % c.name.toString() % qt.toString() % "" % (c.dh.tc ? "TC" : "") % (c.dh.rd ? "RD" : "") % (c.dh.aa ? "AA" : "") % ("Question" + extra)).str()); - - if (limit && *limit == ++num) { - break; - } - } - } - } - num = 0; - - string extra; - for (const auto& c : rr) { - bool nmmatch = true; - bool dnmatch = true; - bool msecmatch = true; - if (nm) { - nmmatch = nm->match(c.requestor); - } - if (dn) { - if (c.name.empty()) { - dnmatch = false; - } - else { - dnmatch = c.name.isPartOf(*dn); - } - } - if (msec != -1) { - msecmatch = (c.usec/1000 > (unsigned int)msec); - } - - if (nmmatch && dnmatch && msecmatch) { - QType qt(c.qtype); - if (!c.dh.rcode) { - extra = ". " +std::to_string(htons(c.dh.ancount)) + " answers"; - } - else { - extra.clear(); - } - - std::string server = c.ds.toStringWithPort(); - std::string protocol = dnsdist::Protocol(c.protocol).toString(); - if (server == "0.0.0.0:0") { - server = "Cache"; - protocol = "-"; - } - if (c.usec != std::numeric_limits::max()) { - out.emplace(c.when, (fmt % DiffTime(now, c.when) % c.requestor.toStringWithPort() % protocol % server % htons(c.dh.id) % c.name.toString() % qt.toString() % (c.usec / 1000.0) % (c.dh.tc ? "TC" : "") % (c.dh.rd ? "RD" : "") % (c.dh.aa ? "AA" : "") % (RCode::to_s(c.dh.rcode) + extra)).str()); - } - else { - out.emplace(c.when, (fmt % DiffTime(now, c.when) % c.requestor.toStringWithPort() % protocol % server % htons(c.dh.id) % c.name.toString() % qt.toString() % "T.O" % (c.dh.tc ? "TC" : "") % (c.dh.rd ? "RD" : "") % (c.dh.aa ? "AA" : "") % (RCode::to_s(c.dh.rcode) + extra)).str()); - } - - if (limit && *limit == ++num) { - break; - } - } - } - - for (const auto& p : out) { - if (!outputFile) { - g_outputBuffer += p.second; - } - else { - fprintf(outputFile.get(), "%s", p.second.c_str()); - } - } - }); - - luaCtx.writeFunction("showResponseLatency", []() { - setLuaNoSideEffect(); - map histo; - double bin=100; - for(int i=0; i < 15; ++i) { - histo[bin]; - bin*=2; - } - - double totlat=0; - unsigned int size=0; - { - for (const auto& shard : g_rings.d_shards) { - auto rl = shard->respRing.lock(); - for(const auto& r : *rl) { - /* skip actively discovered timeouts */ - if (r.usec == std::numeric_limits::max()) - continue; - - ++size; - auto iter = histo.lower_bound(r.usec); - if(iter != histo.end()) - iter->second++; - else - histo.rbegin()++; - totlat+=r.usec; - } - } - } - - if (size == 0) { - g_outputBuffer = "No traffic yet.\n"; - return; - } - - g_outputBuffer = (boost::format("Average response latency: %.02f ms\n") % (0.001*totlat/size)).str(); - double highest=0; - - for(auto iter = histo.cbegin(); iter != histo.cend(); ++iter) { - highest=std::max(highest, iter->second*1.0); - } - boost::format fmt("%7.2f\t%s\n"); - g_outputBuffer += (fmt % "ms" % "").str(); - - for(auto iter = histo.cbegin(); iter != histo.cend(); ++iter) { - int stars = (70.0 * iter->second/highest); - char c='*'; - if(!stars && iter->second) { - stars=1; // you get 1 . to show something is there.. - if(70.0*iter->second/highest > 0.5) - c=':'; - else - c='.'; - } - g_outputBuffer += (fmt % (iter->first/1000.0) % string(stars, c)).str(); - } - }); - - luaCtx.writeFunction("showTCPStats", [] { - setLuaNoSideEffect(); - ostringstream ret; - boost::format fmt("%-12d %-12d %-12d %-12d"); - ret << (fmt % "Workers" % "Max Workers" % "Queued" % "Max Queued") << endl; - ret << (fmt % g_tcpclientthreads->getThreadsCount() % (g_maxTCPClientThreads ? *g_maxTCPClientThreads : 0) % g_tcpclientthreads->getQueuedCount() % g_maxTCPQueuedConnections) << endl; - ret << endl; - - ret << "Frontends:" << endl; - fmt = boost::format("%-3d %-20.20s %-20d %-20d %-20d %-25d %-20d %-20d %-20d %-20f %-20f %-20d %-20d %-25d %-25d %-15d %-15d %-15d %-15d %-15d"); - ret << (fmt % "#" % "Address" % "Connections" % "Max concurrent conn" % "Died reading query" % "Died sending response" % "Gave up" % "Client timeouts" % "Downstream timeouts" % "Avg queries/conn" % "Avg duration" % "TLS new sessions" % "TLS Resumptions" % "TLS unknown ticket keys" % "TLS inactive ticket keys" % "TLS 1.0" % "TLS 1.1" % "TLS 1.2" % "TLS 1.3" % "TLS other") << endl; - - size_t counter = 0; - for(const auto& f : g_frontends) { - ret << (fmt % counter % f->local.toStringWithPort() % f->tcpCurrentConnections % f->tcpMaxConcurrentConnections % f->tcpDiedReadingQuery % f->tcpDiedSendingResponse % f->tcpGaveUp % f->tcpClientTimeouts % f->tcpDownstreamTimeouts % f->tcpAvgQueriesPerConnection % f->tcpAvgConnectionDuration % f->tlsNewSessions % f->tlsResumptions % f->tlsUnknownTicketKey % f->tlsInactiveTicketKey % f->tls10queries % f->tls11queries % f->tls12queries % f->tls13queries % f->tlsUnknownqueries) << endl; - ++counter; - } - ret << endl; - - ret << "Backends:" << endl; - fmt = boost::format("%-3d %-20.20s %-20.20s %-20d %-20d %-25d %-25d %-20d %-20d %-20d %-20d %-20d %-20d %-20d %-20d %-20f %-20f"); - ret << (fmt % "#" % "Name" % "Address" % "Connections" % "Max concurrent conn" % "Died sending query" % "Died reading response" % "Gave up" % "Read timeouts" % "Write timeouts" % "Connect timeouts" % "Too many conn" % "Total connections" % "Reused connections" % "TLS resumptions" % "Avg queries/conn" % "Avg duration") << endl; - - auto states = g_dstates.getLocal(); - counter = 0; - for(const auto& s : *states) { - ret << (fmt % counter % s->getName() % s->d_config.remote.toStringWithPort() % s->tcpCurrentConnections % s->tcpMaxConcurrentConnections % s->tcpDiedSendingQuery % s->tcpDiedReadingResponse % s->tcpGaveUp % s->tcpReadTimeouts % s->tcpWriteTimeouts % s->tcpConnectTimeouts % s->tcpTooManyConcurrentConnections % s->tcpNewConnections % s->tcpReusedConnections % s->tlsResumptions % s->tcpAvgQueriesPerConnection % s->tcpAvgConnectionDuration) << endl; - ++counter; - } - - g_outputBuffer=ret.str(); - }); - - luaCtx.writeFunction("showTLSErrorCounters", [] { - setLuaNoSideEffect(); - ostringstream ret; - boost::format fmt("%-3d %-20.20s %-23d %-23d %-23d %-23d %-23d %-23d %-23d %-23d"); - - ret << (fmt % "#" % "Address" % "DH key too small" % "Inappropriate fallback" % "No shared cipher" % "Unknown cipher type" % "Unknown exchange type" % "Unknown protocol" % "Unsupported EC" % "Unsupported protocol") << endl; - - size_t counter = 0; - for(const auto& f : g_frontends) { - if (!f->hasTLS()) { - continue; - } - const TLSErrorCounters* errorCounters = nullptr; - if (f->tlsFrontend != nullptr) { - errorCounters = &f->tlsFrontend->d_tlsCounters; - } - else if (f->dohFrontend != nullptr) { - errorCounters = &f->dohFrontend->d_tlsContext.d_tlsCounters; - } - if (errorCounters == nullptr) { - continue; - } - - ret << (fmt % counter % f->local.toStringWithPort() % errorCounters->d_dhKeyTooSmall % errorCounters->d_inappropriateFallBack % errorCounters->d_noSharedCipher % errorCounters->d_unknownCipherType % errorCounters->d_unknownKeyExchangeType % errorCounters->d_unknownProtocol % errorCounters->d_unsupportedEC % errorCounters->d_unsupportedProtocol) << endl; - ++counter; - } - ret << endl; - - g_outputBuffer=ret.str(); - }); - - luaCtx.writeFunction("requestTCPStatesDump", [] { - setLuaNoSideEffect(); - extern std::atomic g_tcpStatesDumpRequested; - g_tcpStatesDumpRequested += g_tcpclientthreads->getThreadsCount(); - }); - - luaCtx.writeFunction("requestDoHStatesDump", [] { - setLuaNoSideEffect(); -#if defined(HAVE_DNS_OVER_HTTPS) && defined(HAVE_NGHTTP2) - g_dohStatesDumpRequested += g_dohClientThreads->getThreadsCount(); -#endif - }); - - luaCtx.writeFunction("dumpStats", [] { - setLuaNoSideEffect(); - vector leftcolumn, rightcolumn; - - boost::format fmt("%-35s\t%+11s"); - g_outputBuffer.clear(); - auto entries = *dnsdist::metrics::g_stats.entries.read_lock(); - sort(entries.begin(), entries.end(), - [](const decltype(entries)::value_type& a, const decltype(entries)::value_type& b) { - return a.d_name < b.d_name; - }); - boost::format flt(" %9.1f"); - for (const auto& entry : entries) { - string second; - if (const auto& val = std::get_if(&entry.d_value)) { - second = std::to_string((*val)->load()); - } - else if (const auto& adval = std::get_if*>(&entry.d_value)) { - second = (flt % (*adval)->load()).str(); - } - else if (const auto& dval = std::get_if(&entry.d_value)) { - second = (flt % (**dval)).str(); - } - else if (const auto& func = std::get_if(&entry.d_value)) { - second = std::to_string((*func)(entry.d_name)); - } - - if (leftcolumn.size() < entries.size() / 2) { - leftcolumn.push_back((fmt % entry.d_name % second).str()); - } - else { - rightcolumn.push_back((fmt % entry.d_name % second).str()); - } - } - - auto leftiter=leftcolumn.begin(), rightiter=rightcolumn.begin(); - boost::format clmn("%|0t|%1% %|51t|%2%\n"); - - for(;leftiter != leftcolumn.end() || rightiter != rightcolumn.end();) { - string lentry, rentry; - if(leftiter!= leftcolumn.end()) { - lentry = *leftiter; - leftiter++; - } - if(rightiter!= rightcolumn.end()) { - rentry = *rightiter; - rightiter++; - } - g_outputBuffer += (clmn % lentry % rentry).str(); - } - }); - -#ifndef DISABLE_DYNBLOCKS -#ifndef DISABLE_DEPRECATED_DYNBLOCK - luaCtx.writeFunction("exceedServFails", [](unsigned int rate, int seconds) { - setLuaNoSideEffect(); - return exceedRCode(rate, seconds, RCode::ServFail); - }); - luaCtx.writeFunction("exceedNXDOMAINs", [](unsigned int rate, int seconds) { - setLuaNoSideEffect(); - return exceedRCode(rate, seconds, RCode::NXDomain); - }); - - luaCtx.writeFunction("exceedRespByterate", [](unsigned int rate, int seconds) { - setLuaNoSideEffect(); - return exceedRespByterate(rate, seconds); - }); - - luaCtx.writeFunction("exceedQTypeRate", [](uint16_t type, unsigned int rate, int seconds) { - setLuaNoSideEffect(); - return exceedQueryGen(rate, seconds, [type](counts_t& counts, const Rings::Query& q) { - if(q.qtype==type) - counts[q.requestor]++; - }); - }); - - luaCtx.writeFunction("exceedQRate", [](unsigned int rate, int seconds) { - setLuaNoSideEffect(); - return exceedQueryGen(rate, seconds, [](counts_t& counts, const Rings::Query& q) { - counts[q.requestor]++; - }); - }); - - luaCtx.writeFunction("getRespRing", getRespRing); - - /* StatNode */ - luaCtx.registerFunction("numChildren", - [](const StatNode& sn) -> unsigned int { - return sn.children.size(); - } ); - luaCtx.registerMember("fullname", &StatNode::fullname); - luaCtx.registerMember("labelsCount", &StatNode::labelsCount); - luaCtx.registerMember("servfails", &StatNode::Stat::servfails); - luaCtx.registerMember("nxdomains", &StatNode::Stat::nxdomains); - luaCtx.registerMember("queries", &StatNode::Stat::queries); - luaCtx.registerMember("noerrors", &StatNode::Stat::noerrors); - luaCtx.registerMember("drops", &StatNode::Stat::drops); - luaCtx.registerMember("bytes", &StatNode::Stat::bytes); - luaCtx.registerMember("hits", &StatNode::Stat::hits); - - luaCtx.writeFunction("statNodeRespRing", [](statvisitor_t visitor, boost::optional seconds) { - statNodeRespRing(std::move(visitor), seconds ? *seconds : 0U); - }); -#endif /* DISABLE_DEPRECATED_DYNBLOCK */ - - /* DynBlockRulesGroup */ - luaCtx.writeFunction("dynBlockRulesGroup", []() { return std::make_shared(); }); - luaCtx.registerFunction::*)(unsigned int, unsigned int, const std::string&, unsigned int, boost::optional, boost::optional)>("setQueryRate", [](std::shared_ptr& group, unsigned int rate, unsigned int seconds, const std::string& reason, unsigned int blockDuration, boost::optional action, boost::optional warningRate) { - if (group) { - group->setQueryRate(rate, warningRate ? *warningRate : 0, seconds, reason, blockDuration, action ? *action : DNSAction::Action::None); - } - }); - luaCtx.registerFunction::*)(unsigned int, unsigned int, const std::string&, unsigned int, boost::optional, boost::optional)>("setResponseByteRate", [](std::shared_ptr& group, unsigned int rate, unsigned int seconds, const std::string& reason, unsigned int blockDuration, boost::optional action, boost::optional warningRate) { - if (group) { - group->setResponseByteRate(rate, warningRate ? *warningRate : 0, seconds, reason, blockDuration, action ? *action : DNSAction::Action::None); - } - }); - luaCtx.registerFunction::*)(unsigned int, const std::string&, unsigned int, boost::optional, DynBlockRulesGroup::smtVisitor_t)>("setSuffixMatchRule", [](std::shared_ptr& group, unsigned int seconds, const std::string& reason, unsigned int blockDuration, boost::optional action, DynBlockRulesGroup::smtVisitor_t visitor) { - if (group) { - group->setSuffixMatchRule(seconds, reason, blockDuration, action ? *action : DNSAction::Action::None, std::move(visitor)); - } - }); - luaCtx.registerFunction::*)(unsigned int, const std::string&, unsigned int, boost::optional, dnsdist_ffi_stat_node_visitor_t)>("setSuffixMatchRuleFFI", [](std::shared_ptr& group, unsigned int seconds, const std::string& reason, unsigned int blockDuration, boost::optional action, dnsdist_ffi_stat_node_visitor_t visitor) { - if (group) { - group->setSuffixMatchRuleFFI(seconds, reason, blockDuration, action ? *action : DNSAction::Action::None, std::move(visitor)); - } - }); - luaCtx.registerFunction::*)(const dnsdist_ffi_dynamic_block_inserted_hook&)>("setNewBlockInsertedHook", [](std::shared_ptr& group, const dnsdist_ffi_dynamic_block_inserted_hook& hook) { - if (group) { - group->setNewBlockHook(hook); - } - }); - luaCtx.registerFunction::*)(uint8_t, unsigned int, unsigned int, const std::string&, unsigned int, boost::optional, boost::optional)>("setRCodeRate", [](std::shared_ptr& group, uint8_t rcode, unsigned int rate, unsigned int seconds, const std::string& reason, unsigned int blockDuration, boost::optional action, boost::optional warningRate) { - if (group) { - group->setRCodeRate(rcode, rate, warningRate ? *warningRate : 0, seconds, reason, blockDuration, action ? *action : DNSAction::Action::None); - } - }); - luaCtx.registerFunction::*)(uint8_t, double, unsigned int, const std::string&, unsigned int, size_t, boost::optional, boost::optional)>("setRCodeRatio", [](std::shared_ptr& group, uint8_t rcode, double ratio, unsigned int seconds, const std::string& reason, unsigned int blockDuration, size_t minimumNumberOfResponses, boost::optional action, boost::optional warningRatio) { - if (group) { - group->setRCodeRatio(rcode, ratio, warningRatio ? *warningRatio : 0.0, seconds, reason, blockDuration, action ? *action : DNSAction::Action::None, minimumNumberOfResponses); - } - }); - luaCtx.registerFunction::*)(uint16_t, unsigned int, unsigned int, const std::string&, unsigned int, boost::optional, boost::optional)>("setQTypeRate", [](std::shared_ptr& group, uint16_t qtype, unsigned int rate, unsigned int seconds, const std::string& reason, unsigned int blockDuration, boost::optional action, boost::optional warningRate) { - if (group) { - group->setQTypeRate(qtype, rate, warningRate ? *warningRate : 0, seconds, reason, blockDuration, action ? *action : DNSAction::Action::None); - } - }); - luaCtx.registerFunction::*)(double, unsigned int, const std::string&, unsigned int, size_t, double, boost::optional, boost::optional)>("setCacheMissRatio", [](std::shared_ptr& group, double ratio, unsigned int seconds, const std::string& reason, unsigned int blockDuration, size_t minimumNumberOfResponses, double minimumGlobalCacheHitRatio, boost::optional action, boost::optional warningRatio) { - if (group) { - group->setCacheMissRatio(ratio, warningRatio ? *warningRatio : 0.0, seconds, reason, blockDuration, action ? *action : DNSAction::Action::None, minimumNumberOfResponses, minimumGlobalCacheHitRatio); - } - }); - luaCtx.registerFunction::*)(uint8_t, uint8_t, uint8_t)>("setMasks", [](std::shared_ptr& group, uint8_t v4, uint8_t v6, uint8_t port) { - if (group) { - if (v4 > 32) { - throw std::runtime_error("Trying to set an invalid IPv4 mask (" + std::to_string(v4) + ") to a Dynamic Block object"); - } - if (v6 > 128) { - throw std::runtime_error("Trying to set an invalid IPv6 mask (" + std::to_string(v6) + ") to a Dynamic Block object"); - } - if (port > 16) { - throw std::runtime_error("Trying to set an invalid port mask (" + std::to_string(port) + ") to a Dynamic Block object"); - } - if (port > 0 && v4 != 32) { - throw std::runtime_error("Setting a non-zero port mask for Dynamic Blocks while only considering parts of IPv4 addresses does not make sense"); - } - group->setMasks(v4, v6, port); - } - }); - luaCtx.registerFunction::*)(boost::variant, NetmaskGroup>)>("excludeRange", [](std::shared_ptr& group, boost::variant, NetmaskGroup> ranges) { - if (ranges.type() == typeid(LuaArray)) { - for (const auto& range : *boost::get>(&ranges)) { - group->excludeRange(Netmask(range.second)); - } - } - else if (ranges.type() == typeid(NetmaskGroup)) { - group->excludeRange(*boost::get(&ranges)); - } - else { - group->excludeRange(Netmask(*boost::get(&ranges))); - } - }); - luaCtx.registerFunction::*)(boost::variant, NetmaskGroup>)>("includeRange", [](std::shared_ptr& group, boost::variant, NetmaskGroup> ranges) { - if (ranges.type() == typeid(LuaArray)) { - for (const auto& range : *boost::get>(&ranges)) { - group->includeRange(Netmask(range.second)); - } - } - else if (ranges.type() == typeid(NetmaskGroup)) { - group->includeRange(*boost::get(&ranges)); - } - else { - group->includeRange(Netmask(*boost::get(&ranges))); - } - }); - luaCtx.registerFunction::*)(boost::variant, NetmaskGroup>)>("removeRange", [](std::shared_ptr& group, boost::variant, NetmaskGroup> ranges) { - if (ranges.type() == typeid(LuaArray)) { - for (const auto& range : *boost::get>(&ranges)) { - group->removeRange(Netmask(range.second)); - } - } - else if (ranges.type() == typeid(NetmaskGroup)) { - group->removeRange(*boost::get(&ranges)); - } - else { - group->removeRange(Netmask(*boost::get(&ranges))); - } - }); - luaCtx.registerFunction::*)(LuaTypeOrArrayOf)>("excludeDomains", [](std::shared_ptr& group, LuaTypeOrArrayOf domains) { - if (domains.type() == typeid(LuaArray)) { - for (const auto& range : *boost::get>(&domains)) { - group->excludeDomain(DNSName(range.second)); - } - } - else { - group->excludeDomain(DNSName(*boost::get(&domains))); - } - }); - luaCtx.registerFunction::*)()>("apply", [](std::shared_ptr& group) { - group->apply(); - }); - luaCtx.registerFunction("setQuiet", &DynBlockRulesGroup::setQuiet); - luaCtx.registerFunction("toString", &DynBlockRulesGroup::toString); - - /* DynBlock object accessors */ - luaCtx.registerMember("reason", &DynBlock::reason); - luaCtx.registerMember("domain", &DynBlock::domain); - luaCtx.registerMember("until", &DynBlock::until); - luaCtx.registerMember("blocks", [](const DynBlock& block) { return block.blocks.load(); }, [](DynBlock& block, [[maybe_unused]] unsigned int blocks) { }); - luaCtx.registerMember("action", &DynBlock::action); - luaCtx.registerMember("warning", &DynBlock::warning); - luaCtx.registerMember("bpf", &DynBlock::bpf); -#endif /* DISABLE_DYNBLOCKS */ -} diff --git a/pdns/dnsdist-lua-rules.cc b/pdns/dnsdist-lua-rules.cc deleted file mode 100644 index f53b98be3932..000000000000 --- a/pdns/dnsdist-lua-rules.cc +++ /dev/null @@ -1,798 +0,0 @@ -/* - * This file is part of PowerDNS or dnsdist. - * Copyright -- PowerDNS.COM B.V. and its contributors - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of version 2 of the GNU General Public License as - * published by the Free Software Foundation. - * - * In addition, for the avoidance of any doubt, permission is granted to - * link this program with OpenSSL and to (re)distribute the binaries - * produced as the result of such linking. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ -#include "dnsdist.hh" -#include "dnsdist-lua.hh" -#include "dnsdist-rules.hh" -#include "dns_random.hh" - -std::shared_ptr makeRule(const luadnsrule_t& var, const std::string& calledFrom) -{ - if (var.type() == typeid(std::shared_ptr)) { - return *boost::get>(&var); - } - - bool suffixSeen = false; - SuffixMatchNode smn; - NetmaskGroup nmg; - auto add = [&nmg, &smn, &suffixSeen](const string& src) { - try { - nmg.addMask(src); // need to try mask first, all masks are domain names! - } catch (...) { - suffixSeen = true; - smn.add(DNSName(src)); - } - }; - - if (var.type() == typeid(string)) { - add(*boost::get(&var)); - } - else if (var.type() == typeid(LuaArray)) { - for (const auto& str : *boost::get>(&var)) { - add(str.second); - } - } - else if (var.type() == typeid(DNSName)) { - smn.add(*boost::get(&var)); - } - else if (var.type() == typeid(LuaArray)) { - smn = SuffixMatchNode(); - for (const auto& name : *boost::get>(&var)) { - smn.add(name.second); - } - } - - if (nmg.empty()) { - return std::make_shared(smn); - } - if (suffixSeen) { - warnlog("At least one parameter to %s has been parsed as a domain name amongst network masks, and will be ignored!", calledFrom); - } - return std::make_shared(nmg, true); -} - -static boost::uuids::uuid makeRuleID(std::string& id) -{ - if (id.empty()) { - return getUniqueID(); - } - - return getUniqueID(id); -} - -void parseRuleParams(boost::optional& params, boost::uuids::uuid& uuid, std::string& name, uint64_t& creationOrder) -{ - static uint64_t s_creationOrder = 0; - - string uuidStr; - - getOptionalValue(params, "uuid", uuidStr); - getOptionalValue(params, "name", name); - - uuid = makeRuleID(uuidStr); - creationOrder = s_creationOrder++; -} - -typedef LuaAssociativeTable > > ruleparams_t; - -template -static std::string rulesToString(const std::vector& rules, boost::optional& vars) -{ - int num = 0; - bool showUUIDs = false; - size_t truncateRuleWidth = string::npos; - std::string result; - - getOptionalValue(vars, "showUUIDs", showUUIDs); - getOptionalValue(vars, "truncateRuleWidth", truncateRuleWidth); - checkAllParametersConsumed("rulesToString", vars); - - if (showUUIDs) { - boost::format fmt("%-3d %-30s %-38s %9d %9d %-56s %s\n"); - result += (fmt % "#" % "Name" % "UUID" % "Cr. Order" % "Matches" % "Rule" % "Action").str(); - for(const auto& lim : rules) { - string desc = lim.d_rule->toString().substr(0, truncateRuleWidth); - result += (fmt % num % lim.d_name % boost::uuids::to_string(lim.d_id) % lim.d_creationOrder % lim.d_rule->d_matches % desc % lim.d_action->toString()).str(); - ++num; - } - } - else { - boost::format fmt("%-3d %-30s %9d %-56s %s\n"); - result += (fmt % "#" % "Name" % "Matches" % "Rule" % "Action").str(); - for(const auto& lim : rules) { - string desc = lim.d_rule->toString().substr(0, truncateRuleWidth); - result += (fmt % num % lim.d_name % lim.d_rule->d_matches % desc % lim.d_action->toString()).str(); - ++num; - } - } - return result; -} - -template -static void showRules(GlobalStateHolder > *someRuleActions, boost::optional& vars) { - setLuaNoSideEffect(); - - auto rules = someRuleActions->getLocal(); - g_outputBuffer += rulesToString(*rules, vars); -} - -template -static void rmRule(GlobalStateHolder > *someRuleActions, const boost::variant& id) { - setLuaSideEffect(); - auto rules = someRuleActions->getCopy(); - if (auto str = boost::get(&id)) { - try { - const auto uuid = getUniqueID(*str); - auto removeIt = std::remove_if(rules.begin(), - rules.end(), - [&uuid](const T& rule) { return rule.d_id == uuid; }); - if (removeIt == rules.end()) { - g_outputBuffer = "Error: no rule matched\n"; - return; - } - rules.erase(removeIt, - rules.end()); - } - catch (const std::runtime_error& e) { - /* it was not an UUID, let's see if it was a name instead */ - auto removeIt = std::remove_if(rules.begin(), - rules.end(), - [&str](const T& rule) { return rule.d_name == *str; }); - if (removeIt == rules.end()) { - g_outputBuffer = "Error: no rule matched\n"; - return; - } - rules.erase(removeIt, - rules.end()); - } - } - else if (auto pos = boost::get(&id)) { - if (*pos >= rules.size()) { - g_outputBuffer = "Error: attempt to delete non-existing rule\n"; - return; - } - rules.erase(rules.begin()+*pos); - } - someRuleActions->setState(std::move(rules)); -} - -template -static void moveRuleToTop(GlobalStateHolder > *someRuleActions) { - setLuaSideEffect(); - auto rules = someRuleActions->getCopy(); - if(rules.empty()) - return; - auto subject = *rules.rbegin(); - rules.erase(std::prev(rules.end())); - rules.insert(rules.begin(), subject); - someRuleActions->setState(std::move(rules)); -} - -template -static void mvRule(GlobalStateHolder > *someRespRuleActions, unsigned int from, unsigned int to) { - setLuaSideEffect(); - auto rules = someRespRuleActions->getCopy(); - if(from >= rules.size() || to > rules.size()) { - g_outputBuffer = "Error: attempt to move rules from/to invalid index\n"; - return; - } - auto subject = rules[from]; - rules.erase(rules.begin()+from); - if(to > rules.size()) - rules.push_back(subject); - else { - if(from < to) - --to; - rules.insert(rules.begin()+to, subject); - } - someRespRuleActions->setState(std::move(rules)); -} - -template -static std::vector getTopRules(const std::vector& rules, unsigned int top) -{ - std::vector> counts; - counts.reserve(rules.size()); - - size_t pos = 0; - for (const auto& rule : rules) { - counts.push_back({rule.d_rule->d_matches.load(), pos}); - pos++; - } - - sort(counts.begin(), counts.end(), [](const decltype(counts)::value_type& a, - const decltype(counts)::value_type& b) { - return b.first < a.first; - }); - - std::vector results; - results.reserve(top); - - size_t count = 0; - for (const auto& entry : counts) { - results.emplace_back(rules.at(entry.second)); - ++count; - if (count == top) { - break; - } - } - - return results; -} - -template -static LuaArray toLuaArray(std::vector&& rules) -{ - LuaArray results; - results.reserve(rules.size()); - - size_t pos = 1; - for (auto& rule : rules) { - results.emplace_back(pos, std::move(rule)); - pos++; - } - - return results; -} - -template -static boost::optional getRuleFromSelector(const std::vector& rules, const boost::variant& selector) -{ - if (auto str = boost::get(&selector)) { - /* let's see if this a UUID */ - try { - const auto uuid = getUniqueID(*str); - for (const auto& rule : rules) { - if (rule.d_id == uuid) { - return rule; - } - } - } - catch (const std::exception& e) { - /* a name, then */ - for (const auto& rule : rules) { - if (rule.d_name == *str) { - return rule; - } - } - } - } - else if (auto pos = boost::get(&selector)) { - /* this will throw a std::out_of_range exception if the - supplied position is out of bounds, this is fine */ - return rules.at(*pos); - } - return boost::none; -} - -namespace -{ -std::shared_ptr qnameSuffixRule(const boost::variant> names, boost::optional quiet) -{ - if (names.type() == typeid(string)) { - SuffixMatchNode smn; - smn.add(DNSName(*boost::get(&names))); - return std::shared_ptr(new SuffixMatchNodeRule(smn, quiet ? *quiet : false)); - } - - if (names.type() == typeid(LuaArray)) { - SuffixMatchNode smn; - for (const auto& str : *boost::get>(&names)) { - smn.add(DNSName(str.second)); - } - return std::shared_ptr(new SuffixMatchNodeRule(smn, quiet ? *quiet : false)); - } - - const auto& smn = *boost::get(&names); - return std::shared_ptr(new SuffixMatchNodeRule(smn, quiet ? *quiet : false)); -} -} - -// NOLINTNEXTLINE(readability-function-cognitive-complexity): this function declares Lua bindings, even with a good refactoring it will likely blow up the threshold -void setupLuaRules(LuaContext& luaCtx) -{ - luaCtx.writeFunction("makeRule", [](const luadnsrule_t& var) -> std::shared_ptr { - return makeRule(var, "makeRule"); - }); - - luaCtx.registerFunction::*)()const>("toString", [](const std::shared_ptr& rule) { return rule->toString(); }); - - luaCtx.registerFunction::*)()const>("getMatches", [](const std::shared_ptr& rule) { return rule->d_matches.load(); }); - - luaCtx.registerFunction(DNSDistRuleAction::*)()const>("getSelector", [](const DNSDistRuleAction& rule) { return rule.d_rule; }); - - luaCtx.registerFunction(DNSDistRuleAction::*)()const>("getAction", [](const DNSDistRuleAction& rule) { return rule.d_action; }); - - luaCtx.registerFunction(DNSDistResponseRuleAction::*)()const>("getSelector", [](const DNSDistResponseRuleAction& rule) { return rule.d_rule; }); - - luaCtx.registerFunction(DNSDistResponseRuleAction::*)()const>("getAction", [](const DNSDistResponseRuleAction& rule) { return rule.d_action; }); - - luaCtx.writeFunction("showResponseRules", [](boost::optional vars) { - showRules(&g_respruleactions, vars); - }); - - luaCtx.writeFunction("rmResponseRule", [](boost::variant id) { - rmRule(&g_respruleactions, id); - }); - - luaCtx.writeFunction("mvResponseRuleToTop", []() { - moveRuleToTop(&g_respruleactions); - }); - - luaCtx.writeFunction("mvResponseRule", [](unsigned int from, unsigned int to) { - mvRule(&g_respruleactions, from, to); - }); - - luaCtx.writeFunction("showCacheHitResponseRules", [](boost::optional vars) { - showRules(&g_cachehitrespruleactions, vars); - }); - - luaCtx.writeFunction("rmCacheHitResponseRule", [](boost::variant id) { - rmRule(&g_cachehitrespruleactions, id); - }); - - luaCtx.writeFunction("mvCacheHitResponseRuleToTop", []() { - moveRuleToTop(&g_cachehitrespruleactions); - }); - - luaCtx.writeFunction("mvCacheHitResponseRule", [](unsigned int from, unsigned int to) { - mvRule(&g_cachehitrespruleactions, from, to); - }); - - luaCtx.writeFunction("showCacheInsertedResponseRules", [](boost::optional vars) { - showRules(&g_cacheInsertedRespRuleActions, vars); - }); - - luaCtx.writeFunction("rmCacheInsertedResponseRule", [](boost::variant id) { - rmRule(&g_cacheInsertedRespRuleActions, id); - }); - - luaCtx.writeFunction("mvCacheInsertedResponseRuleToTop", []() { - moveRuleToTop(&g_cacheInsertedRespRuleActions); - }); - - luaCtx.writeFunction("mvCacheInsertedResponseRule", [](unsigned int from, unsigned int to) { - mvRule(&g_cacheInsertedRespRuleActions, from, to); - }); - - luaCtx.writeFunction("showSelfAnsweredResponseRules", [](boost::optional vars) { - showRules(&g_selfansweredrespruleactions, vars); - }); - - luaCtx.writeFunction("rmSelfAnsweredResponseRule", [](boost::variant id) { - rmRule(&g_selfansweredrespruleactions, id); - }); - - luaCtx.writeFunction("mvSelfAnsweredResponseRuleToTop", []() { - moveRuleToTop(&g_selfansweredrespruleactions); - }); - - luaCtx.writeFunction("mvSelfAnsweredResponseRule", [](unsigned int from, unsigned int to) { - mvRule(&g_selfansweredrespruleactions, from, to); - }); - - luaCtx.writeFunction("rmRule", [](boost::variant id) { - rmRule(&g_ruleactions, id); - }); - - luaCtx.writeFunction("mvRuleToTop", []() { - moveRuleToTop(&g_ruleactions); - }); - - luaCtx.writeFunction("mvRule", [](unsigned int from, unsigned int to) { - mvRule(&g_ruleactions, from, to); - }); - - luaCtx.writeFunction("clearRules", []() { - setLuaSideEffect(); - g_ruleactions.modify([](decltype(g_ruleactions)::value_type& ruleactions) { - ruleactions.clear(); - }); - }); - - luaCtx.writeFunction("setRules", [](const LuaArray>& newruleactions) { - setLuaSideEffect(); - g_ruleactions.modify([newruleactions](decltype(g_ruleactions)::value_type& gruleactions) { - gruleactions.clear(); - for (const auto& pair : newruleactions) { - const auto& newruleaction = pair.second; - if (newruleaction->d_action) { - auto rule = newruleaction->d_rule; - gruleactions.push_back({std::move(rule), newruleaction->d_action, newruleaction->d_name, newruleaction->d_id, newruleaction->d_creationOrder}); - } - } - }); - }); - - luaCtx.writeFunction("getRule", [](boost::variant selector) -> boost::optional { - auto rules = g_ruleactions.getLocal(); - return getRuleFromSelector(*rules, selector); - }); - - luaCtx.writeFunction("getTopRules", [](boost::optional top) { - setLuaNoSideEffect(); - auto rules = g_ruleactions.getLocal(); - return toLuaArray(getTopRules(*rules, (top ? *top : 10))); - }); - - luaCtx.writeFunction("topRules", [](boost::optional top, boost::optional vars) { - setLuaNoSideEffect(); - auto rules = g_ruleactions.getLocal(); - return rulesToString(getTopRules(*rules, (top ? *top : 10)), vars); - }); - - luaCtx.writeFunction("getCacheHitResponseRule", [](boost::variant selector) -> boost::optional { - auto rules = g_cachehitrespruleactions.getLocal(); - return getRuleFromSelector(*rules, selector); - }); - - luaCtx.writeFunction("getTopCacheHitResponseRules", [](boost::optional top) { - setLuaNoSideEffect(); - auto rules = g_cachehitrespruleactions.getLocal(); - return toLuaArray(getTopRules(*rules, (top ? *top : 10))); - }); - - luaCtx.writeFunction("topCacheHitResponseRules", [](boost::optional top, boost::optional vars) { - setLuaNoSideEffect(); - auto rules = g_cachehitrespruleactions.getLocal(); - return rulesToString(getTopRules(*rules, (top ? *top : 10)), vars); - }); - - luaCtx.writeFunction("getCacheInsertedResponseRule", [](boost::variant selector) -> boost::optional { - auto rules = g_cacheInsertedRespRuleActions.getLocal(); - return getRuleFromSelector(*rules, selector); - }); - - luaCtx.writeFunction("getTopCacheInsertedResponseRules", [](boost::optional top) { - setLuaNoSideEffect(); - auto rules = g_cacheInsertedRespRuleActions.getLocal(); - return toLuaArray(getTopRules(*rules, (top ? *top : 10))); - }); - - luaCtx.writeFunction("topCacheInsertedResponseRules", [](boost::optional top, boost::optional vars) { - setLuaNoSideEffect(); - auto rules = g_cacheInsertedRespRuleActions.getLocal(); - return rulesToString(getTopRules(*rules, (top ? *top : 10)), vars); - }); - - luaCtx.writeFunction("getResponseRule", [](boost::variant selector) -> boost::optional { - auto rules = g_respruleactions.getLocal(); - return getRuleFromSelector(*rules, selector); - }); - - luaCtx.writeFunction("getTopResponseRules", [](boost::optional top) { - setLuaNoSideEffect(); - auto rules = g_respruleactions.getLocal(); - return toLuaArray(getTopRules(*rules, (top ? *top : 10))); - }); - - luaCtx.writeFunction("topResponseRules", [](boost::optional top, boost::optional vars) { - setLuaNoSideEffect(); - auto rules = g_respruleactions.getLocal(); - return rulesToString(getTopRules(*rules, (top ? *top : 10)), vars); - }); - - luaCtx.writeFunction("getSelfAnsweredResponseRule", [](boost::variant selector) -> boost::optional { - auto rules = g_selfansweredrespruleactions.getLocal(); - return getRuleFromSelector(*rules, selector); - }); - - luaCtx.writeFunction("getTopSelfAnsweredResponseRules", [](boost::optional top) { - setLuaNoSideEffect(); - auto rules = g_selfansweredrespruleactions.getLocal(); - return toLuaArray(getTopRules(*rules, (top ? *top : 10))); - }); - - luaCtx.writeFunction("topSelfAnsweredResponseRules", [](boost::optional top, boost::optional vars) { - setLuaNoSideEffect(); - auto rules = g_selfansweredrespruleactions.getLocal(); - return rulesToString(getTopRules(*rules, (top ? *top : 10)), vars); - }); - - luaCtx.writeFunction("MaxQPSIPRule", [](unsigned int qps, boost::optional ipv4trunc, boost::optional ipv6trunc, boost::optional burst, boost::optional expiration, boost::optional cleanupDelay, boost::optional scanFraction, boost::optional shards) { - return std::shared_ptr(new MaxQPSIPRule(qps, (burst ? *burst : qps), (ipv4trunc ? *ipv4trunc : 32), (ipv6trunc ? *ipv6trunc : 64), (expiration ? *expiration : 300), (cleanupDelay ? *cleanupDelay : 60), (scanFraction ? *scanFraction : 10), (shards ? *shards : 10))); - }); - - luaCtx.writeFunction("MaxQPSRule", [](unsigned int qps, boost::optional burst) { - if(!burst) - return std::shared_ptr(new MaxQPSRule(qps)); - else - return std::shared_ptr(new MaxQPSRule(qps, *burst)); - }); - - luaCtx.writeFunction("RegexRule", [](const std::string& str) { - return std::shared_ptr(new RegexRule(str)); - }); - -#ifdef HAVE_DNS_OVER_HTTPS - luaCtx.writeFunction("HTTPHeaderRule", [](const std::string& header, const std::string& regex) { - return std::shared_ptr(new HTTPHeaderRule(header, regex)); - }); - luaCtx.writeFunction("HTTPPathRule", [](const std::string& path) { - return std::shared_ptr(new HTTPPathRule(path)); - }); - luaCtx.writeFunction("HTTPPathRegexRule", [](const std::string& regex) { - return std::shared_ptr(new HTTPPathRegexRule(regex)); - }); -#endif - -#ifdef HAVE_RE2 - luaCtx.writeFunction("RE2Rule", [](const std::string& str) { - return std::shared_ptr(new RE2Rule(str)); - }); -#endif - - luaCtx.writeFunction("SNIRule", [](const std::string& name) { - return std::shared_ptr(new SNIRule(name)); - }); - - luaCtx.writeFunction("SuffixMatchNodeRule", qnameSuffixRule); - - luaCtx.writeFunction("NetmaskGroupRule", [](const boost::variant> netmasks, boost::optional src, boost::optional quiet) { - if (netmasks.type() == typeid(string)) { - NetmaskGroup nmg; - nmg.addMask(*boost::get(&netmasks)); - return std::shared_ptr(new NetmaskGroupRule(nmg, src ? *src : true, quiet ? *quiet : false)); - } - - if (netmasks.type() == typeid(LuaArray)) { - NetmaskGroup nmg; - for (const auto& str : *boost::get>(&netmasks)) { - nmg.addMask(str.second); - } - return std::shared_ptr(new NetmaskGroupRule(nmg, src ? *src : true, quiet ? *quiet : false)); - } - - const auto& nmg = *boost::get(&netmasks); - return std::shared_ptr(new NetmaskGroupRule(nmg, src ? *src : true, quiet ? *quiet : false)); - }); - - luaCtx.writeFunction("benchRule", [](std::shared_ptr rule, boost::optional times_, boost::optional suffix_) { - setLuaNoSideEffect(); - unsigned int times = times_ ? *times_ : 100000; - DNSName suffix(suffix_ ? *suffix_ : "powerdns.com"); - struct item { - PacketBuffer packet; - InternalQueryState ids; - }; - vector items; - items.reserve(1000); - for (int n = 0; n < 1000; ++n) { - struct item i; - i.ids.qname = DNSName(std::to_string(dns_random_uint32())); - i.ids.qname += suffix; - i.ids.qtype = dns_random(0xff); - i.ids.qclass = QClass::IN; - i.ids.protocol = dnsdist::Protocol::DoUDP; - i.ids.origRemote = ComboAddress("127.0.0.1"); - i.ids.origRemote.sin4.sin_addr.s_addr = random(); - i.ids.queryRealTime.start(); - GenericDNSPacketWriter pw(i.packet, i.ids.qname, i.ids.qtype); - items.push_back(std::move(i)); - } - - int matches = 0; - ComboAddress dummy("127.0.0.1"); - StopWatch sw; - sw.start(); - for (unsigned int n = 0; n < times; ++n) { - item& i = items[n % items.size()]; - DNSQuestion dq(i.ids, i.packet); - - if (rule->matches(&dq)) { - matches++; - } - } - double udiff = sw.udiff(); - g_outputBuffer=(boost::format("Had %d matches out of %d, %.1f qps, in %.1f us\n") % matches % times % (1000000*(1.0*times/udiff)) % udiff).str(); - - }); - - luaCtx.writeFunction("AllRule", []() { - return std::shared_ptr(new AllRule()); - }); - - luaCtx.writeFunction("ProbaRule", [](double proba) { - return std::shared_ptr(new ProbaRule(proba)); - }); - - luaCtx.writeFunction("QNameRule", [](const std::string& qname) { - return std::shared_ptr(new QNameRule(DNSName(qname))); - }); - - luaCtx.writeFunction("QNameSuffixRule", qnameSuffixRule); - - luaCtx.writeFunction("QTypeRule", [](boost::variant str) { - uint16_t qtype; - if (auto dir = boost::get(&str)) { - qtype = *dir; - } - else { - string val = boost::get(str); - qtype = QType::chartocode(val.c_str()); - if (!qtype) { - throw std::runtime_error("Unable to convert '"+val+"' to a DNS type"); - } - } - return std::shared_ptr(new QTypeRule(qtype)); - }); - - luaCtx.writeFunction("QClassRule", [](uint64_t c) { - checkParameterBound("QClassRule", c, std::numeric_limits::max()); - return std::shared_ptr(new QClassRule(c)); - }); - - luaCtx.writeFunction("OpcodeRule", [](uint64_t code) { - checkParameterBound("OpcodeRule", code, std::numeric_limits::max()); - return std::shared_ptr(new OpcodeRule(code)); - }); - - luaCtx.writeFunction("AndRule", [](const LuaArray>& a) { - return std::shared_ptr(new AndRule(a)); - }); - - luaCtx.writeFunction("OrRule", [](const LuaArray>& a) { - return std::shared_ptr(new OrRule(a)); - }); - - luaCtx.writeFunction("DSTPortRule", [](uint64_t port) { - checkParameterBound("DSTPortRule", port, std::numeric_limits::max()); - return std::shared_ptr(new DSTPortRule(port)); - }); - - luaCtx.writeFunction("TCPRule", [](bool tcp) { - return std::shared_ptr(new TCPRule(tcp)); - }); - - luaCtx.writeFunction("DNSSECRule", []() { - return std::shared_ptr(new DNSSECRule()); - }); - - luaCtx.writeFunction("NotRule", [](const std::shared_ptr& rule) { - return std::shared_ptr(new NotRule(rule)); - }); - - luaCtx.writeFunction("RecordsCountRule", [](uint64_t section, uint64_t minCount, uint64_t maxCount) { - checkParameterBound("RecordsCountRule", section, std::numeric_limits::max()); - checkParameterBound("RecordsCountRule", minCount, std::numeric_limits::max()); - checkParameterBound("RecordsCountRule", maxCount, std::numeric_limits::max()); - return std::shared_ptr(new RecordsCountRule(section, minCount, maxCount)); - }); - - luaCtx.writeFunction("RecordsTypeCountRule", [](uint64_t section, uint64_t type, uint64_t minCount, uint64_t maxCount) { - checkParameterBound("RecordsTypeCountRule", section, std::numeric_limits::max()); - checkParameterBound("RecordsTypeCountRule", type, std::numeric_limits::max()); - checkParameterBound("RecordsTypeCountRule", minCount, std::numeric_limits::max()); - checkParameterBound("RecordsTypeCountRule", maxCount, std::numeric_limits::max()); - return std::shared_ptr(new RecordsTypeCountRule(section, type, minCount, maxCount)); - }); - - luaCtx.writeFunction("TrailingDataRule", []() { - return std::shared_ptr(new TrailingDataRule()); - }); - - luaCtx.writeFunction("QNameLabelsCountRule", [](uint64_t minLabelsCount, uint64_t maxLabelsCount) { - checkParameterBound("QNameLabelsCountRule", minLabelsCount, std::numeric_limits::max()); - checkParameterBound("QNameLabelsCountRule", maxLabelsCount, std::numeric_limits::max()); - return std::shared_ptr(new QNameLabelsCountRule(minLabelsCount, maxLabelsCount)); - }); - - luaCtx.writeFunction("QNameWireLengthRule", [](uint64_t min, uint64_t max) { - return std::shared_ptr(new QNameWireLengthRule(min, max)); - }); - - luaCtx.writeFunction("RCodeRule", [](uint64_t rcode) { - checkParameterBound("RCodeRule", rcode, std::numeric_limits::max()); - return std::shared_ptr(new RCodeRule(rcode)); - }); - - luaCtx.writeFunction("ERCodeRule", [](uint64_t rcode) { - checkParameterBound("ERCodeRule", rcode, std::numeric_limits::max()); - return std::shared_ptr(new ERCodeRule(rcode)); - }); - - luaCtx.writeFunction("EDNSVersionRule", [](uint64_t version) { - checkParameterBound("EDNSVersionRule", version, std::numeric_limits::max()); - return std::shared_ptr(new EDNSVersionRule(version)); - }); - - luaCtx.writeFunction("EDNSOptionRule", [](uint64_t optcode) { - checkParameterBound("EDNSOptionRule", optcode, std::numeric_limits::max()); - return std::shared_ptr(new EDNSOptionRule(optcode)); - }); - - luaCtx.writeFunction("showRules", [](boost::optional vars) { - showRules(&g_ruleactions, vars); - }); - - luaCtx.writeFunction("RDRule", []() { - return std::shared_ptr(new RDRule()); - }); - - luaCtx.writeFunction("TagRule", [](const std::string& tag, boost::optional value) { - return std::shared_ptr(new TagRule(tag, std::move(value))); - }); - - luaCtx.writeFunction("TimedIPSetRule", []() { - return std::shared_ptr(new TimedIPSetRule()); - }); - - luaCtx.writeFunction("PoolAvailableRule", [](const std::string& poolname) { - return std::shared_ptr(new PoolAvailableRule(poolname)); - }); - - luaCtx.writeFunction("PoolOutstandingRule", [](const std::string& poolname, uint64_t limit) { - return std::shared_ptr(new PoolOutstandingRule(poolname, limit)); - }); - - luaCtx.registerFunction::*)()>("clear", [](std::shared_ptr tisr) { - tisr->clear(); - }); - - luaCtx.registerFunction::*)()>("cleanup", [](std::shared_ptr tisr) { - tisr->cleanup(); - }); - - luaCtx.registerFunction::*)(const ComboAddress& ca, int t)>("add", [](std::shared_ptr tisr, const ComboAddress& ca, int t) { - tisr->add(ca, time(0)+t); - }); - - luaCtx.registerFunction(std::shared_ptr::*)()>("slice", [](std::shared_ptr tisr) { - return std::dynamic_pointer_cast(tisr); - }); - luaCtx.registerFunction::*)()>("__tostring", [](std::shared_ptr tisr) { - tisr->toString(); - }); - - luaCtx.writeFunction("QNameSetRule", [](const DNSNameSet& names) { - return std::shared_ptr(new QNameSetRule(names)); - }); - -#if defined(HAVE_LMDB) || defined(HAVE_CDB) - luaCtx.writeFunction("KeyValueStoreLookupRule", [](std::shared_ptr& kvs, std::shared_ptr& lookupKey) { - return std::shared_ptr(new KeyValueStoreLookupRule(kvs, lookupKey)); - }); - - luaCtx.writeFunction("KeyValueStoreRangeLookupRule", [](std::shared_ptr& kvs, std::shared_ptr& lookupKey) { - return std::shared_ptr(new KeyValueStoreRangeLookupRule(kvs, lookupKey)); - }); -#endif /* defined(HAVE_LMDB) || defined(HAVE_CDB) */ - - luaCtx.writeFunction("LuaRule", [](LuaRule::func_t func) { - return std::shared_ptr(new LuaRule(func)); - }); - - luaCtx.writeFunction("LuaFFIRule", [](LuaFFIRule::func_t func) { - return std::shared_ptr(new LuaFFIRule(func)); - }); - - luaCtx.writeFunction("LuaFFIPerThreadRule", [](const std::string& code) { - return std::shared_ptr(new LuaFFIPerThreadRule(code)); - }); - - luaCtx.writeFunction("ProxyProtocolValueRule", [](uint8_t type, boost::optional value) { - return std::shared_ptr(new ProxyProtocolValueRule(type, std::move(value))); - }); - - luaCtx.writeFunction("PayloadSizeRule", [](const std::string& comparison, uint16_t size) { - return std::shared_ptr(new PayloadSizeRule(comparison, size)); - }); -} diff --git a/pdns/dnsdist-lua-vars.cc b/pdns/dnsdist-lua-vars.cc deleted file mode 100644 index 89927b72456a..000000000000 --- a/pdns/dnsdist-lua-vars.cc +++ /dev/null @@ -1,129 +0,0 @@ -/* - * This file is part of PowerDNS or dnsdist. - * Copyright -- PowerDNS.COM B.V. and its contributors - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of version 2 of the GNU General Public License as - * published by the Free Software Foundation. - * - * In addition, for the avoidance of any doubt, permission is granted to - * link this program with OpenSSL and to (re)distribute the binaries - * produced as the result of such linking. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ -#include "dnsdist.hh" -#include "dnsdist-lua.hh" -#include "ednsoptions.hh" - -#undef BADSIG // signal.h SIG_ERR - -void setupLuaVars(LuaContext& luaCtx) -{ - luaCtx.writeVariable("DNSAction", LuaAssociativeTable{ - {"Drop", (int)DNSAction::Action::Drop}, - {"Nxdomain", (int)DNSAction::Action::Nxdomain}, - {"Refused", (int)DNSAction::Action::Refused}, - {"Spoof", (int)DNSAction::Action::Spoof}, - {"SpoofPacket", (int)DNSAction::Action::SpoofPacket}, - {"SpoofRaw", (int)DNSAction::Action::SpoofRaw}, - {"Allow", (int)DNSAction::Action::Allow}, - {"HeaderModify", (int)DNSAction::Action::HeaderModify}, - {"Pool", (int)DNSAction::Action::Pool}, - {"None",(int)DNSAction::Action::None}, - {"NoOp",(int)DNSAction::Action::NoOp}, - {"Delay", (int)DNSAction::Action::Delay}, - {"Truncate", (int)DNSAction::Action::Truncate}, - {"ServFail", (int)DNSAction::Action::ServFail}, - {"NoRecurse", (int)DNSAction::Action::NoRecurse} - }); - - luaCtx.writeVariable("DNSResponseAction", LuaAssociativeTable{ - {"Allow", (int)DNSResponseAction::Action::Allow }, - {"Delay", (int)DNSResponseAction::Action::Delay }, - {"Drop", (int)DNSResponseAction::Action::Drop }, - {"HeaderModify", (int)DNSResponseAction::Action::HeaderModify }, - {"ServFail", (int)DNSResponseAction::Action::ServFail }, - {"Truncate", (int)DNSResponseAction::Action::Truncate }, - {"None", (int)DNSResponseAction::Action::None } - }); - - luaCtx.writeVariable("DNSClass", LuaAssociativeTable{ - {"IN", QClass::IN }, - {"CHAOS", QClass::CHAOS }, - {"NONE", QClass::NONE }, - {"ANY", QClass::ANY } - }); - - luaCtx.writeVariable("DNSOpcode", LuaAssociativeTable{ - {"Query", Opcode::Query }, - {"IQuery", Opcode::IQuery }, - {"Status", Opcode::Status }, - {"Notify", Opcode::Notify }, - {"Update", Opcode::Update } - }); - - luaCtx.writeVariable("DNSSection", LuaAssociativeTable{ - {"Question", 0 }, - {"Answer", 1 }, - {"Authority", 2 }, - {"Additional",3 } - }); - - luaCtx.writeVariable("EDNSOptionCode", LuaAssociativeTable{ - {"NSID", EDNSOptionCode::NSID }, - {"DAU", EDNSOptionCode::DAU }, - {"DHU", EDNSOptionCode::DHU }, - {"N3U", EDNSOptionCode::N3U }, - {"ECS", EDNSOptionCode::ECS }, - {"EXPIRE", EDNSOptionCode::EXPIRE }, - {"COOKIE", EDNSOptionCode::COOKIE }, - {"TCPKEEPALIVE", EDNSOptionCode::TCPKEEPALIVE }, - {"PADDING", EDNSOptionCode::PADDING }, - {"CHAIN", EDNSOptionCode::CHAIN }, - {"KEYTAG", EDNSOptionCode::KEYTAG } - }); - - luaCtx.writeVariable("DNSRCode", LuaAssociativeTable{ - {"NOERROR", RCode::NoError }, - {"FORMERR", RCode::FormErr }, - {"SERVFAIL", RCode::ServFail }, - {"NXDOMAIN", RCode::NXDomain }, - {"NOTIMP", RCode::NotImp }, - {"REFUSED", RCode::Refused }, - {"YXDOMAIN", RCode::YXDomain }, - {"YXRRSET", RCode::YXRRSet }, - {"NXRRSET", RCode::NXRRSet }, - {"NOTAUTH", RCode::NotAuth }, - {"NOTZONE", RCode::NotZone }, - {"BADVERS", ERCode::BADVERS }, - {"BADSIG", ERCode::BADSIG }, - {"BADKEY", ERCode::BADKEY }, - {"BADTIME", ERCode::BADTIME }, - {"BADMODE", ERCode::BADMODE }, - {"BADNAME", ERCode::BADNAME }, - {"BADALG", ERCode::BADALG }, - {"BADTRUNC", ERCode::BADTRUNC }, - {"BADCOOKIE",ERCode::BADCOOKIE } - }); - - LuaAssociativeTable dd; - for (const auto& n : QType::names) { - dd[n.first] = n.second; - } - luaCtx.writeVariable("DNSQType", dd); - -#ifdef HAVE_DNSCRYPT - luaCtx.writeVariable("DNSCryptExchangeVersion", LuaAssociativeTable{ - { "VERSION1", DNSCryptExchangeVersion::VERSION1 }, - { "VERSION2", DNSCryptExchangeVersion::VERSION2 }, - }); -#endif -} diff --git a/pdns/dnsdist-lua.cc b/pdns/dnsdist-lua.cc deleted file mode 100644 index 54b7109b1968..000000000000 --- a/pdns/dnsdist-lua.cc +++ /dev/null @@ -1,3360 +0,0 @@ -/* - * This file is part of PowerDNS or dnsdist. - * Copyright -- PowerDNS.COM B.V. and its contributors - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of version 2 of the GNU General Public License as - * published by the Free Software Foundation. - * - * In addition, for the avoidance of any doubt, permission is granted to - * link this program with OpenSSL and to (re)distribute the binaries - * produced as the result of such linking. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include -#include -#include -#include - -// for OpenBSD, sys/socket.h needs to come before net/if.h -#include -#include - -#include -#include -#include -#include -#include - -#include "dnsdist.hh" -#include "dnsdist-carbon.hh" -#include "dnsdist-concurrent-connections.hh" -#include "dnsdist-console.hh" -#include "dnsdist-crypto.hh" -#include "dnsdist-dynblocks.hh" -#include "dnsdist-discovery.hh" -#include "dnsdist-ecs.hh" -#include "dnsdist-healthchecks.hh" -#include "dnsdist-lua.hh" -#ifdef LUAJIT_VERSION -#include "dnsdist-lua-ffi.hh" -#endif /* LUAJIT_VERSION */ -#include "dnsdist-metrics.hh" -#include "dnsdist-nghttp2.hh" -#include "dnsdist-proxy-protocol.hh" -#include "dnsdist-rings.hh" -#include "dnsdist-secpoll.hh" -#include "dnsdist-session-cache.hh" -#include "dnsdist-tcp-downstream.hh" -#include "dnsdist-web.hh" - -#include "base64.hh" -#include "coverage.hh" -#include "doh.hh" -#include "doq-common.hh" -#include "dolog.hh" -#include "threadname.hh" - -#ifdef HAVE_LIBSSL -#include "libssl.hh" -#endif - -#include -#include - -#ifdef HAVE_SYSTEMD -#include -#endif - -using std::thread; - -static boost::optional>> g_launchWork = boost::none; - -boost::tribool g_noLuaSideEffect; -static bool g_included{false}; - -/* this is a best effort way to prevent logging calls with no side-effects in the output of delta() - Functions can declare setLuaNoSideEffect() and if nothing else does declare a side effect, or nothing - has done so before on this invocation, this call won't be part of delta() output */ -void setLuaNoSideEffect() -{ - if (g_noLuaSideEffect == false) // there has been a side effect already - return; - g_noLuaSideEffect = true; -} - -void setLuaSideEffect() -{ - g_noLuaSideEffect = false; -} - -bool getLuaNoSideEffect() -{ - if (g_noLuaSideEffect) { - return true; - } - return false; -} - -void resetLuaSideEffect() -{ - g_noLuaSideEffect = boost::logic::indeterminate; -} - -using localbind_t = LuaAssociativeTable, LuaArray, LuaAssociativeTable>>; - -static void parseLocalBindVars(boost::optional& vars, bool& reusePort, int& tcpFastOpenQueueSize, std::string& interface, std::set& cpus, int& tcpListenQueueSize, uint64_t& maxInFlightQueriesPerConnection, uint64_t& tcpMaxConcurrentConnections, bool& enableProxyProtocol) -{ - if (vars) { - LuaArray setCpus; - - getOptionalValue(vars, "reusePort", reusePort); - getOptionalValue(vars, "enableProxyProtocol", enableProxyProtocol); - getOptionalValue(vars, "tcpFastOpenQueueSize", tcpFastOpenQueueSize); - getOptionalValue(vars, "tcpListenQueueSize", tcpListenQueueSize); - getOptionalValue(vars, "maxConcurrentTCPConnections", tcpMaxConcurrentConnections); - getOptionalValue(vars, "maxInFlight", maxInFlightQueriesPerConnection); - getOptionalValue(vars, "interface", interface); - if (getOptionalValue(vars, "cpus", setCpus) > 0) { - for (const auto& cpu : setCpus) { - cpus.insert(cpu.second); - } - } - } -} - -#if defined(HAVE_DNS_OVER_TLS) || defined(HAVE_DNS_OVER_HTTPS) || defined(HAVE_DNS_OVER_QUIC) -static bool loadTLSCertificateAndKeys(const std::string& context, std::vector& pairs, const boost::variant, LuaArray, LuaArray>>& certFiles, const LuaTypeOrArrayOf& keyFiles) -{ - if (certFiles.type() == typeid(std::string) && keyFiles.type() == typeid(std::string)) { - auto certFile = boost::get(certFiles); - auto keyFile = boost::get(keyFiles); - pairs.clear(); - pairs.emplace_back(certFile, keyFile); - } - else if (certFiles.type() == typeid(std::shared_ptr)) { - auto cert = boost::get>(certFiles); - pairs.clear(); - pairs.emplace_back(*cert); - } - else if (certFiles.type() == typeid(LuaArray>)) { - auto certs = boost::get>>(certFiles); - pairs.clear(); - for (const auto& cert : certs) { - pairs.emplace_back(*(cert.second)); - } - } - else if (certFiles.type() == typeid(LuaArray) && keyFiles.type() == typeid(LuaArray)) { - auto certFilesVect = boost::get>(certFiles); - auto keyFilesVect = boost::get>(keyFiles); - if (certFilesVect.size() == keyFilesVect.size()) { - pairs.clear(); - for (size_t idx = 0; idx < certFilesVect.size(); idx++) { - pairs.emplace_back(certFilesVect.at(idx).second, keyFilesVect.at(idx).second); - } - } - else { - errlog("Error, mismatching number of certificates and keys in call to %s()!", context); - g_outputBuffer = "Error, mismatching number of certificates and keys in call to " + context + "()!"; - return false; - } - } - else { - errlog("Error, mismatching number of certificates and keys in call to %s()!", context); - g_outputBuffer = "Error, mismatching number of certificates and keys in call to " + context + "()!"; - return false; - } - - return true; -} - -static void parseTLSConfig(TLSConfig& config, const std::string& context, boost::optional& vars) -{ - getOptionalValue(vars, "ciphers", config.d_ciphers); - getOptionalValue(vars, "ciphersTLS13", config.d_ciphers13); - -#ifdef HAVE_LIBSSL - std::string minVersion; - if (getOptionalValue(vars, "minTLSVersion", minVersion) > 0) { - config.d_minTLSVersion = libssl_tls_version_from_string(minVersion); - } -#else /* HAVE_LIBSSL */ - if (vars->erase("minTLSVersion") > 0) - warnlog("minTLSVersion has no effect with chosen TLS library"); -#endif /* HAVE_LIBSSL */ - - getOptionalValue(vars, "ticketKeyFile", config.d_ticketKeyFile); - getOptionalValue(vars, "ticketsKeysRotationDelay", config.d_ticketsKeyRotationDelay); - getOptionalValue(vars, "numberOfTicketsKeys", config.d_numberOfTicketsKeys); - getOptionalValue(vars, "preferServerCiphers", config.d_preferServerCiphers); - getOptionalValue(vars, "sessionTimeout", config.d_sessionTimeout); - getOptionalValue(vars, "sessionTickets", config.d_enableTickets); - int numberOfStoredSessions{0}; - if (getOptionalValue(vars, "numberOfStoredSessions", numberOfStoredSessions) > 0) { - if (numberOfStoredSessions < 0) { - errlog("Invalid value '%d' for %s() parameter 'numberOfStoredSessions', should be >= 0, dismissing", numberOfStoredSessions, context); - g_outputBuffer = "Invalid value '" + std::to_string(numberOfStoredSessions) + "' for " + context + "() parameter 'numberOfStoredSessions', should be >= 0, dimissing"; - } - else { - config.d_maxStoredSessions = numberOfStoredSessions; - } - } - - LuaArray files; - if (getOptionalValue(vars, "ocspResponses", files) > 0) { - for (const auto& file : files) { - config.d_ocspFiles.push_back(file.second); - } - } - - if (vars->count("keyLogFile") > 0) { -#ifdef HAVE_SSL_CTX_SET_KEYLOG_CALLBACK - getOptionalValue(vars, "keyLogFile", config.d_keyLogFile); -#else - errlog("TLS Key logging has been enabled using the 'keyLogFile' parameter to %s(), but this version of OpenSSL does not support it", context); - g_outputBuffer = "TLS Key logging has been enabled using the 'keyLogFile' parameter to " + context + "(), but this version of OpenSSL does not support it"; -#endif - } - - getOptionalValue(vars, "releaseBuffers", config.d_releaseBuffers); - getOptionalValue(vars, "enableRenegotiation", config.d_enableRenegotiation); - getOptionalValue(vars, "tlsAsyncMode", config.d_asyncMode); - getOptionalValue(vars, "ktls", config.d_ktls); - getOptionalValue(vars, "readAhead", config.d_readAhead); -} - -#endif // defined(HAVE_DNS_OVER_TLS) || defined(HAVE_DNS_OVER_HTTPS) - -void checkParameterBound(const std::string& parameter, uint64_t value, size_t max) -{ - if (value > max) { - throw std::runtime_error("The value (" + std::to_string(value) + ") passed to " + parameter + " is too large, the maximum is " + std::to_string(max)); - } -} - -static void LuaThread(const std::string& code) -{ - setThreadName("dnsdist/lua-bg"); - LuaContext l; - - // mask SIGTERM on threads so the signal always comes to dnsdist itself - sigset_t blockSignals; - - sigemptyset(&blockSignals); - sigaddset(&blockSignals, SIGTERM); - - pthread_sigmask(SIG_BLOCK, &blockSignals, nullptr); - - // submitToMainThread is camelcased, threadmessage is not. - // This follows our tradition of hooks we call being lowercased but functions the user can call being camelcased. - l.writeFunction("submitToMainThread", [](std::string cmd, LuaAssociativeTable data) { - auto lua = g_lua.lock(); - // maybe offer more than `void` - auto func = lua->readVariable data)>>>("threadmessage"); - if (func) { - func.get()(std::move(cmd), std::move(data)); - } - else { - errlog("Lua thread called submitToMainThread but no threadmessage receiver is defined"); - } - }); - - // function threadmessage(cmd, data) print("got thread data:", cmd) for k,v in pairs(data) do print(k,v) end end - - for (;;) { - try { - l.executeCode(code); - errlog("Lua thread exited, restarting in 5 seconds"); - } - catch (const std::exception& e) { - errlog("Lua thread crashed, restarting in 5 seconds: %s", e.what()); - } - catch (...) { - errlog("Lua thread crashed, restarting in 5 seconds"); - } - sleep(5); - } -} - -static bool checkConfigurationTime(const std::string& name) -{ - if (!g_configurationDone) { - return true; - } - g_outputBuffer = name + " cannot be used at runtime!\n"; - errlog("%s cannot be used at runtime!", name); - return false; -} - -// NOLINTNEXTLINE(readability-function-cognitive-complexity): this function declares Lua bindings, even with a good refactoring it will likely blow up the threshold -static void setupLuaConfig(LuaContext& luaCtx, bool client, bool configCheck) -{ - typedef LuaAssociativeTable, DownstreamState::checkfunc_t>> newserver_t; - luaCtx.writeFunction("inClientStartup", [client]() { - return client && !g_configurationDone; - }); - - luaCtx.writeFunction("inConfigCheck", [configCheck]() { - return configCheck; - }); - - luaCtx.writeFunction("newServer", - [client, configCheck](boost::variant pvars, boost::optional qps) { - setLuaSideEffect(); - - boost::optional vars = newserver_t(); - DownstreamState::Config config; - - std::string serverAddressStr; - if (auto addrStr = boost::get(&pvars)) { - serverAddressStr = *addrStr; - if (qps) { - (*vars)["qps"] = std::to_string(*qps); - } - } - else { - vars = boost::get(pvars); - getOptionalValue(vars, "address", serverAddressStr); - } - - std::string source; - if (getOptionalValue(vars, "source", source) > 0) { - /* handle source in the following forms: - - v4 address ("192.0.2.1") - - v6 address ("2001:DB8::1") - - interface name ("eth0") - - v4 address and interface name ("192.0.2.1@eth0") - - v6 address and interface name ("2001:DB8::1@eth0") - */ - bool parsed = false; - std::string::size_type pos = source.find("@"); - if (pos == std::string::npos) { - /* no '@', try to parse that as a valid v4/v6 address */ - try { - config.sourceAddr = ComboAddress(source); - parsed = true; - } - catch (...) { - } - } - - if (parsed == false) { - /* try to parse as interface name, or v4/v6@itf */ - config.sourceItfName = source.substr(pos == std::string::npos ? 0 : pos + 1); - unsigned int itfIdx = if_nametoindex(config.sourceItfName.c_str()); - if (itfIdx != 0) { - if (pos == 0 || pos == std::string::npos) { - /* "eth0" or "@eth0" */ - config.sourceItf = itfIdx; - } - else { - /* "192.0.2.1@eth0" */ - config.sourceAddr = ComboAddress(source.substr(0, pos)); - config.sourceItf = itfIdx; - } -#ifdef SO_BINDTODEVICE - /* we need to retain CAP_NET_RAW to be able to set SO_BINDTODEVICE in the health checks */ - g_capabilitiesToRetain.insert("CAP_NET_RAW"); -#endif - } - else { - warnlog("Dismissing source %s because '%s' is not a valid interface name", source, config.sourceItfName); - } - } - } - - std::string valueStr; - if (getOptionalValue(vars, "sockets", valueStr) > 0) { - config.d_numberOfSockets = std::stoul(valueStr); - if (config.d_numberOfSockets == 0) { - warnlog("Dismissing invalid number of sockets '%s', using 1 instead", valueStr); - config.d_numberOfSockets = 1; - } - } - - getOptionalIntegerValue("newServer", vars, "qps", config.d_qpsLimit); - getOptionalIntegerValue("newServer", vars, "order", config.order); - getOptionalIntegerValue("newServer", vars, "weight", config.d_weight); - if (config.d_weight < 1) { - errlog("Error creating new server: downstream weight value must be greater than 0."); - return std::shared_ptr(); - } - - getOptionalIntegerValue("newServer", vars, "retries", config.d_retries); - getOptionalIntegerValue("newServer", vars, "tcpConnectTimeout", config.tcpConnectTimeout); - getOptionalIntegerValue("newServer", vars, "tcpSendTimeout", config.tcpSendTimeout); - getOptionalIntegerValue("newServer", vars, "tcpRecvTimeout", config.tcpRecvTimeout); - - if (getOptionalValue(vars, "checkInterval", valueStr) > 0) { - config.checkInterval = static_cast(std::stoul(valueStr)); - } - - bool fastOpen{false}; - if (getOptionalValue(vars, "tcpFastOpen", fastOpen) > 0) { - if (fastOpen) { -#ifdef MSG_FASTOPEN - config.tcpFastOpen = true; -#else - warnlog("TCP Fast Open has been configured on downstream server %s but is not supported", serverAddressStr); -#endif - } - } - - getOptionalIntegerValue("newServer", vars, "maxInFlight", config.d_maxInFlightQueriesPerConn); - getOptionalIntegerValue("newServer", vars, "maxConcurrentTCPConnections", config.d_tcpConcurrentConnectionsLimit); - - getOptionalValue(vars, "name", config.name); - - if (getOptionalValue(vars, "id", valueStr) > 0) { - config.id = boost::uuids::string_generator()(valueStr); - } - - if (getOptionalValue(vars, "healthCheckMode", valueStr) > 0) { - const auto& mode = valueStr; - if (pdns_iequals(mode, "auto")) { - config.availability = DownstreamState::Availability::Auto; - } - else if (pdns_iequals(mode, "lazy")) { - config.availability = DownstreamState::Availability::Lazy; - } - else if (pdns_iequals(mode, "up")) { - config.availability = DownstreamState::Availability::Up; - } - else if (pdns_iequals(mode, "down")) { - config.availability = DownstreamState::Availability::Down; - } - else { - warnlog("Ignoring unknown value '%s' for 'healthCheckMode' on 'newServer'", mode); - } - } - - if (getOptionalValue(vars, "checkName", valueStr) > 0) { - config.checkName = DNSName(valueStr); - } - - getOptionalValue(vars, "checkType", config.checkType); - getOptionalIntegerValue("newServer", vars, "checkClass", config.checkClass); - getOptionalValue(vars, "checkFunction", config.checkFunction); - getOptionalIntegerValue("newServer", vars, "checkTimeout", config.checkTimeout); - getOptionalValue(vars, "checkTCP", config.d_tcpCheck); - getOptionalValue(vars, "setCD", config.setCD); - getOptionalValue(vars, "mustResolve", config.mustResolve); - - if (getOptionalValue(vars, "lazyHealthCheckSampleSize", valueStr) > 0) { - const auto& value = std::stoi(valueStr); - checkParameterBound("lazyHealthCheckSampleSize", value); - config.d_lazyHealthCheckSampleSize = value; - } - - if (getOptionalValue(vars, "lazyHealthCheckMinSampleCount", valueStr) > 0) { - const auto& value = std::stoi(valueStr); - checkParameterBound("lazyHealthCheckMinSampleCount", value); - config.d_lazyHealthCheckMinSampleCount = value; - } - - if (getOptionalValue(vars, "lazyHealthCheckThreshold", valueStr) > 0) { - const auto& value = std::stoi(valueStr); - checkParameterBound("lazyHealthCheckThreshold", value, std::numeric_limits::max()); - config.d_lazyHealthCheckThreshold = value; - } - - if (getOptionalValue(vars, "lazyHealthCheckFailedInterval", valueStr) > 0) { - const auto& value = std::stoi(valueStr); - checkParameterBound("lazyHealthCheckFailedInterval", value); - config.d_lazyHealthCheckFailedInterval = value; - } - - getOptionalValue(vars, "lazyHealthCheckUseExponentialBackOff", config.d_lazyHealthCheckUseExponentialBackOff); - - if (getOptionalValue(vars, "lazyHealthCheckMaxBackOff", valueStr) > 0) { - const auto& value = std::stoi(valueStr); - checkParameterBound("lazyHealthCheckMaxBackOff", value); - config.d_lazyHealthCheckMaxBackOff = value; - } - - if (getOptionalValue(vars, "lazyHealthCheckMode", valueStr) > 0) { - const auto& mode = valueStr; - if (pdns_iequals(mode, "TimeoutOnly")) { - config.d_lazyHealthCheckMode = DownstreamState::LazyHealthCheckMode::TimeoutOnly; - } - else if (pdns_iequals(mode, "TimeoutOrServFail")) { - config.d_lazyHealthCheckMode = DownstreamState::LazyHealthCheckMode::TimeoutOrServFail; - } - else { - warnlog("Ignoring unknown value '%s' for 'lazyHealthCheckMode' on 'newServer'", mode); - } - } - - getOptionalValue(vars, "lazyHealthCheckWhenUpgraded", config.d_upgradeToLazyHealthChecks); - - getOptionalValue(vars, "useClientSubnet", config.useECS); - getOptionalValue(vars, "useProxyProtocol", config.useProxyProtocol); - getOptionalValue(vars, "proxyProtocolAdvertiseTLS", config.d_proxyProtocolAdvertiseTLS); - getOptionalValue(vars, "disableZeroScope", config.disableZeroScope); - getOptionalValue(vars, "ipBindAddrNoPort", config.ipBindAddrNoPort); - - getOptionalIntegerValue("newServer", vars, "addXPF", config.xpfRRCode); - getOptionalIntegerValue("newServer", vars, "maxCheckFailures", config.maxCheckFailures); - getOptionalIntegerValue("newServer", vars, "rise", config.minRiseSuccesses); - - getOptionalValue(vars, "reconnectOnUp", config.reconnectOnUp); - - LuaArray cpuMap; - if (getOptionalValue(vars, "cpus", cpuMap) > 0) { - for (const auto& cpu : cpuMap) { - config.d_cpus.insert(std::stoi(cpu.second)); - } - } - - getOptionalValue(vars, "tcpOnly", config.d_tcpOnly); - - std::shared_ptr tlsCtx; - getOptionalValue(vars, "ciphers", config.d_tlsParams.d_ciphers); - getOptionalValue(vars, "ciphers13", config.d_tlsParams.d_ciphers13); - getOptionalValue(vars, "caStore", config.d_tlsParams.d_caStore); - getOptionalValue(vars, "validateCertificates", config.d_tlsParams.d_validateCertificates); - getOptionalValue(vars, "releaseBuffers", config.d_tlsParams.d_releaseBuffers); - getOptionalValue(vars, "enableRenegotiation", config.d_tlsParams.d_enableRenegotiation); - getOptionalValue(vars, "ktls", config.d_tlsParams.d_ktls); - getOptionalValue(vars, "subjectName", config.d_tlsSubjectName); - - if (getOptionalValue(vars, "subjectAddr", valueStr) > 0) { - try { - ComboAddress ca(valueStr); - config.d_tlsSubjectName = ca.toString(); - config.d_tlsSubjectIsAddr = true; - } - catch (const std::exception& e) { - errlog("Error creating new server: downstream subjectAddr value must be a valid IP address"); - return std::shared_ptr(); - } - } - - uint16_t serverPort = 53; - - if (getOptionalValue(vars, "tls", valueStr) > 0) { - serverPort = 853; - config.d_tlsParams.d_provider = valueStr; - tlsCtx = getTLSContext(config.d_tlsParams); - - if (getOptionalValue(vars, "dohPath", valueStr) > 0) { -#if !defined(HAVE_DNS_OVER_HTTPS) || !defined(HAVE_NGHTTP2) - throw std::runtime_error("Outgoing DNS over HTTPS support requested (via 'dohPath' on newServer()) but it is not available"); -#endif - - serverPort = 443; - config.d_dohPath = valueStr; - - getOptionalValue(vars, "addXForwardedHeaders", config.d_addXForwardedHeaders); - } - } - - try { - config.remote = ComboAddress(serverAddressStr, serverPort); - } - catch (const PDNSException& e) { - g_outputBuffer = "Error creating new server: " + string(e.reason); - errlog("Error creating new server with address %s: %s", serverAddressStr, e.reason); - return std::shared_ptr(); - } - catch (const std::exception& e) { - g_outputBuffer = "Error creating new server: " + string(e.what()); - errlog("Error creating new server with address %s: %s", serverAddressStr, e.what()); - return std::shared_ptr(); - } - - if (IsAnyAddress(config.remote)) { - g_outputBuffer = "Error creating new server: invalid address for a downstream server."; - errlog("Error creating new server: %s is not a valid address for a downstream server", serverAddressStr); - return std::shared_ptr(); - } - - LuaArray pools; - if (getOptionalValue(vars, "pool", valueStr, false) > 0) { - config.pools.insert(valueStr); - } - else if (getOptionalValue(vars, "pool", pools) > 0) { - for (auto& p : pools) { - config.pools.insert(p.second); - } - } - - bool autoUpgrade = false; - bool keepAfterUpgrade = false; - uint32_t upgradeInterval = 3600; - uint16_t upgradeDoHKey = dnsdist::ServiceDiscovery::s_defaultDoHSVCKey; - std::string upgradePool; - - getOptionalValue(vars, "autoUpgrade", autoUpgrade); - if (autoUpgrade) { - if (getOptionalValue(vars, "autoUpgradeInterval", valueStr) > 0) { - try { - upgradeInterval = static_cast(std::stoul(valueStr)); - } - catch (const std::exception& e) { - warnlog("Error parsing 'autoUpgradeInterval' value: %s", e.what()); - } - } - getOptionalValue(vars, "autoUpgradeKeep", keepAfterUpgrade); - getOptionalValue(vars, "autoUpgradePool", upgradePool); - if (getOptionalValue(vars, "autoUpgradeDoHKey", valueStr) > 0) { - try { - upgradeDoHKey = static_cast(std::stoul(valueStr)); - } - catch (const std::exception& e) { - warnlog("Error parsing 'autoUpgradeDoHKey' value: %s", e.what()); - } - } - } - - // create but don't connect the socket in client or check-config modes - auto ret = std::make_shared(std::move(config), std::move(tlsCtx), !(client || configCheck)); - if (!(client || configCheck)) { - infolog("Added downstream server %s", ret->d_config.remote.toStringWithPort()); - } - - if (autoUpgrade && ret->getProtocol() != dnsdist::Protocol::DoT && ret->getProtocol() != dnsdist::Protocol::DoH) { - dnsdist::ServiceDiscovery::addUpgradeableServer(ret, upgradeInterval, upgradePool, upgradeDoHKey, keepAfterUpgrade); - } - - /* this needs to be done _AFTER_ the order has been set, - since the server are kept ordered inside the pool */ - auto localPools = g_pools.getCopy(); - if (!ret->d_config.pools.empty()) { - for (const auto& poolName : ret->d_config.pools) { - addServerToPool(localPools, poolName, ret); - } - } - else { - addServerToPool(localPools, "", ret); - } - g_pools.setState(localPools); - - if (ret->connected) { - if (g_launchWork) { - g_launchWork->push_back([ret]() { - ret->start(); - }); - } - else { - ret->start(); - } - } - - auto states = g_dstates.getCopy(); - states.push_back(ret); - std::stable_sort(states.begin(), states.end(), [](const decltype(ret)& a, const decltype(ret)& b) { - return a->d_config.order < b->d_config.order; - }); - g_dstates.setState(states); - checkAllParametersConsumed("newServer", vars); - return ret; - }); - - luaCtx.writeFunction("rmServer", - [](boost::variant, int, std::string> var) { - setLuaSideEffect(); - shared_ptr server = nullptr; - auto states = g_dstates.getCopy(); - if (auto* rem = boost::get>(&var)) { - server = *rem; - } - else if (auto str = boost::get(&var)) { - const auto uuid = getUniqueID(*str); - for (auto& state : states) { - if (*state->d_config.id == uuid) { - server = state; - } - } - } - else { - int idx = boost::get(var); - server = states.at(idx); - } - if (!server) { - throw std::runtime_error("unable to locate the requested server"); - } - auto localPools = g_pools.getCopy(); - for (const string& poolName : server->d_config.pools) { - removeServerFromPool(localPools, poolName, server); - } - /* the server might also be in the default pool */ - removeServerFromPool(localPools, "", server); - g_pools.setState(localPools); - states.erase(remove(states.begin(), states.end(), server), states.end()); - g_dstates.setState(states); - server->stop(); - }); - - luaCtx.writeFunction("truncateTC", [](bool tc) { setLuaSideEffect(); g_truncateTC=tc; }); - luaCtx.writeFunction("fixupCase", [](bool fu) { setLuaSideEffect(); g_fixupCase=fu; }); - - luaCtx.writeFunction("addACL", [](const std::string& domain) { - setLuaSideEffect(); - g_ACL.modify([domain](NetmaskGroup& nmg) { nmg.addMask(domain); }); - }); - - luaCtx.writeFunction("rmACL", [](const std::string& netmask) { - setLuaSideEffect(); - g_ACL.modify([netmask](NetmaskGroup& nmg) { nmg.deleteMask(netmask); }); - }); - - luaCtx.writeFunction("setLocal", [client](const std::string& addr, boost::optional vars) { - setLuaSideEffect(); - if (client) { - return; - } - - if (!checkConfigurationTime("setLocal")) { - return; - } - - bool reusePort = false; - int tcpFastOpenQueueSize = 0; - int tcpListenQueueSize = 0; - uint64_t maxInFlightQueriesPerConn = 0; - uint64_t tcpMaxConcurrentConnections = 0; - std::string interface; - std::set cpus; - bool enableProxyProtocol = true; - - parseLocalBindVars(vars, reusePort, tcpFastOpenQueueSize, interface, cpus, tcpListenQueueSize, maxInFlightQueriesPerConn, tcpMaxConcurrentConnections, enableProxyProtocol); - - checkAllParametersConsumed("setLocal", vars); - - try { - ComboAddress loc(addr, 53); - for (auto it = g_frontends.begin(); it != g_frontends.end();) { - /* DoH, DoT and DNSCrypt frontends are separate */ - if ((*it)->tlsFrontend == nullptr && (*it)->dnscryptCtx == nullptr && (*it)->dohFrontend == nullptr) { - it = g_frontends.erase(it); - } - else { - ++it; - } - } - - // only works pre-startup, so no sync necessary - g_frontends.push_back(std::make_unique(loc, false, reusePort, tcpFastOpenQueueSize, interface, cpus, enableProxyProtocol)); - auto tcpCS = std::make_unique(loc, true, reusePort, tcpFastOpenQueueSize, interface, cpus, enableProxyProtocol); - if (tcpListenQueueSize > 0) { - tcpCS->tcpListenQueueSize = tcpListenQueueSize; - } - if (maxInFlightQueriesPerConn > 0) { - tcpCS->d_maxInFlightQueriesPerConn = maxInFlightQueriesPerConn; - } - if (tcpMaxConcurrentConnections > 0) { - tcpCS->d_tcpConcurrentConnectionsLimit = tcpMaxConcurrentConnections; - } - - g_frontends.push_back(std::move(tcpCS)); - } - catch (const std::exception& e) { - g_outputBuffer = "Error: " + string(e.what()) + "\n"; - } - }); - - luaCtx.writeFunction("addLocal", [client](const std::string& addr, boost::optional vars) { - setLuaSideEffect(); - if (client) - return; - - if (!checkConfigurationTime("addLocal")) { - return; - } - bool reusePort = false; - int tcpFastOpenQueueSize = 0; - int tcpListenQueueSize = 0; - uint64_t maxInFlightQueriesPerConn = 0; - uint64_t tcpMaxConcurrentConnections = 0; - std::string interface; - std::set cpus; - bool enableProxyProtocol = true; - - parseLocalBindVars(vars, reusePort, tcpFastOpenQueueSize, interface, cpus, tcpListenQueueSize, maxInFlightQueriesPerConn, tcpMaxConcurrentConnections, enableProxyProtocol); - checkAllParametersConsumed("addLocal", vars); - - try { - ComboAddress loc(addr, 53); - // only works pre-startup, so no sync necessary - g_frontends.push_back(std::make_unique(loc, false, reusePort, tcpFastOpenQueueSize, interface, cpus, enableProxyProtocol)); - auto tcpCS = std::make_unique(loc, true, reusePort, tcpFastOpenQueueSize, interface, cpus, enableProxyProtocol); - if (tcpListenQueueSize > 0) { - tcpCS->tcpListenQueueSize = tcpListenQueueSize; - } - if (maxInFlightQueriesPerConn > 0) { - tcpCS->d_maxInFlightQueriesPerConn = maxInFlightQueriesPerConn; - } - if (tcpMaxConcurrentConnections > 0) { - tcpCS->d_tcpConcurrentConnectionsLimit = tcpMaxConcurrentConnections; - } - g_frontends.push_back(std::move(tcpCS)); - } - catch (std::exception& e) { - g_outputBuffer = "Error: " + string(e.what()) + "\n"; - errlog("Error while trying to listen on %s: %s\n", addr, string(e.what())); - } - }); - - luaCtx.writeFunction("setACL", [](LuaTypeOrArrayOf inp) { - setLuaSideEffect(); - NetmaskGroup nmg; - if (auto str = boost::get(&inp)) { - nmg.addMask(*str); - } - else - for (const auto& p : boost::get>(inp)) { - nmg.addMask(p.second); - } - g_ACL.setState(nmg); - }); - - luaCtx.writeFunction("setACLFromFile", [](const std::string& file) { - setLuaSideEffect(); - NetmaskGroup nmg; - - ifstream ifs(file); - if (!ifs) { - throw std::runtime_error("Could not open '" + file + "': " + stringerror()); - } - - string::size_type pos; - string line; - while (getline(ifs, line)) { - pos = line.find('#'); - if (pos != string::npos) - line.resize(pos); - boost::trim(line); - if (line.empty()) - continue; - - nmg.addMask(line); - } - - g_ACL.setState(nmg); - }); - - luaCtx.writeFunction("showACL", []() { - setLuaNoSideEffect(); - auto aclEntries = g_ACL.getLocal()->toStringVector(); - - for (const auto& entry : aclEntries) { - g_outputBuffer += entry + "\n"; - } - }); - - luaCtx.writeFunction("shutdown", []() { -#ifdef HAVE_SYSTEMD - sd_notify(0, "STOPPING=1"); -#endif /* HAVE_SYSTEMD */ -#if 0 - // Useful for debugging leaks, but might lead to race under load - // since other threads are still running. - for (auto& frontend : g_tlslocals) { - frontend->cleanup(); - } - g_tlslocals.clear(); - g_rings.clear(); -#endif /* 0 */ - pdns::coverage::dumpCoverageData(); - _exit(0); - }); - - typedef LuaAssociativeTable> showserversopts_t; - - luaCtx.writeFunction("showServers", [](boost::optional vars) { - setLuaNoSideEffect(); - bool showUUIDs = false; - getOptionalValue(vars, "showUUIDs", showUUIDs); - checkAllParametersConsumed("showServers", vars); - - try { - ostringstream ret; - boost::format fmt; - - auto latFmt = boost::format("%5.1f"); - if (showUUIDs) { - fmt = boost::format("%1$-3d %15$-36s %2$-20.20s %|62t|%3% %|107t|%4$5s %|88t|%5$7.1f %|103t|%6$7d %|106t|%7$10d %|115t|%8$10d %|117t|%9$10d %|123t|%10$7d %|128t|%11$5.1f %|146t|%12$5s %|152t|%16$5s %|158t|%13$11d %14%"); - // 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 (tcp latency) - ret << (fmt % "#" % "Name" % "Address" % "State" % "Qps" % "Qlim" % "Ord" % "Wt" % "Queries" % "Drops" % "Drate" % "Lat" % "Outstanding" % "Pools" % "UUID" % "TCP") << endl; - } - else { - fmt = boost::format("%1$-3d %2$-20.20s %|25t|%3% %|70t|%4$5s %|51t|%5$7.1f %|66t|%6$7d %|69t|%7$10d %|78t|%8$10d %|80t|%9$10d %|86t|%10$7d %|91t|%11$5.1f %|109t|%12$5s %|115t|%15$5s %|121t|%13$11d %14%"); - ret << (fmt % "#" % "Name" % "Address" % "State" % "Qps" % "Qlim" % "Ord" % "Wt" % "Queries" % "Drops" % "Drate" % "Lat" % "Outstanding" % "Pools" % "TCP") << endl; - } - - uint64_t totQPS{0}, totQueries{0}, totDrops{0}; - int counter = 0; - auto states = g_dstates.getLocal(); - for (const auto& s : *states) { - string status = s->getStatus(); - string pools; - for (const auto& p : s->d_config.pools) { - if (!pools.empty()) { - pools += " "; - } - pools += p; - } - const std::string latency = (s->latencyUsec == 0.0 ? "-" : boost::str(latFmt % (s->latencyUsec / 1000.0))); - const std::string latencytcp = (s->latencyUsecTCP == 0.0 ? "-" : boost::str(latFmt % (s->latencyUsecTCP / 1000.0))); - if (showUUIDs) { - ret << (fmt % counter % s->getName() % s->d_config.remote.toStringWithPort() % status % s->queryLoad % s->qps.getRate() % s->d_config.order % s->d_config.d_weight % s->queries.load() % s->reuseds.load() % (s->dropRate) % latency % s->outstanding.load() % pools % *s->d_config.id % latencytcp) << endl; - } - else { - ret << (fmt % counter % s->getName() % s->d_config.remote.toStringWithPort() % status % s->queryLoad % s->qps.getRate() % s->d_config.order % s->d_config.d_weight % s->queries.load() % s->reuseds.load() % (s->dropRate) % latency % s->outstanding.load() % pools % latencytcp) << endl; - } - totQPS += s->queryLoad; - totQueries += s->queries.load(); - totDrops += s->reuseds.load(); - ++counter; - } - if (showUUIDs) { - ret << (fmt % "All" % "" % "" % "" - % (double)totQPS % "" % "" % "" % totQueries % totDrops % "" % "" % "" % "" % "" % "") - << endl; - } - else { - ret << (fmt % "All" % "" % "" % "" - % (double)totQPS % "" % "" % "" % totQueries % totDrops % "" % "" % "" % "" % "") - << endl; - } - - g_outputBuffer = ret.str(); - } - catch (std::exception& e) { - g_outputBuffer = e.what(); - throw; - } - }); - - luaCtx.writeFunction("getServers", []() { - setLuaNoSideEffect(); - LuaArray> ret; - int count = 1; - for (const auto& s : g_dstates.getCopy()) { - ret.emplace_back(count++, s); - } - return ret; - }); - - luaCtx.writeFunction("getPoolServers", [](const string& pool) { - const auto poolServers = getDownstreamCandidates(g_pools.getCopy(), pool); - return *poolServers; - }); - - luaCtx.writeFunction("getServer", [client](boost::variant i) { - if (client) { - return std::make_shared(ComboAddress()); - } - auto states = g_dstates.getCopy(); - if (auto str = boost::get(&i)) { - const auto uuid = getUniqueID(*str); - for (auto& state : states) { - if (*state->d_config.id == uuid) { - return state; - } - } - } - else if (auto pos = boost::get(&i)) { - return states.at(*pos); - } - - g_outputBuffer = "Error: no rule matched\n"; - return std::shared_ptr(nullptr); - }); - -#ifndef DISABLE_CARBON - luaCtx.writeFunction("carbonServer", [](const std::string& address, boost::optional ourName, boost::optional interval, boost::optional namespace_name, boost::optional instance_name) { - setLuaSideEffect(); - dnsdist::Carbon::Endpoint endpoint{ComboAddress(address, 2003), - (namespace_name && !namespace_name->empty()) ? *namespace_name : "dnsdist", - ourName ? *ourName : "", - (instance_name && !instance_name->empty()) ? *instance_name : "main", - (interval && *interval < std::numeric_limits::max()) ? static_cast(*interval) : 30}; - dnsdist::Carbon::addEndpoint(std::move(endpoint)); - }); -#endif /* DISABLE_CARBON */ - - luaCtx.writeFunction("webserver", [client, configCheck](const std::string& address) { - setLuaSideEffect(); - ComboAddress local; - try { - local = ComboAddress(address); - } - catch (const PDNSException& e) { - throw std::runtime_error(std::string("Error parsing the bind address for the webserver: ") + e.reason); - } - - if (client || configCheck) { - return; - } - - try { - int sock = SSocket(local.sin4.sin_family, SOCK_STREAM, 0); - SSetsockopt(sock, SOL_SOCKET, SO_REUSEADDR, 1); - SBind(sock, local); - SListen(sock, 5); - auto launch = [sock, local]() { - thread t(dnsdistWebserverThread, sock, local); - t.detach(); - }; - if (g_launchWork) { - g_launchWork->push_back(launch); - } - else { - launch(); - } - } - catch (const std::exception& e) { - g_outputBuffer = "Unable to bind to webserver socket on " + local.toStringWithPort() + ": " + e.what(); - errlog("Unable to bind to webserver socket on %s: %s", local.toStringWithPort(), e.what()); - } - }); - - typedef LuaAssociativeTable>> webserveropts_t; - - luaCtx.writeFunction("setWebserverConfig", [](boost::optional vars) { - setLuaSideEffect(); - - if (!vars) { - return; - } - - bool hashPlaintextCredentials = false; - getOptionalValue(vars, "hashPlaintextCredentials", hashPlaintextCredentials); - - std::string password; - std::string apiKey; - std::string acl; - LuaAssociativeTable headers; - bool statsRequireAuthentication{true}; - bool apiRequiresAuthentication{true}; - bool dashboardRequiresAuthentication{true}; - int maxConcurrentConnections = 0; - - if (getOptionalValue(vars, "password", password) > 0) { - auto holder = make_unique(std::move(password), hashPlaintextCredentials); - if (!holder->wasHashed() && holder->isHashingAvailable()) { - infolog("Passing a plain-text password via the 'password' parameter to 'setWebserverConfig()' is not advised, please consider generating a hashed one using 'hashPassword()' instead."); - } - - setWebserverPassword(std::move(holder)); - } - - if (getOptionalValue(vars, "apiKey", apiKey) > 0) { - auto holder = make_unique(std::move(apiKey), hashPlaintextCredentials); - if (!holder->wasHashed() && holder->isHashingAvailable()) { - infolog("Passing a plain-text API key via the 'apiKey' parameter to 'setWebserverConfig()' is not advised, please consider generating a hashed one using 'hashPassword()' instead."); - } - - setWebserverAPIKey(std::move(holder)); - } - - if (getOptionalValue(vars, "acl", acl) > 0) { - setWebserverACL(acl); - } - - if (getOptionalValue(vars, "customHeaders", headers) > 0) { - setWebserverCustomHeaders(headers); - } - - if (getOptionalValue(vars, "statsRequireAuthentication", statsRequireAuthentication) > 0) { - setWebserverStatsRequireAuthentication(statsRequireAuthentication); - } - - if (getOptionalValue(vars, "apiRequiresAuthentication", apiRequiresAuthentication) > 0) { - setWebserverAPIRequiresAuthentication(apiRequiresAuthentication); - } - - if (getOptionalValue(vars, "dashboardRequiresAuthentication", dashboardRequiresAuthentication) > 0) { - setWebserverDashboardRequiresAuthentication(dashboardRequiresAuthentication); - } - - if (getOptionalIntegerValue("setWebserverConfig", vars, "maxConcurrentConnections", maxConcurrentConnections) > 0) { - setWebserverMaxConcurrentConnections(maxConcurrentConnections); - } - }); - - luaCtx.writeFunction("showWebserverConfig", []() { - setLuaNoSideEffect(); - return getWebserverConfig(); - }); - - luaCtx.writeFunction("hashPassword", [](const std::string& password, boost::optional workFactor) { - if (workFactor) { - return hashPassword(password, *workFactor, CredentialsHolder::s_defaultParallelFactor, CredentialsHolder::s_defaultBlockSize); - } - return hashPassword(password); - }); - - luaCtx.writeFunction("controlSocket", [client, configCheck](const std::string& str) { - setLuaSideEffect(); - ComboAddress local(str, 5199); - - if (client || configCheck) { - g_serverControl = local; - return; - } - - g_consoleEnabled = true; -#if defined(HAVE_LIBSODIUM) || defined(HAVE_LIBCRYPTO) - if (g_configurationDone && g_consoleKey.empty()) { - warnlog("Warning, the console has been enabled via 'controlSocket()' but no key has been set with 'setKey()' so all connections will fail until a key has been set"); - } -#endif - - try { - auto sock = std::make_shared(local.sin4.sin_family, SOCK_STREAM, 0); - sock->bind(local, true); - sock->listen(5); - auto launch = [sock = std::move(sock), local]() { - std::thread consoleControlThread(controlThread, sock, local); - consoleControlThread.detach(); - }; - if (g_launchWork) { - g_launchWork->emplace_back(std::move(launch)); - } - else { - launch(); - } - } - catch (std::exception& e) { - g_outputBuffer = "Unable to bind to control socket on " + local.toStringWithPort() + ": " + e.what(); - errlog("Unable to bind to control socket on %s: %s", local.toStringWithPort(), e.what()); - } - }); - - luaCtx.writeFunction("addConsoleACL", [](const std::string& netmask) { - setLuaSideEffect(); -#if !defined(HAVE_LIBSODIUM) && !defined(HAVE_LIBCRYPTO) - warnlog("Allowing remote access to the console while neither libsodium not libcrypto support has been enabled is not secure, and will result in cleartext communications"); -#endif - - g_consoleACL.modify([netmask](NetmaskGroup& nmg) { nmg.addMask(netmask); }); - }); - - luaCtx.writeFunction("setConsoleACL", [](LuaTypeOrArrayOf inp) { - setLuaSideEffect(); - -#if !defined(HAVE_LIBSODIUM) && !defined(HAVE_LIBCRYPTO) - warnlog("Allowing remote access to the console while neither libsodium nor libcrypto support has not been enabled is not secure, and will result in cleartext communications"); -#endif - - NetmaskGroup nmg; - if (auto str = boost::get(&inp)) { - nmg.addMask(*str); - } - else - for (const auto& p : boost::get>(inp)) { - nmg.addMask(p.second); - } - g_consoleACL.setState(nmg); - }); - - luaCtx.writeFunction("showConsoleACL", []() { - setLuaNoSideEffect(); - -#if !defined(HAVE_LIBSODIUM) && !defined(HAVE_LIBCRYPTO) - warnlog("Allowing remote access to the console while neither libsodium nor libcrypto support has not been enabled is not secure, and will result in cleartext communications"); -#endif - - auto aclEntries = g_consoleACL.getLocal()->toStringVector(); - - for (const auto& entry : aclEntries) { - g_outputBuffer += entry + "\n"; - } - }); - - luaCtx.writeFunction("setConsoleMaximumConcurrentConnections", [](uint64_t max) { - setLuaSideEffect(); - setConsoleMaximumConcurrentConnections(max); - }); - - luaCtx.writeFunction("clearQueryCounters", []() { - unsigned int size{0}; - { - auto records = g_qcount.records.write_lock(); - size = records->size(); - records->clear(); - } - - boost::format fmt("%d records cleared from query counter buffer\n"); - g_outputBuffer = (fmt % size).str(); - }); - - luaCtx.writeFunction("getQueryCounters", [](boost::optional optMax) { - setLuaNoSideEffect(); - auto records = g_qcount.records.read_lock(); - g_outputBuffer = "query counting is currently: "; - g_outputBuffer += g_qcount.enabled ? "enabled" : "disabled"; - g_outputBuffer += (boost::format(" (%d records in buffer)\n") % records->size()).str(); - - boost::format fmt("%-3d %s: %d request(s)\n"); - uint64_t max = optMax ? *optMax : 10U; - uint64_t index{1}; - for (auto it = records->begin(); it != records->end() && index <= max; ++it, ++index) { - g_outputBuffer += (fmt % index % it->first % it->second).str(); - } - }); - - luaCtx.writeFunction("setQueryCount", [](bool enabled) { g_qcount.enabled = enabled; }); - - luaCtx.writeFunction("setQueryCountFilter", [](QueryCountFilter func) { - g_qcount.filter = std::move(func); - }); - - luaCtx.writeFunction("makeKey", []() { - setLuaNoSideEffect(); - g_outputBuffer = "setKey(" + dnsdist::crypto::authenticated::newKey() + ")\n"; - }); - - luaCtx.writeFunction("setKey", [](const std::string& key) { - if (!g_configurationDone && !g_consoleKey.empty()) { // this makes sure the commandline -k key prevails over dnsdist.conf - return; // but later setKeys() trump the -k value again - } -#if !defined(HAVE_LIBSODIUM) && !defined(HAVE_LIBCRYPTO) - warnlog("Calling setKey() while neither libsodium nor libcrypto support has been enabled is not secure, and will result in cleartext communications"); -#endif - - setLuaSideEffect(); - string newkey; - if (B64Decode(key, newkey) < 0) { - g_outputBuffer = string("Unable to decode ") + key + " as Base64"; - errlog("%s", g_outputBuffer); - } - else - g_consoleKey = std::move(newkey); - }); - - luaCtx.writeFunction("clearConsoleHistory", []() { - clearConsoleHistory(); - }); - - luaCtx.writeFunction("testCrypto", [](boost::optional optTestMsg) { - setLuaNoSideEffect(); -#if defined(HAVE_LIBSODIUM) || defined(HAVE_LIBCRYPTO) - try { - string testmsg; - - if (optTestMsg) { - testmsg = *optTestMsg; - } - else { - testmsg = "testStringForCryptoTests"; - } - - dnsdist::crypto::authenticated::Nonce nonce1; - dnsdist::crypto::authenticated::Nonce nonce2; - nonce1.init(); - nonce2 = nonce1; - string encrypted = dnsdist::crypto::authenticated::encryptSym(testmsg, g_consoleKey, nonce1); - string decrypted = dnsdist::crypto::authenticated::decryptSym(encrypted, g_consoleKey, nonce2); - - nonce1.increment(); - nonce2.increment(); - - encrypted = dnsdist::crypto::authenticated::encryptSym(testmsg, g_consoleKey, nonce1); - decrypted = dnsdist::crypto::authenticated::decryptSym(encrypted, g_consoleKey, nonce2); - - if (testmsg == decrypted) { - g_outputBuffer = "Everything is ok!\n"; - } - else { - g_outputBuffer = "Crypto failed.. (the decoded value does not match the cleartext one)\n"; - } - } - catch (const std::exception& e) { - g_outputBuffer = "Crypto failed: " + std::string(e.what()) + "\n"; - } - catch (...) { - g_outputBuffer = "Crypto failed..\n"; - } -#else - g_outputBuffer = "Crypto not available.\n"; -#endif - }); - - luaCtx.writeFunction("setTCPRecvTimeout", [](int timeout) { g_tcpRecvTimeout = timeout; }); - - luaCtx.writeFunction("setTCPSendTimeout", [](int timeout) { g_tcpSendTimeout = timeout; }); - - luaCtx.writeFunction("setUDPTimeout", [](int timeout) { DownstreamState::s_udpTimeout = timeout; }); - - luaCtx.writeFunction("setMaxUDPOutstanding", [](uint64_t max) { - if (!checkConfigurationTime("setMaxUDPOutstanding")) { - return; - } - - checkParameterBound("setMaxUDPOutstanding", max); - g_maxOutstanding = max; - }); - - luaCtx.writeFunction("setMaxTCPClientThreads", [](uint64_t max) { - if (!checkConfigurationTime("setMaxTCPClientThreads")) { - return; - } - g_maxTCPClientThreads = max; - }); - - luaCtx.writeFunction("setMaxTCPQueuedConnections", [](uint64_t max) { - if (!checkConfigurationTime("setMaxTCPQueuedConnections")) { - return; - } - g_maxTCPQueuedConnections = max; - }); - - luaCtx.writeFunction("setMaxTCPQueriesPerConnection", [](uint64_t max) { - if (!checkConfigurationTime("setMaxTCPQueriesPerConnection")) { - return; - } - g_maxTCPQueriesPerConn = max; - }); - - luaCtx.writeFunction("setMaxTCPConnectionsPerClient", [](uint64_t max) { - if (!checkConfigurationTime("setMaxTCPConnectionsPerClient")) { - return; - } - dnsdist::IncomingConcurrentTCPConnectionsManager::setMaxTCPConnectionsPerClient(max); - }); - - luaCtx.writeFunction("setMaxTCPConnectionDuration", [](uint64_t max) { - if (!checkConfigurationTime("setMaxTCPConnectionDuration")) { - return; - } - g_maxTCPConnectionDuration = max; - }); - - luaCtx.writeFunction("setMaxCachedTCPConnectionsPerDownstream", [](uint64_t max) { - setTCPDownstreamMaxIdleConnectionsPerBackend(max); - }); - -#if defined(HAVE_DNS_OVER_HTTPS) && defined(HAVE_NGHTTP2) - luaCtx.writeFunction("setMaxIdleDoHConnectionsPerDownstream", [](uint64_t max) { - setDoHDownstreamMaxIdleConnectionsPerBackend(max); - }); - - luaCtx.writeFunction("setOutgoingDoHWorkerThreads", [](uint64_t workers) { - if (!checkConfigurationTime("setOutgoingDoHWorkerThreads")) { - return; - } - g_outgoingDoHWorkerThreads = workers; - }); -#endif /* HAVE_DNS_OVER_HTTPS && HAVE_NGHTTP2 */ - - luaCtx.writeFunction("setOutgoingTLSSessionsCacheMaxTicketsPerBackend", [](uint64_t max) { - if (!checkConfigurationTime("setOutgoingTLSSessionsCacheMaxTicketsPerBackend")) { - return; - } - TLSSessionCache::setMaxTicketsPerBackend(max); - }); - - luaCtx.writeFunction("setOutgoingTLSSessionsCacheCleanupDelay", [](time_t delay) { - if (!checkConfigurationTime("setOutgoingTLSSessionsCacheCleanupDelay")) { - return; - } - TLSSessionCache::setCleanupDelay(delay); - }); - - luaCtx.writeFunction("setOutgoingTLSSessionsCacheMaxTicketValidity", [](time_t validity) { - if (!checkConfigurationTime("setOutgoingTLSSessionsCacheMaxTicketValidity")) { - return; - } - TLSSessionCache::setSessionValidity(validity); - }); - - luaCtx.writeFunction("getOutgoingTLSSessionCacheSize", []() { - setLuaNoSideEffect(); - return g_sessionCache.getSize(); - }); - - luaCtx.writeFunction("setCacheCleaningDelay", [](uint64_t delay) { - checkParameterBound("setCacheCleaningDelay", delay, std::numeric_limits::max()); - g_cacheCleaningDelay = delay; - }); - - luaCtx.writeFunction("setCacheCleaningPercentage", [](uint64_t percentage) { if (percentage < 100) g_cacheCleaningPercentage = percentage; else g_cacheCleaningPercentage = 100; }); - - luaCtx.writeFunction("setECSSourcePrefixV4", [](uint64_t prefix) { - checkParameterBound("setECSSourcePrefixV4", prefix, std::numeric_limits::max()); - g_ECSSourcePrefixV4 = prefix; - }); - - luaCtx.writeFunction("setECSSourcePrefixV6", [](uint64_t prefix) { - checkParameterBound("setECSSourcePrefixV6", prefix, std::numeric_limits::max()); - g_ECSSourcePrefixV6 = prefix; - }); - - luaCtx.writeFunction("setECSOverride", [](bool override) { g_ECSOverride = override; }); - -#ifndef DISABLE_DYNBLOCKS - luaCtx.writeFunction("showDynBlocks", []() { - setLuaNoSideEffect(); - auto slow = g_dynblockNMG.getCopy(); - struct timespec now; - gettime(&now); - boost::format fmt("%-24s %8d %8d %-10s %-20s %-10s %s\n"); - g_outputBuffer = (fmt % "What" % "Seconds" % "Blocks" % "Warning" % "Action" % "eBPF" % "Reason").str(); - for (const auto& e : slow) { - if (now < e.second.until) { - uint64_t counter = e.second.blocks; - if (g_defaultBPFFilter && e.second.bpf) { - counter += g_defaultBPFFilter->getHits(e.first.getNetwork()); - } - g_outputBuffer += (fmt % e.first.toString() % (e.second.until.tv_sec - now.tv_sec) % counter % (e.second.warning ? "true" : "false") % DNSAction::typeToString(e.second.action != DNSAction::Action::None ? e.second.action : g_dynBlockAction) % (g_defaultBPFFilter && e.second.bpf ? "*" : "") % e.second.reason).str(); - } - } - auto slow2 = g_dynblockSMT.getCopy(); - slow2.visit([&now, &fmt](const SuffixMatchTree& node) { - if (now < node.d_value.until) { - string dom("empty"); - if (!node.d_value.domain.empty()) - dom = node.d_value.domain.toString(); - g_outputBuffer += (fmt % dom % (node.d_value.until.tv_sec - now.tv_sec) % node.d_value.blocks % (node.d_value.warning ? "true" : "false") % DNSAction::typeToString(node.d_value.action != DNSAction::Action::None ? node.d_value.action : g_dynBlockAction) % "" % node.d_value.reason).str(); - } - }); - }); - - luaCtx.writeFunction("getDynamicBlocks", []() { - setLuaNoSideEffect(); - struct timespec now - { - }; - gettime(&now); - - LuaAssociativeTable entries; - auto fullCopy = g_dynblockNMG.getCopy(); - for (const auto& blockPair : fullCopy) { - const auto& requestor = blockPair.first; - if (!(now < blockPair.second.until)) { - continue; - } - auto entry = blockPair.second; - if (g_defaultBPFFilter && entry.bpf) { - entry.blocks += g_defaultBPFFilter->getHits(requestor.getNetwork()); - } - if (entry.action == DNSAction::Action::None) { - entry.action = g_dynBlockAction; - } - entries.emplace(requestor.toString(), std::move(entry)); - } - return entries; - }); - - luaCtx.writeFunction("getDynamicBlocksSMT", []() { - setLuaNoSideEffect(); - struct timespec now - { - }; - gettime(&now); - - LuaAssociativeTable entries; - auto fullCopy = g_dynblockSMT.getCopy(); - fullCopy.visit([&now, &entries](const SuffixMatchTree& node) { - if (!(now < node.d_value.until)) { - return; - } - auto entry = node.d_value; - string key("empty"); - if (!entry.domain.empty()) { - key = entry.domain.toString(); - } - if (entry.action == DNSAction::Action::None) { - entry.action = g_dynBlockAction; - } - entries.emplace(std::move(key), std::move(entry)); - }); - return entries; - }); - - luaCtx.writeFunction("clearDynBlocks", []() { - setLuaSideEffect(); - nmts_t nmg; - g_dynblockNMG.setState(nmg); - SuffixMatchTree smt; - g_dynblockSMT.setState(smt); - }); - -#ifndef DISABLE_DEPRECATED_DYNBLOCK - luaCtx.writeFunction("addDynBlocks", - [](const std::unordered_map& m, const std::string& msg, boost::optional seconds, boost::optional action) { - if (m.empty()) { - return; - } - setLuaSideEffect(); - auto slow = g_dynblockNMG.getCopy(); - struct timespec until, now; - gettime(&now); - until = now; - int actualSeconds = seconds ? *seconds : 10; - until.tv_sec += actualSeconds; - for (const auto& capair : m) { - unsigned int count = 0; - /* this legacy interface does not support ranges or ports, use DynBlockRulesGroup instead */ - AddressAndPortRange requestor(capair.first, capair.first.isIPv4() ? 32 : 128, 0); - auto got = slow.lookup(requestor); - bool expired = false; - if (got) { - if (until < got->second.until) { - // had a longer policy - continue; - } - if (now < got->second.until) { - // only inherit count on fresh query we are extending - count = got->second.blocks; - } - else { - expired = true; - } - } - DynBlock db{msg, until, DNSName(), (action ? *action : DNSAction::Action::None)}; - db.blocks = count; - if (!got || expired) { - warnlog("Inserting dynamic block for %s for %d seconds: %s", capair.first.toString(), actualSeconds, msg); - } - slow.insert(requestor).second = std::move(db); - } - g_dynblockNMG.setState(slow); - }); - - luaCtx.writeFunction("setDynBlocksAction", [](DNSAction::Action action) { - if (!checkConfigurationTime("setDynBlocksAction")) { - return; - } - if (action == DNSAction::Action::Drop || action == DNSAction::Action::NoOp || action == DNSAction::Action::Nxdomain || action == DNSAction::Action::Refused || action == DNSAction::Action::Truncate || action == DNSAction::Action::NoRecurse) { - g_dynBlockAction = action; - } - else { - errlog("Dynamic blocks action can only be Drop, NoOp, NXDomain, Refused, Truncate or NoRecurse!"); - g_outputBuffer = "Dynamic blocks action can only be Drop, NoOp, NXDomain, Refused, Truncate or NoRecurse!\n"; - } - }); -#endif /* DISABLE_DEPRECATED_DYNBLOCK */ - - luaCtx.writeFunction("addDynBlockSMT", - [](const LuaArray& names, const std::string& msg, boost::optional seconds, boost::optional action) { - if (names.empty()) { - return; - } - setLuaSideEffect(); - struct timespec now - { - }; - gettime(&now); - unsigned int actualSeconds = seconds ? *seconds : 10; - - bool needUpdate = false; - auto slow = g_dynblockSMT.getCopy(); - for (const auto& capair : names) { - DNSName domain(capair.second); - domain.makeUsLowerCase(); - - if (dnsdist::DynamicBlocks::addOrRefreshBlockSMT(slow, now, domain, msg, actualSeconds, action ? *action : DNSAction::Action::None, false)) { - needUpdate = true; - } - } - - if (needUpdate) { - g_dynblockSMT.setState(slow); - } - }); - - luaCtx.writeFunction("addDynamicBlock", - [](const boost::variant& clientIP, const std::string& msg, const boost::optional action, const boost::optional seconds, boost::optional clientIPMask, boost::optional clientIPPortMask) { - setLuaSideEffect(); - - ComboAddress clientIPCA; - if (clientIP.type() == typeid(ComboAddress)) { - clientIPCA = boost::get(clientIP); - } - else { - const auto& clientIPStr = boost::get(clientIP); - try { - clientIPCA = ComboAddress(clientIPStr); - } - catch (const std::exception& exp) { - errlog("addDynamicBlock: Unable to parse '%s': %s", clientIPStr, exp.what()); - return; - } - catch (const PDNSException& exp) { - errlog("addDynamicBlock: Unable to parse '%s': %s", clientIPStr, exp.reason); - return; - } - } - AddressAndPortRange target(clientIPCA, clientIPMask ? *clientIPMask : (clientIPCA.isIPv4() ? 32 : 128), clientIPPortMask ? *clientIPPortMask : 0); - unsigned int actualSeconds = seconds ? *seconds : 10; - - struct timespec now - { - }; - gettime(&now); - auto slow = g_dynblockNMG.getCopy(); - if (dnsdist::DynamicBlocks::addOrRefreshBlock(slow, now, target, msg, actualSeconds, action ? *action : DNSAction::Action::None, false, false)) { - g_dynblockNMG.setState(slow); - } - }); - - luaCtx.writeFunction("setDynBlocksPurgeInterval", [](uint64_t interval) { - DynBlockMaintenance::s_expiredDynBlocksPurgeInterval = interval; - }); -#endif /* DISABLE_DYNBLOCKS */ - -#ifdef HAVE_DNSCRYPT - luaCtx.writeFunction("addDNSCryptBind", [](const std::string& addr, const std::string& providerName, LuaTypeOrArrayOf certFiles, LuaTypeOrArrayOf keyFiles, boost::optional vars) { - if (!checkConfigurationTime("addDNSCryptBind")) { - return; - } - bool reusePort = false; - int tcpFastOpenQueueSize = 0; - int tcpListenQueueSize = 0; - uint64_t maxInFlightQueriesPerConn = 0; - uint64_t tcpMaxConcurrentConnections = 0; - std::string interface; - std::set cpus; - std::vector certKeys; - bool enableProxyProtocol = true; - - parseLocalBindVars(vars, reusePort, tcpFastOpenQueueSize, interface, cpus, tcpListenQueueSize, maxInFlightQueriesPerConn, tcpMaxConcurrentConnections, enableProxyProtocol); - checkAllParametersConsumed("addDNSCryptBind", vars); - - if (certFiles.type() == typeid(std::string) && keyFiles.type() == typeid(std::string)) { - auto certFile = boost::get(certFiles); - auto keyFile = boost::get(keyFiles); - certKeys.push_back({std::move(certFile), std::move(keyFile)}); - } - else if (certFiles.type() == typeid(LuaArray) && keyFiles.type() == typeid(LuaArray)) { - auto certFilesVect = boost::get>(certFiles); - auto keyFilesVect = boost::get>(keyFiles); - if (certFilesVect.size() == keyFilesVect.size()) { - for (size_t idx = 0; idx < certFilesVect.size(); idx++) { - certKeys.push_back({certFilesVect.at(idx).second, keyFilesVect.at(idx).second}); - } - } - else { - errlog("Error, mismatching number of certificates and keys in call to addDNSCryptBind!"); - g_outputBuffer = "Error, mismatching number of certificates and keys in call to addDNSCryptBind()!"; - return; - } - } - else { - errlog("Error, mismatching number of certificates and keys in call to addDNSCryptBind()!"); - g_outputBuffer = "Error, mismatching number of certificates and keys in call to addDNSCryptBind()!"; - return; - } - - try { - auto ctx = std::make_shared(providerName, certKeys); - - /* UDP */ - auto clientState = std::make_unique(ComboAddress(addr, 443), false, reusePort, tcpFastOpenQueueSize, interface, cpus, enableProxyProtocol); - clientState->dnscryptCtx = ctx; - g_dnsCryptLocals.push_back(ctx); - g_frontends.push_back(std::move(clientState)); - - /* TCP */ - clientState = std::make_unique(ComboAddress(addr, 443), true, reusePort, tcpFastOpenQueueSize, interface, cpus, enableProxyProtocol); - clientState->dnscryptCtx = std::move(ctx); - if (tcpListenQueueSize > 0) { - clientState->tcpListenQueueSize = tcpListenQueueSize; - } - if (maxInFlightQueriesPerConn > 0) { - clientState->d_maxInFlightQueriesPerConn = maxInFlightQueriesPerConn; - } - if (tcpMaxConcurrentConnections > 0) { - clientState->d_tcpConcurrentConnectionsLimit = tcpMaxConcurrentConnections; - } - - g_frontends.push_back(std::move(clientState)); - } - catch (const std::exception& e) { - errlog("Error during addDNSCryptBind() processing: %s", e.what()); - g_outputBuffer = "Error during addDNSCryptBind() processing: " + string(e.what()) + "\n"; - } - }); - - luaCtx.writeFunction("showDNSCryptBinds", []() { - setLuaNoSideEffect(); - ostringstream ret; - boost::format fmt("%1$-3d %2% %|25t|%3$-20.20s"); - ret << (fmt % "#" % "Address" % "Provider Name") << endl; - size_t idx = 0; - - std::unordered_set> contexts; - for (const auto& frontend : g_frontends) { - const std::shared_ptr ctx = frontend->dnscryptCtx; - if (!ctx || contexts.count(ctx) != 0) { - continue; - } - contexts.insert(ctx); - ret << (fmt % idx % frontend->local.toStringWithPort() % ctx->getProviderName()) << endl; - idx++; - } - - g_outputBuffer = ret.str(); - }); - - luaCtx.writeFunction("getDNSCryptBind", [](uint64_t idx) { - setLuaNoSideEffect(); - std::shared_ptr ret = nullptr; - if (idx < g_dnsCryptLocals.size()) { - ret = g_dnsCryptLocals.at(idx); - } - return ret; - }); - - luaCtx.writeFunction("getDNSCryptBindCount", []() { - setLuaNoSideEffect(); - return g_dnsCryptLocals.size(); - }); -#endif /* HAVE_DNSCRYPT */ - - luaCtx.writeFunction("showPools", []() { - setLuaNoSideEffect(); - try { - ostringstream ret; - boost::format fmt("%1$-20.20s %|25t|%2$20s %|25t|%3$20s %|50t|%4%"); - // 1 2 3 4 - ret << (fmt % "Name" % "Cache" % "ServerPolicy" % "Servers") << endl; - - const auto localPools = g_pools.getCopy(); - for (const auto& entry : localPools) { - const string& name = entry.first; - const std::shared_ptr pool = entry.second; - string cache = pool->packetCache != nullptr ? pool->packetCache->toString() : ""; - string policy = g_policy.getLocal()->getName(); - if (pool->policy != nullptr) { - policy = pool->policy->getName(); - } - string servers; - - const auto poolServers = pool->getServers(); - for (const auto& server : *poolServers) { - if (!servers.empty()) { - servers += ", "; - } - if (!server.second->getName().empty()) { - servers += server.second->getName(); - servers += " "; - } - servers += server.second->d_config.remote.toStringWithPort(); - } - - ret << (fmt % name % cache % policy % servers) << endl; - } - g_outputBuffer = ret.str(); - } - catch (std::exception& e) { - g_outputBuffer = e.what(); - throw; - } - }); - - luaCtx.writeFunction("getPoolNames", []() { - setLuaNoSideEffect(); - LuaArray ret; - int count = 1; - const auto localPools = g_pools.getCopy(); - for (const auto& entry : localPools) { - const string& name = entry.first; - ret.emplace_back(count++, name); - } - return ret; - }); - - luaCtx.writeFunction("getPool", [client](const string& poolName) { - if (client) { - return std::make_shared(); - } - auto localPools = g_pools.getCopy(); - std::shared_ptr pool = createPoolIfNotExists(localPools, poolName); - g_pools.setState(localPools); - return pool; - }); - - luaCtx.writeFunction("setVerbose", [](bool verbose) { g_verbose = verbose; }); - luaCtx.writeFunction("getVerbose", []() { return g_verbose; }); - luaCtx.writeFunction("setVerboseHealthChecks", [](bool verbose) { g_verboseHealthChecks = verbose; }); - luaCtx.writeFunction("setVerboseLogDestination", [](const std::string& dest) { - if (!checkConfigurationTime("setVerboseLogDestination")) { - return; - } - try { - auto stream = std::ofstream(dest.c_str()); - dnsdist::logging::LoggingConfiguration::setVerboseStream(std::move(stream)); - } - catch (const std::exception& e) { - errlog("Error while opening the verbose logging destination file %s: %s", dest, e.what()); - } - }); - luaCtx.writeFunction("setStructuredLogging", [](bool enable, boost::optional> options) { - std::string levelPrefix; - std::string timeFormat; - if (options) { - getOptionalValue(options, "levelPrefix", levelPrefix); - if (getOptionalValue(options, "timeFormat", timeFormat) == 1) { - if (timeFormat == "numeric") { - dnsdist::logging::LoggingConfiguration::setStructuredTimeFormat(dnsdist::logging::LoggingConfiguration::TimeFormat::Numeric); - } - else if (timeFormat == "ISO8601") { - dnsdist::logging::LoggingConfiguration::setStructuredTimeFormat(dnsdist::logging::LoggingConfiguration::TimeFormat::ISO8601); - } - else { - warnlog("Unknown value '%s' to setStructuredLogging's 'timeFormat' parameter", timeFormat); - } - } - checkAllParametersConsumed("setStructuredLogging", options); - } - - dnsdist::logging::LoggingConfiguration::setStructuredLogging(enable, levelPrefix); - }); - - luaCtx.writeFunction("setStaleCacheEntriesTTL", [](uint64_t ttl) { - checkParameterBound("setStaleCacheEntriesTTL", ttl, std::numeric_limits::max()); - g_staleCacheEntriesTTL = ttl; - }); - - luaCtx.writeFunction("showBinds", []() { - setLuaNoSideEffect(); - try { - ostringstream ret; - boost::format fmt("%1$-3d %2$-20.20s %|35t|%3$-20.20s %|57t|%4%"); - // 1 2 3 4 - ret << (fmt % "#" % "Address" % "Protocol" % "Queries") << endl; - - size_t counter = 0; - for (const auto& front : g_frontends) { - ret << (fmt % counter % front->local.toStringWithPort() % front->getType() % front->queries) << endl; - counter++; - } - g_outputBuffer = ret.str(); - } - catch (std::exception& e) { - g_outputBuffer = e.what(); - throw; - } - }); - - luaCtx.writeFunction("getBind", [](uint64_t num) { - setLuaNoSideEffect(); - ClientState* ret = nullptr; - if (num < g_frontends.size()) { - ret = g_frontends[num].get(); - } - return ret; - }); - - luaCtx.writeFunction("getBindCount", []() { - setLuaNoSideEffect(); - return g_frontends.size(); - }); - - luaCtx.writeFunction("help", [](boost::optional command) { - setLuaNoSideEffect(); - g_outputBuffer = ""; -#ifndef DISABLE_COMPLETION - for (const auto& keyword : g_consoleKeywords) { - if (!command) { - g_outputBuffer += keyword.toString() + "\n"; - } - else if (keyword.name == command) { - g_outputBuffer = keyword.toString() + "\n"; - return; - } - } -#endif /* DISABLE_COMPLETION */ - if (command) { - g_outputBuffer = "Nothing found for " + *command + "\n"; - } - }); - - luaCtx.writeFunction("showVersion", []() { - setLuaNoSideEffect(); - g_outputBuffer = "dnsdist " + std::string(VERSION) + "\n"; - }); - -#ifdef HAVE_EBPF - luaCtx.writeFunction("setDefaultBPFFilter", [](std::shared_ptr bpf) { - if (!checkConfigurationTime("setDefaultBPFFilter")) { - return; - } - g_defaultBPFFilter = std::move(bpf); - }); - - luaCtx.writeFunction("registerDynBPFFilter", [](std::shared_ptr dbpf) { - if (dbpf) { - g_dynBPFFilters.push_back(std::move(dbpf)); - } - }); - - luaCtx.writeFunction("unregisterDynBPFFilter", [](std::shared_ptr dbpf) { - if (dbpf) { - for (auto it = g_dynBPFFilters.begin(); it != g_dynBPFFilters.end(); it++) { - if (*it == dbpf) { - g_dynBPFFilters.erase(it); - break; - } - } - } - }); - -#ifndef DISABLE_DYNBLOCKS -#ifndef DISABLE_DEPRECATED_DYNBLOCK - luaCtx.writeFunction("addBPFFilterDynBlocks", [](const std::unordered_map& m, std::shared_ptr dynbpf, boost::optional seconds, boost::optional msg) { - if (!dynbpf) { - return; - } - setLuaSideEffect(); - struct timespec until, now; - clock_gettime(CLOCK_MONOTONIC, &now); - until = now; - int actualSeconds = seconds ? *seconds : 10; - until.tv_sec += actualSeconds; - for (const auto& capair : m) { - if (dynbpf->block(capair.first, until)) { - warnlog("Inserting eBPF dynamic block for %s for %d seconds: %s", capair.first.toString(), actualSeconds, msg ? *msg : ""); - } - } - }); -#endif /* DISABLE_DEPRECATED_DYNBLOCK */ -#endif /* DISABLE_DYNBLOCKS */ - -#endif /* HAVE_EBPF */ - - luaCtx.writeFunction()>("getStatisticsCounters", []() { - setLuaNoSideEffect(); - std::unordered_map res; - { - auto entries = dnsdist::metrics::g_stats.entries.read_lock(); - res.reserve(entries->size()); - for (const auto& entry : *entries) { - if (const auto& val = std::get_if(&entry.d_value)) { - res[entry.d_name] = (*val)->load(); - } - } - } - return res; - }); - - luaCtx.writeFunction("includeDirectory", [&luaCtx](const std::string& dirname) { - if (!checkConfigurationTime("includeDirectory")) { - return; - } - if (g_included) { - errlog("includeDirectory() cannot be used recursively!"); - g_outputBuffer = "includeDirectory() cannot be used recursively!\n"; - return; - } - - struct stat st; - if (stat(dirname.c_str(), &st)) { - errlog("The included directory %s does not exist!", dirname.c_str()); - g_outputBuffer = "The included directory " + dirname + " does not exist!"; - return; - } - - if (!S_ISDIR(st.st_mode)) { - errlog("The included directory %s is not a directory!", dirname.c_str()); - g_outputBuffer = "The included directory " + dirname + " is not a directory!"; - return; - } - - std::vector files; - auto directoryError = pdns::visit_directory(dirname, [&dirname, &files]([[maybe_unused]] ino_t inodeNumber, const std::string_view& name) { - if (boost::starts_with(name, ".")) { - return true; - } - if (boost::ends_with(name, ".conf")) { - std::ostringstream namebuf; - namebuf << dirname << "/" << name; - struct stat fileStat - { - }; - if (stat(namebuf.str().c_str(), &fileStat) == 0 && S_ISREG(fileStat.st_mode)) { - files.push_back(namebuf.str()); - } - } - return true; - }); - - if (directoryError) { - errlog("Error opening included directory: %s!", *directoryError); - g_outputBuffer = "Error opening included directory: " + *directoryError + "!"; - return; - } - - std::sort(files.begin(), files.end()); - - g_included = true; - - for (const auto& file : files) { - std::ifstream ifs(file); - if (!ifs) { - warnlog("Unable to read configuration from '%s'", file); - } - else { - vinfolog("Read configuration from '%s'", file); - } - - try { - luaCtx.executeCode(ifs); - } - catch (...) { - g_included = false; - throw; - } - - luaCtx.executeCode(ifs); - } - - g_included = false; - }); - - luaCtx.writeFunction("setAPIWritable", [](bool writable, boost::optional apiConfigDir) { - setLuaSideEffect(); - g_apiReadWrite = writable; - if (apiConfigDir) { - if (!(*apiConfigDir).empty()) { - g_apiConfigDirectory = *apiConfigDir; - } - else { - errlog("The API configuration directory value cannot be empty!"); - g_outputBuffer = "The API configuration directory value cannot be empty!"; - } - } - }); - - luaCtx.writeFunction("setServFailWhenNoServer", [](bool servfail) { - setLuaSideEffect(); - g_servFailOnNoPolicy = servfail; - }); - - luaCtx.writeFunction("setRoundRobinFailOnNoServer", [](bool fail) { - setLuaSideEffect(); - g_roundrobinFailOnNoServer = fail; - }); - - luaCtx.writeFunction("setConsistentHashingBalancingFactor", [](double factor) { - setLuaSideEffect(); - if (factor >= 1.0) { - g_consistentHashBalancingFactor = factor; - } - else { - errlog("Invalid value passed to setConsistentHashingBalancingFactor()!"); - g_outputBuffer = "Invalid value passed to setConsistentHashingBalancingFactor()!\n"; - return; - } - }); - - luaCtx.writeFunction("setWeightedBalancingFactor", [](double factor) { - setLuaSideEffect(); - if (factor >= 1.0) { - g_weightedBalancingFactor = factor; - } - else { - errlog("Invalid value passed to setWeightedBalancingFactor()!"); - g_outputBuffer = "Invalid value passed to setWeightedBalancingFactor()!\n"; - return; - } - }); - - luaCtx.writeFunction("setRingBuffersSize", [client](uint64_t capacity, boost::optional numberOfShards) { - setLuaSideEffect(); - if (!checkConfigurationTime("setRingBuffersSize")) { - return; - } - if (!client) { - g_rings.setCapacity(capacity, numberOfShards ? *numberOfShards : 10); - } - else { - g_rings.setCapacity(0, 1); - } - }); - - luaCtx.writeFunction("setRingBuffersLockRetries", [](uint64_t retries) { - setLuaSideEffect(); - g_rings.setNumberOfLockRetries(retries); - }); - - luaCtx.writeFunction("setRingBuffersOptions", [](const LuaAssociativeTable>& options) { - setLuaSideEffect(); - if (!checkConfigurationTime("setRingBuffersOptions")) { - return; - } - if (options.count("lockRetries") > 0) { - auto retries = boost::get(options.at("lockRetries")); - g_rings.setNumberOfLockRetries(retries); - } - if (options.count("recordQueries") > 0) { - auto record = boost::get(options.at("recordQueries")); - g_rings.setRecordQueries(record); - } - if (options.count("recordResponses") > 0) { - auto record = boost::get(options.at("recordResponses")); - g_rings.setRecordResponses(record); - } - }); - - luaCtx.writeFunction("setWHashedPertubation", [](uint64_t perturb) { - setLuaSideEffect(); - checkParameterBound("setWHashedPertubation", perturb, std::numeric_limits::max()); - g_hashperturb = perturb; - }); - - luaCtx.writeFunction("setTCPInternalPipeBufferSize", [](uint64_t size) { g_tcpInternalPipeBufferSize = size; }); - luaCtx.writeFunction("setTCPFastOpenKey", [](const std::string& keyString) { - setLuaSideEffect(); - uint32_t key[4] = {}; - auto ret = sscanf(keyString.c_str(), "%" SCNx32 "-%" SCNx32 "-%" SCNx32 "-%" SCNx32, &key[0], &key[1], &key[2], &key[3]); - if (ret != 4) { - g_outputBuffer = "Invalid value passed to setTCPFastOpenKey()!\n"; - return; - } - extern vector g_TCPFastOpenKey; - for (const auto i : key) { - g_TCPFastOpenKey.push_back(i); - } - }); - -#ifdef HAVE_NET_SNMP - luaCtx.writeFunction("snmpAgent", [client, configCheck](bool enableTraps, boost::optional daemonSocket) { - if (client || configCheck) { - return; - } - if (!checkConfigurationTime("snmpAgent")) { - return; - } - if (g_snmpEnabled) { - errlog("snmpAgent() cannot be used twice!"); - g_outputBuffer = "snmpAgent() cannot be used twice!\n"; - return; - } - - g_snmpEnabled = true; - g_snmpTrapsEnabled = enableTraps; - g_snmpAgent = new DNSDistSNMPAgent("dnsdist", daemonSocket ? *daemonSocket : std::string()); - }); - - luaCtx.writeFunction("sendCustomTrap", [](const std::string& str) { - if (g_snmpAgent && g_snmpTrapsEnabled) { - g_snmpAgent->sendCustomTrap(str); - } - }); -#endif /* HAVE_NET_SNMP */ - -#ifndef DISABLE_POLICIES_BINDINGS - luaCtx.writeFunction("setServerPolicy", [](const ServerPolicy& policy) { - setLuaSideEffect(); - g_policy.setState(policy); - }); - - luaCtx.writeFunction("setServerPolicyLua", [](const string& name, ServerPolicy::policyfunc_t policy) { - setLuaSideEffect(); - g_policy.setState(ServerPolicy{name, policy, true}); - }); - - luaCtx.writeFunction("setServerPolicyLuaFFI", [](const string& name, ServerPolicy::ffipolicyfunc_t policy) { - setLuaSideEffect(); - auto pol = ServerPolicy(name, policy); - g_policy.setState(std::move(pol)); - }); - - luaCtx.writeFunction("setServerPolicyLuaFFIPerThread", [](const string& name, const std::string& policyCode) { - setLuaSideEffect(); - auto pol = ServerPolicy(name, policyCode); - g_policy.setState(std::move(pol)); - }); - - luaCtx.writeFunction("showServerPolicy", []() { - setLuaSideEffect(); - g_outputBuffer = g_policy.getLocal()->getName() + "\n"; - }); - - luaCtx.writeFunction("setPoolServerPolicy", [](ServerPolicy policy, const string& pool) { - setLuaSideEffect(); - auto localPools = g_pools.getCopy(); - setPoolPolicy(localPools, pool, std::make_shared(std::move(policy))); - g_pools.setState(localPools); - }); - - luaCtx.writeFunction("setPoolServerPolicyLua", [](const string& name, ServerPolicy::policyfunc_t policy, const string& pool) { - setLuaSideEffect(); - auto localPools = g_pools.getCopy(); - setPoolPolicy(localPools, pool, std::make_shared(ServerPolicy{name, std::move(policy), true})); - g_pools.setState(localPools); - }); - - luaCtx.writeFunction("setPoolServerPolicyLuaFFI", [](const string& name, ServerPolicy::ffipolicyfunc_t policy, const string& pool) { - setLuaSideEffect(); - auto localPools = g_pools.getCopy(); - setPoolPolicy(localPools, pool, std::make_shared(ServerPolicy{name, std::move(policy)})); - g_pools.setState(localPools); - }); - - luaCtx.writeFunction("setPoolServerPolicyLuaFFIPerThread", [](const string& name, const std::string& policyCode, const std::string& pool) { - setLuaSideEffect(); - auto localPools = g_pools.getCopy(); - setPoolPolicy(localPools, pool, std::make_shared(ServerPolicy{name, policyCode})); - g_pools.setState(localPools); - }); - - luaCtx.writeFunction("showPoolServerPolicy", [](const std::string& pool) { - setLuaSideEffect(); - auto localPools = g_pools.getCopy(); - auto poolObj = getPool(localPools, pool); - if (poolObj->policy == nullptr) { - g_outputBuffer = g_policy.getLocal()->getName() + "\n"; - } - else { - g_outputBuffer = poolObj->policy->getName() + "\n"; - } - }); -#endif /* DISABLE_POLICIES_BINDINGS */ - - luaCtx.writeFunction("setTCPDownstreamCleanupInterval", [](uint64_t interval) { - setLuaSideEffect(); - checkParameterBound("setTCPDownstreamCleanupInterval", interval); - setTCPDownstreamCleanupInterval(interval); - }); - -#if defined(HAVE_DNS_OVER_HTTPS) && defined(HAVE_NGHTTP2) - luaCtx.writeFunction("setDoHDownstreamCleanupInterval", [](uint64_t interval) { - setLuaSideEffect(); - checkParameterBound("setDoHDownstreamCleanupInterval", interval); - setDoHDownstreamCleanupInterval(interval); - }); -#endif /* HAVE_DNS_OVER_HTTPS && HAVE_NGHTTP2 */ - - luaCtx.writeFunction("setTCPDownstreamMaxIdleTime", [](uint64_t max) { - setLuaSideEffect(); - checkParameterBound("setTCPDownstreamMaxIdleTime", max); - setTCPDownstreamMaxIdleTime(max); - }); - -#if defined(HAVE_DNS_OVER_HTTPS) && defined(HAVE_NGHTTP2) - luaCtx.writeFunction("setDoHDownstreamMaxIdleTime", [](uint64_t max) { - setLuaSideEffect(); - checkParameterBound("setDoHDownstreamMaxIdleTime", max); - setDoHDownstreamMaxIdleTime(max); - }); -#endif /* HAVE_DNS_OVER_HTTPS && HAVE_NGHTTP2 */ - - luaCtx.writeFunction("setConsoleConnectionsLogging", [](bool enabled) { - g_logConsoleConnections = enabled; - }); - - luaCtx.writeFunction("setConsoleOutputMaxMsgSize", [](uint64_t size) { - checkParameterBound("setConsoleOutputMaxMsgSize", size, std::numeric_limits::max()); - g_consoleOutputMsgMaxSize = size; - }); - - luaCtx.writeFunction("setProxyProtocolACL", [](LuaTypeOrArrayOf inp) { - if (!checkConfigurationTime("setProxyProtocolACL")) { - return; - } - setLuaSideEffect(); - NetmaskGroup nmg; - if (auto str = boost::get(&inp)) { - nmg.addMask(*str); - } - else { - for (const auto& p : boost::get>(inp)) { - nmg.addMask(p.second); - } - } - g_proxyProtocolACL = std::move(nmg); - }); - - luaCtx.writeFunction("setProxyProtocolApplyACLToProxiedClients", [](bool apply) { - if (!checkConfigurationTime("setProxyProtocolApplyACLToProxiedClients")) { - return; - } - setLuaSideEffect(); - g_applyACLToProxiedClients = apply; - }); - - luaCtx.writeFunction("setProxyProtocolMaximumPayloadSize", [](uint64_t size) { - if (!checkConfigurationTime("setProxyProtocolMaximumPayloadSize")) { - return; - } - setLuaSideEffect(); - g_proxyProtocolMaximumSize = std::max(static_cast(16), size); - }); - -#ifndef DISABLE_RECVMMSG - luaCtx.writeFunction("setUDPMultipleMessagesVectorSize", [](uint64_t vSize) { - if (!checkConfigurationTime("setUDPMultipleMessagesVectorSize")) { - return; - } -#if defined(HAVE_RECVMMSG) && defined(HAVE_SENDMMSG) && defined(MSG_WAITFORONE) - setLuaSideEffect(); - g_udpVectorSize = vSize; -#else - errlog("recvmmsg() support is not available!"); - g_outputBuffer = "recvmmsg support is not available!\n"; -#endif - }); -#endif /* DISABLE_RECVMMSG */ - - luaCtx.writeFunction("setAddEDNSToSelfGeneratedResponses", [](bool add) { - g_addEDNSToSelfGeneratedResponses = add; - }); - - luaCtx.writeFunction("setPayloadSizeOnSelfGeneratedAnswers", [](uint64_t payloadSize) { - if (payloadSize < 512) { - warnlog("setPayloadSizeOnSelfGeneratedAnswers() is set too low, using 512 instead!"); - g_outputBuffer = "setPayloadSizeOnSelfGeneratedAnswers() is set too low, using 512 instead!"; - payloadSize = 512; - } - if (payloadSize > s_udpIncomingBufferSize) { - warnlog("setPayloadSizeOnSelfGeneratedAnswers() is set too high, capping to %d instead!", s_udpIncomingBufferSize); - g_outputBuffer = "setPayloadSizeOnSelfGeneratedAnswers() is set too high, capping to " + std::to_string(s_udpIncomingBufferSize) + " instead"; - payloadSize = s_udpIncomingBufferSize; - } - g_PayloadSizeSelfGenAnswers = payloadSize; - }); - -#ifndef DISABLE_SECPOLL - luaCtx.writeFunction("showSecurityStatus", []() { - setLuaNoSideEffect(); - g_outputBuffer = std::to_string(dnsdist::metrics::g_stats.securityStatus) + "\n"; - }); - - luaCtx.writeFunction("setSecurityPollSuffix", [](const std::string& suffix) { - if (!checkConfigurationTime("setSecurityPollSuffix")) { - return; - } - g_secPollSuffix = suffix; - }); - - luaCtx.writeFunction("setSecurityPollInterval", [](time_t newInterval) { - if (newInterval <= 0) { - warnlog("setSecurityPollInterval() should be > 0, skipping"); - g_outputBuffer = "setSecurityPollInterval() should be > 0, skipping"; - } - - g_secPollInterval = newInterval; - }); -#endif /* DISABLE_SECPOLL */ - - luaCtx.writeFunction("setSyslogFacility", [](boost::variant facility) { - if (!checkConfigurationTime("setSyslogFacility")) { - return; - } - setLuaSideEffect(); - if (facility.type() == typeid(std::string)) { - static std::map const facilities = { - {"local0", LOG_LOCAL0}, - {"log_local0", LOG_LOCAL0}, - {"local1", LOG_LOCAL1}, - {"log_local1", LOG_LOCAL1}, - {"local2", LOG_LOCAL2}, - {"log_local2", LOG_LOCAL2}, - {"local3", LOG_LOCAL3}, - {"log_local3", LOG_LOCAL3}, - {"local4", LOG_LOCAL4}, - {"log_local4", LOG_LOCAL4}, - {"local5", LOG_LOCAL5}, - {"log_local5", LOG_LOCAL5}, - {"local6", LOG_LOCAL6}, - {"log_local6", LOG_LOCAL6}, - {"local7", LOG_LOCAL7}, - {"log_local7", LOG_LOCAL7}, - /* most of these likely make very little sense - for dnsdist, but why not? */ - {"kern", LOG_KERN}, - {"log_kern", LOG_KERN}, - {"user", LOG_USER}, - {"log_user", LOG_USER}, - {"mail", LOG_MAIL}, - {"log_mail", LOG_MAIL}, - {"daemon", LOG_DAEMON}, - {"log_daemon", LOG_DAEMON}, - {"auth", LOG_AUTH}, - {"log_auth", LOG_AUTH}, - {"syslog", LOG_SYSLOG}, - {"log_syslog", LOG_SYSLOG}, - {"lpr", LOG_LPR}, - {"log_lpr", LOG_LPR}, - {"news", LOG_NEWS}, - {"log_news", LOG_NEWS}, - {"uucp", LOG_UUCP}, - {"log_uucp", LOG_UUCP}, - {"cron", LOG_CRON}, - {"log_cron", LOG_CRON}, - {"authpriv", LOG_AUTHPRIV}, - {"log_authpriv", LOG_AUTHPRIV}, - {"ftp", LOG_FTP}, - {"log_ftp", LOG_FTP}}; - auto facilityStr = boost::get(facility); - toLowerInPlace(facilityStr); - auto it = facilities.find(facilityStr); - if (it == facilities.end()) { - g_outputBuffer = "Unknown facility '" + facilityStr + "' passed to setSyslogFacility()!\n"; - return; - } - setSyslogFacility(it->second); - } - else { - setSyslogFacility(boost::get(facility)); - } - }); - - typedef std::unordered_map tlscertificateopts_t; - luaCtx.writeFunction("newTLSCertificate", [client](const std::string& cert, boost::optional opts) { - std::shared_ptr result = nullptr; - if (client) { - return result; - } -#if defined(HAVE_DNS_OVER_TLS) || defined(HAVE_DNS_OVER_HTTPS) - std::optional key, password; - if (opts) { - if (opts->count("key")) { - key = boost::get((*opts)["key"]); - } - if (opts->count("password")) { - password = boost::get((*opts)["password"]); - } - } - result = std::make_shared(cert, std::move(key), std::move(password)); -#endif - return result; - }); - - luaCtx.writeFunction("addDOHLocal", [client](const std::string& addr, boost::optional, LuaArray, LuaArray>>> certFiles, boost::optional>> keyFiles, boost::optional> urls, boost::optional vars) { - if (client) { - return; - } -#ifdef HAVE_DNS_OVER_HTTPS - if (!checkConfigurationTime("addDOHLocal")) { - return; - } - setLuaSideEffect(); - - auto frontend = std::make_shared(); - if (getOptionalValue(vars, "library", frontend->d_library) == 0) { -#ifdef HAVE_NGHTTP2 - frontend->d_library = "nghttp2"; -#else /* HAVE_NGHTTP2 */ - frontend->d_library = "h2o"; -#endif /* HAVE_NGHTTP2 */ - } - if (frontend->d_library == "h2o") { -#ifdef HAVE_LIBH2OEVLOOP - frontend = std::make_shared(); - // we _really_ need to set it again, as we just replaced the generic frontend by a new one - frontend->d_library = "h2o"; -#else /* HAVE_LIBH2OEVLOOP */ - errlog("DOH bind %s is configured to use libh2o but the library is not available", addr); - return; -#endif /* HAVE_LIBH2OEVLOOP */ - } - else if (frontend->d_library == "nghttp2") { -#ifndef HAVE_NGHTTP2 - errlog("DOH bind %s is configured to use nghttp2 but the library is not available", addr); - return; -#endif /* HAVE_NGHTTP2 */ - } - else { - errlog("DOH bind %s is configured to use an unknown library ('%s')", addr, frontend->d_library); - return; - } - - bool useTLS = true; - if (certFiles && !certFiles->empty()) { - if (!loadTLSCertificateAndKeys("addDOHLocal", frontend->d_tlsContext.d_tlsConfig.d_certKeyPairs, *certFiles, *keyFiles)) { - return; - } - - frontend->d_tlsContext.d_addr = ComboAddress(addr, 443); - } - else { - frontend->d_tlsContext.d_addr = ComboAddress(addr, 80); - infolog("No certificate provided for DoH endpoint %s, running in DNS over HTTP mode instead of DNS over HTTPS", frontend->d_tlsContext.d_addr.toStringWithPort()); - useTLS = false; - } - - if (urls) { - if (urls->type() == typeid(std::string)) { - frontend->d_urls.insert(boost::get(*urls)); - } - else if (urls->type() == typeid(LuaArray)) { - auto urlsVect = boost::get>(*urls); - for (const auto& p : urlsVect) { - frontend->d_urls.insert(p.second); - } - } - } - else { - frontend->d_urls.insert("/dns-query"); - } - - bool reusePort = false; - int tcpFastOpenQueueSize = 0; - int tcpListenQueueSize = 0; - uint64_t maxInFlightQueriesPerConn = 0; - uint64_t tcpMaxConcurrentConnections = 0; - std::string interface; - std::set cpus; - std::vector> additionalAddresses; - bool enableProxyProtocol = true; - - if (vars) { - parseLocalBindVars(vars, reusePort, tcpFastOpenQueueSize, interface, cpus, tcpListenQueueSize, maxInFlightQueriesPerConn, tcpMaxConcurrentConnections, enableProxyProtocol); - getOptionalValue(vars, "idleTimeout", frontend->d_idleTimeout); - getOptionalValue(vars, "serverTokens", frontend->d_serverTokens); - getOptionalValue(vars, "provider", frontend->d_tlsContext.d_provider); - boost::algorithm::to_lower(frontend->d_tlsContext.d_provider); - getOptionalValue(vars, "proxyProtocolOutsideTLS", frontend->d_tlsContext.d_proxyProtocolOutsideTLS); - - LuaAssociativeTable customResponseHeaders; - if (getOptionalValue(vars, "customResponseHeaders", customResponseHeaders) > 0) { - for (auto const& headerMap : customResponseHeaders) { - auto headerResponse = std::pair(boost::to_lower_copy(headerMap.first), headerMap.second); - frontend->d_customResponseHeaders.insert(headerResponse); - } - } - - getOptionalValue(vars, "sendCacheControlHeaders", frontend->d_sendCacheControlHeaders); - getOptionalValue(vars, "keepIncomingHeaders", frontend->d_keepIncomingHeaders); - getOptionalValue(vars, "trustForwardedForHeader", frontend->d_trustForwardedForHeader); - getOptionalValue(vars, "earlyACLDrop", frontend->d_earlyACLDrop); - getOptionalValue(vars, "internalPipeBufferSize", frontend->d_internalPipeBufferSize); - getOptionalValue(vars, "exactPathMatching", frontend->d_exactPathMatching); - - LuaArray addresses; - if (getOptionalValue(vars, "additionalAddresses", addresses) > 0) { - for (const auto& [_, add] : addresses) { - try { - ComboAddress address(add); - additionalAddresses.emplace_back(address, -1); - } - catch (const PDNSException& e) { - errlog("Unable to parse additional address %s for DOH bind: %s", add, e.reason); - return; - } - } - } - - parseTLSConfig(frontend->d_tlsContext.d_tlsConfig, "addDOHLocal", vars); - - bool ignoreTLSConfigurationErrors = false; - if (getOptionalValue(vars, "ignoreTLSConfigurationErrors", ignoreTLSConfigurationErrors) > 0 && ignoreTLSConfigurationErrors) { - // we are asked to try to load the certificates so we can return a potential error - // and properly ignore the frontend before actually launching it - try { - std::map ocspResponses = {}; - auto ctx = libssl_init_server_context(frontend->d_tlsContext.d_tlsConfig, ocspResponses); - } - catch (const std::runtime_error& e) { - errlog("Ignoring DoH frontend: '%s'", e.what()); - return; - } - } - - checkAllParametersConsumed("addDOHLocal", vars); - } - - if (useTLS && frontend->d_library == "nghttp2") { - if (!frontend->d_tlsContext.d_provider.empty()) { - vinfolog("Loading TLS provider '%s'", frontend->d_tlsContext.d_provider); - } - else { -#ifdef HAVE_LIBSSL - const std::string provider("openssl"); -#else - const std::string provider("gnutls"); -#endif - vinfolog("Loading default TLS provider '%s'", provider); - } - } - - g_dohlocals.push_back(frontend); - auto clientState = std::make_unique(frontend->d_tlsContext.d_addr, true, reusePort, tcpFastOpenQueueSize, interface, cpus, enableProxyProtocol); - clientState->dohFrontend = std::move(frontend); - clientState->d_additionalAddresses = std::move(additionalAddresses); - - if (tcpListenQueueSize > 0) { - clientState->tcpListenQueueSize = tcpListenQueueSize; - } - if (tcpMaxConcurrentConnections > 0) { - clientState->d_tcpConcurrentConnectionsLimit = tcpMaxConcurrentConnections; - } - g_frontends.push_back(std::move(clientState)); -#else /* HAVE_DNS_OVER_HTTPS */ - throw std::runtime_error("addDOHLocal() called but DNS over HTTPS support is not present!"); -#endif /* HAVE_DNS_OVER_HTTPS */ - }); - - // NOLINTNEXTLINE(performance-unnecessary-value-param): somehow clang-tidy gets confused about the fact vars could be const while it cannot - luaCtx.writeFunction("addDOH3Local", [client](const std::string& addr, const boost::variant, LuaArray, LuaArray>>& certFiles, const boost::variant>& keyFiles, boost::optional vars) { - if (client) { - return; - } -#ifdef HAVE_DNS_OVER_HTTP3 - if (!checkConfigurationTime("addDOH3Local")) { - return; - } - setLuaSideEffect(); - - auto frontend = std::make_shared(); - if (!loadTLSCertificateAndKeys("addDOH3Local", frontend->d_quicheParams.d_tlsConfig.d_certKeyPairs, certFiles, keyFiles)) { - return; - } - frontend->d_local = ComboAddress(addr, 443); - - bool reusePort = false; - int tcpFastOpenQueueSize = 0; - int tcpListenQueueSize = 0; - uint64_t maxInFlightQueriesPerConn = 0; - uint64_t tcpMaxConcurrentConnections = 0; - std::string interface; - std::set cpus; - std::vector> additionalAddresses; - bool enableProxyProtocol = true; - - if (vars) { - parseLocalBindVars(vars, reusePort, tcpFastOpenQueueSize, interface, cpus, tcpListenQueueSize, maxInFlightQueriesPerConn, tcpMaxConcurrentConnections, enableProxyProtocol); - if (maxInFlightQueriesPerConn > 0) { - frontend->d_quicheParams.d_maxInFlight = maxInFlightQueriesPerConn; - } - getOptionalValue(vars, "internalPipeBufferSize", frontend->d_internalPipeBufferSize); - getOptionalValue(vars, "idleTimeout", frontend->d_quicheParams.d_idleTimeout); - getOptionalValue(vars, "keyLogFile", frontend->d_quicheParams.d_keyLogFile); - { - std::string valueStr; - if (getOptionalValue(vars, "congestionControlAlgo", valueStr) > 0) { - if (dnsdist::doq::s_available_cc_algorithms.count(valueStr) > 0) { - frontend->d_quicheParams.d_ccAlgo = valueStr; - } - else { - warnlog("Ignoring unknown value '%s' for 'congestionControlAlgo' on 'addDOH3Local'", valueStr); - } - } - } - parseTLSConfig(frontend->d_quicheParams.d_tlsConfig, "addDOH3Local", vars); - - bool ignoreTLSConfigurationErrors = false; - if (getOptionalValue(vars, "ignoreTLSConfigurationErrors", ignoreTLSConfigurationErrors) > 0 && ignoreTLSConfigurationErrors) { - // we are asked to try to load the certificates so we can return a potential error - // and properly ignore the frontend before actually launching it - try { - std::map ocspResponses = {}; - auto ctx = libssl_init_server_context(frontend->d_quicheParams.d_tlsConfig, ocspResponses); - } - catch (const std::runtime_error& e) { - errlog("Ignoring DoH3 frontend: '%s'", e.what()); - return; - } - } - - checkAllParametersConsumed("addDOH3Local", vars); - } - g_doh3locals.push_back(frontend); - auto clientState = std::make_unique(frontend->d_local, false, reusePort, tcpFastOpenQueueSize, interface, cpus, enableProxyProtocol); - clientState->doh3Frontend = frontend; - clientState->d_additionalAddresses = std::move(additionalAddresses); - - g_frontends.push_back(std::move(clientState)); -#else - throw std::runtime_error("addDOH3Local() called but DNS over HTTP/3 support is not present!"); -#endif - }); - - // NOLINTNEXTLINE(performance-unnecessary-value-param): somehow clang-tidy gets confused about the fact vars could be const while it cannot - luaCtx.writeFunction("addDOQLocal", [client](const std::string& addr, const boost::variant, LuaArray, LuaArray>>& certFiles, const boost::variant>& keyFiles, boost::optional vars) { - if (client) { - return; - } -#ifdef HAVE_DNS_OVER_QUIC - if (!checkConfigurationTime("addDOQLocal")) { - return; - } - setLuaSideEffect(); - - auto frontend = std::make_shared(); - if (!loadTLSCertificateAndKeys("addDOQLocal", frontend->d_quicheParams.d_tlsConfig.d_certKeyPairs, certFiles, keyFiles)) { - return; - } - frontend->d_local = ComboAddress(addr, 853); - - bool reusePort = false; - int tcpFastOpenQueueSize = 0; - int tcpListenQueueSize = 0; - uint64_t maxInFlightQueriesPerConn = 0; - uint64_t tcpMaxConcurrentConnections = 0; - std::string interface; - std::set cpus; - std::vector> additionalAddresses; - bool enableProxyProtocol = true; - - if (vars) { - parseLocalBindVars(vars, reusePort, tcpFastOpenQueueSize, interface, cpus, tcpListenQueueSize, maxInFlightQueriesPerConn, tcpMaxConcurrentConnections, enableProxyProtocol); - if (maxInFlightQueriesPerConn > 0) { - frontend->d_quicheParams.d_maxInFlight = maxInFlightQueriesPerConn; - } - getOptionalValue(vars, "internalPipeBufferSize", frontend->d_internalPipeBufferSize); - getOptionalValue(vars, "idleTimeout", frontend->d_quicheParams.d_idleTimeout); - getOptionalValue(vars, "keyLogFile", frontend->d_quicheParams.d_keyLogFile); - { - std::string valueStr; - if (getOptionalValue(vars, "congestionControlAlgo", valueStr) > 0) { - if (dnsdist::doq::s_available_cc_algorithms.count(valueStr) > 0) { - frontend->d_quicheParams.d_ccAlgo = std::move(valueStr); - } - else { - warnlog("Ignoring unknown value '%s' for 'congestionControlAlgo' on 'addDOQLocal'", valueStr); - } - } - } - parseTLSConfig(frontend->d_quicheParams.d_tlsConfig, "addDOQLocal", vars); - - bool ignoreTLSConfigurationErrors = false; - if (getOptionalValue(vars, "ignoreTLSConfigurationErrors", ignoreTLSConfigurationErrors) > 0 && ignoreTLSConfigurationErrors) { - // we are asked to try to load the certificates so we can return a potential error - // and properly ignore the frontend before actually launching it - try { - std::map ocspResponses = {}; - auto ctx = libssl_init_server_context(frontend->d_quicheParams.d_tlsConfig, ocspResponses); - } - catch (const std::runtime_error& e) { - errlog("Ignoring DoQ frontend: '%s'", e.what()); - return; - } - } - - checkAllParametersConsumed("addDOQLocal", vars); - } - g_doqlocals.push_back(frontend); - auto clientState = std::make_unique(frontend->d_local, false, reusePort, tcpFastOpenQueueSize, interface, cpus, enableProxyProtocol); - clientState->doqFrontend = std::move(frontend); - clientState->d_additionalAddresses = std::move(additionalAddresses); - - g_frontends.push_back(std::move(clientState)); -#else - throw std::runtime_error("addDOQLocal() called but DNS over QUIC support is not present!"); -#endif - }); - - luaCtx.writeFunction("showDOQFrontends", []() { -#ifdef HAVE_DNS_OVER_QUIC - setLuaNoSideEffect(); - try { - ostringstream ret; - boost::format fmt("%-3d %-20.20s %-15d %-15d %-15d %-15d"); - ret << (fmt % "#" % "Address" % "Bad Version" % "Invalid Token" % "Errors" % "Valid") << endl; - size_t counter = 0; - for (const auto& ctx : g_doqlocals) { - ret << (fmt % counter % ctx->d_local.toStringWithPort() % ctx->d_doqUnsupportedVersionErrors % ctx->d_doqInvalidTokensReceived % ctx->d_errorResponses % ctx->d_validResponses) << endl; - counter++; - } - g_outputBuffer = ret.str(); - } - catch (const std::exception& e) { - g_outputBuffer = e.what(); - throw; - } -#else - g_outputBuffer = "DNS over QUIC support is not present!\n"; -#endif - }); - - luaCtx.writeFunction("showDOHFrontends", []() { -#ifdef HAVE_DNS_OVER_HTTPS - setLuaNoSideEffect(); - try { - ostringstream ret; - boost::format fmt("%-3d %-20.20s %-15d %-15d %-15d %-15d %-15d %-15d %-15d %-15d %-15d %-15d %-15d %-15d"); - ret << (fmt % "#" % "Address" % "HTTP" % "HTTP/1" % "HTTP/2" % "GET" % "POST" % "Bad" % "Errors" % "Redirects" % "Valid" % "# ticket keys" % "Rotation delay" % "Next rotation") << endl; - size_t counter = 0; - for (const auto& ctx : g_dohlocals) { - ret << (fmt % counter % ctx->d_tlsContext.d_addr.toStringWithPort() % ctx->d_httpconnects % ctx->d_http1Stats.d_nbQueries % ctx->d_http2Stats.d_nbQueries % ctx->d_getqueries % ctx->d_postqueries % ctx->d_badrequests % ctx->d_errorresponses % ctx->d_redirectresponses % ctx->d_validresponses % ctx->getTicketsKeysCount() % ctx->getTicketsKeyRotationDelay() % ctx->getNextTicketsKeyRotation()) << endl; - counter++; - } - g_outputBuffer = ret.str(); - } - catch (const std::exception& e) { - g_outputBuffer = e.what(); - throw; - } -#else - g_outputBuffer = "DNS over HTTPS support is not present!\n"; -#endif - }); - - luaCtx.writeFunction("showDOH3Frontends", []() { -#ifdef HAVE_DNS_OVER_HTTP3 - setLuaNoSideEffect(); - try { - ostringstream ret; - boost::format fmt("%-3d %-20.20s %-15d %-15d %-15d %-15d"); - ret << (fmt % "#" % "Address" % "Bad Version" % "Invalid Token" % "Errors" % "Valid") << endl; - size_t counter = 0; - for (const auto& ctx : g_doh3locals) { - ret << (fmt % counter % ctx->d_local.toStringWithPort() % ctx->d_doh3UnsupportedVersionErrors % ctx->d_doh3InvalidTokensReceived % ctx->d_errorResponses % ctx->d_validResponses) << endl; - counter++; - } - g_outputBuffer = ret.str(); - } - catch (const std::exception& e) { - g_outputBuffer = e.what(); - throw; - } -#else - g_outputBuffer = "DNS over HTTP3 support is not present!\n"; -#endif - }); - - luaCtx.writeFunction("showDOHResponseCodes", []() { -#ifdef HAVE_DNS_OVER_HTTPS - setLuaNoSideEffect(); - try { - ostringstream ret; - boost::format fmt("%-3d %-20.20s %-15d %-15d %-15d %-15d %-15d %-15d"); - g_outputBuffer = "\n- HTTP/1:\n\n"; - ret << (fmt % "#" % "Address" % "200" % "400" % "403" % "500" % "502" % "Others") << endl; - size_t counter = 0; - for (const auto& ctx : g_dohlocals) { - ret << (fmt % counter % ctx->d_tlsContext.d_addr.toStringWithPort() % ctx->d_http1Stats.d_nb200Responses % ctx->d_http1Stats.d_nb400Responses % ctx->d_http1Stats.d_nb403Responses % ctx->d_http1Stats.d_nb500Responses % ctx->d_http1Stats.d_nb502Responses % ctx->d_http1Stats.d_nbOtherResponses) << endl; - counter++; - } - g_outputBuffer += ret.str(); - ret.str(""); - - g_outputBuffer += "\n- HTTP/2:\n\n"; - ret << (fmt % "#" % "Address" % "200" % "400" % "403" % "500" % "502" % "Others") << endl; - counter = 0; - for (const auto& ctx : g_dohlocals) { - ret << (fmt % counter % ctx->d_tlsContext.d_addr.toStringWithPort() % ctx->d_http2Stats.d_nb200Responses % ctx->d_http2Stats.d_nb400Responses % ctx->d_http2Stats.d_nb403Responses % ctx->d_http2Stats.d_nb500Responses % ctx->d_http2Stats.d_nb502Responses % ctx->d_http2Stats.d_nbOtherResponses) << endl; - counter++; - } - g_outputBuffer += ret.str(); - } - catch (const std::exception& e) { - g_outputBuffer = e.what(); - throw; - } -#else - g_outputBuffer = "DNS over HTTPS support is not present!\n"; -#endif - }); - - luaCtx.writeFunction("getDOHFrontend", [client](uint64_t index) { - std::shared_ptr result = nullptr; - if (client) { - return result; - } -#ifdef HAVE_DNS_OVER_HTTPS - setLuaNoSideEffect(); - try { - if (index < g_dohlocals.size()) { - result = g_dohlocals.at(index); - } - else { - errlog("Error: trying to get DOH frontend with index %d but we only have %d frontend(s)\n", index, g_dohlocals.size()); - g_outputBuffer = "Error: trying to get DOH frontend with index " + std::to_string(index) + " but we only have " + std::to_string(g_dohlocals.size()) + " frontend(s)\n"; - } - } - catch (const std::exception& e) { - g_outputBuffer = "Error while trying to get DOH frontend with index " + std::to_string(index) + ": " + string(e.what()) + "\n"; - errlog("Error while trying to get DOH frontend with index %d: %s\n", index, string(e.what())); - } -#else - g_outputBuffer="DNS over HTTPS support is not present!\n"; -#endif - return result; - }); - - luaCtx.writeFunction("getDOHFrontendCount", []() { - setLuaNoSideEffect(); - return g_dohlocals.size(); - }); - - luaCtx.registerFunction::*)()>("reloadCertificates", [](std::shared_ptr frontend) { - if (frontend != nullptr) { - frontend->reloadCertificates(); - } - }); - - luaCtx.registerFunction::*)(boost::variant, LuaArray, LuaArray>> certFiles, boost::variant> keyFiles)>("loadNewCertificatesAndKeys", [](std::shared_ptr frontend, boost::variant, LuaArray, LuaArray>> certFiles, boost::variant> keyFiles) { -#ifdef HAVE_DNS_OVER_HTTPS - if (frontend != nullptr) { - if (loadTLSCertificateAndKeys("DOHFrontend::loadNewCertificatesAndKeys", frontend->d_tlsContext.d_tlsConfig.d_certKeyPairs, certFiles, keyFiles)) { - frontend->reloadCertificates(); - } - } -#endif - }); - - luaCtx.registerFunction::*)()>("rotateTicketsKey", [](std::shared_ptr frontend) { - if (frontend != nullptr) { - frontend->rotateTicketsKey(time(nullptr)); - } - }); - - luaCtx.registerFunction::*)(const std::string&)>("loadTicketsKeys", [](std::shared_ptr frontend, const std::string& file) { - if (frontend != nullptr) { - frontend->loadTicketsKeys(file); - } - }); - - luaCtx.registerFunction::*)(const LuaArray>&)>("setResponsesMap", [](std::shared_ptr frontend, const LuaArray>& map) { - if (frontend != nullptr) { - auto newMap = std::make_shared>>(); - newMap->reserve(map.size()); - - for (const auto& entry : map) { - newMap->push_back(entry.second); - } - - frontend->d_responsesMap = std::move(newMap); - } - }); - - luaCtx.writeFunction("addTLSLocal", [client](const std::string& addr, boost::variant, LuaArray, LuaArray>> certFiles, LuaTypeOrArrayOf keyFiles, boost::optional vars) { - if (client) { - return; - } -#ifdef HAVE_DNS_OVER_TLS - if (!checkConfigurationTime("addTLSLocal")) { - return; - } - setLuaSideEffect(); - - auto frontend = std::make_shared(TLSFrontend::ALPN::DoT); - if (!loadTLSCertificateAndKeys("addTLSLocal", frontend->d_tlsConfig.d_certKeyPairs, certFiles, keyFiles)) { - return; - } - - bool reusePort = false; - int tcpFastOpenQueueSize = 0; - int tcpListenQueueSize = 0; - uint64_t maxInFlightQueriesPerConn = 0; - uint64_t tcpMaxConcurrentConns = 0; - std::string interface; - std::set cpus; - std::vector> additionalAddresses; - bool enableProxyProtocol = true; - - if (vars) { - parseLocalBindVars(vars, reusePort, tcpFastOpenQueueSize, interface, cpus, tcpListenQueueSize, maxInFlightQueriesPerConn, tcpMaxConcurrentConns, enableProxyProtocol); - - getOptionalValue(vars, "provider", frontend->d_provider); - boost::algorithm::to_lower(frontend->d_provider); - getOptionalValue(vars, "proxyProtocolOutsideTLS", frontend->d_proxyProtocolOutsideTLS); - - LuaArray addresses; - if (getOptionalValue(vars, "additionalAddresses", addresses) > 0) { - for (const auto& [_, add] : addresses) { - try { - ComboAddress address(add); - additionalAddresses.emplace_back(address, -1); - } - catch (const PDNSException& e) { - errlog("Unable to parse additional address %s for DoT bind: %s", add, e.reason); - return; - } - } - } - - parseTLSConfig(frontend->d_tlsConfig, "addTLSLocal", vars); - - bool ignoreTLSConfigurationErrors = false; - if (getOptionalValue(vars, "ignoreTLSConfigurationErrors", ignoreTLSConfigurationErrors) > 0 && ignoreTLSConfigurationErrors) { - // we are asked to try to load the certificates so we can return a potential error - // and properly ignore the frontend before actually launching it - try { - std::map ocspResponses = {}; - auto ctx = libssl_init_server_context(frontend->d_tlsConfig, ocspResponses); - } - catch (const std::runtime_error& e) { - errlog("Ignoring TLS frontend: '%s'", e.what()); - return; - } - } - - checkAllParametersConsumed("addTLSLocal", vars); - } - - try { - frontend->d_addr = ComboAddress(addr, 853); - if (!frontend->d_provider.empty()) { - vinfolog("Loading TLS provider '%s'", frontend->d_provider); - } - else { -#ifdef HAVE_LIBSSL - const std::string provider("openssl"); -#else - const std::string provider("gnutls"); -#endif - vinfolog("Loading default TLS provider '%s'", provider); - } - // only works pre-startup, so no sync necessary - auto clientState = std::make_unique(frontend->d_addr, true, reusePort, tcpFastOpenQueueSize, interface, cpus, enableProxyProtocol); - clientState->tlsFrontend = frontend; - clientState->d_additionalAddresses = std::move(additionalAddresses); - if (tcpListenQueueSize > 0) { - clientState->tcpListenQueueSize = tcpListenQueueSize; - } - if (maxInFlightQueriesPerConn > 0) { - clientState->d_maxInFlightQueriesPerConn = maxInFlightQueriesPerConn; - } - if (tcpMaxConcurrentConns > 0) { - clientState->d_tcpConcurrentConnectionsLimit = tcpMaxConcurrentConns; - } - - g_tlslocals.push_back(clientState->tlsFrontend); - g_frontends.push_back(std::move(clientState)); - } - catch (const std::exception& e) { - g_outputBuffer = "Error: " + string(e.what()) + "\n"; - } -#else - throw std::runtime_error("addTLSLocal() called but DNS over TLS support is not present!"); -#endif - }); - - luaCtx.writeFunction("showTLSContexts", []() { -#ifdef HAVE_DNS_OVER_TLS - setLuaNoSideEffect(); - try { - ostringstream ret; - boost::format fmt("%1$-3d %2$-20.20s %|25t|%3$-14d %|40t|%4$-14d %|54t|%5$-21.21s"); - // 1 2 3 4 5 - ret << (fmt % "#" % "Address" % "# ticket keys" % "Rotation delay" % "Next rotation") << endl; - size_t counter = 0; - for (const auto& ctx : g_tlslocals) { - ret << (fmt % counter % ctx->d_addr.toStringWithPort() % ctx->getTicketsKeysCount() % ctx->getTicketsKeyRotationDelay() % ctx->getNextTicketsKeyRotation()) << endl; - counter++; - } - g_outputBuffer = ret.str(); - } - catch (const std::exception& e) { - g_outputBuffer = e.what(); - throw; - } -#else - g_outputBuffer = "DNS over TLS support is not present!\n"; -#endif - }); - - luaCtx.writeFunction("getTLSContext", [](uint64_t index) { - std::shared_ptr result = nullptr; -#ifdef HAVE_DNS_OVER_TLS - setLuaNoSideEffect(); - try { - if (index < g_tlslocals.size()) { - result = g_tlslocals.at(index)->getContext(); - } - else { - errlog("Error: trying to get TLS context with index %d but we only have %d context(s)\n", index, g_tlslocals.size()); - g_outputBuffer = "Error: trying to get TLS context with index " + std::to_string(index) + " but we only have " + std::to_string(g_tlslocals.size()) + " context(s)\n"; - } - } - catch (const std::exception& e) { - g_outputBuffer = "Error while trying to get TLS context with index " + std::to_string(index) + ": " + string(e.what()) + "\n"; - errlog("Error while trying to get TLS context with index %d: %s\n", index, string(e.what())); - } -#else - g_outputBuffer="DNS over TLS support is not present!\n"; -#endif - return result; - }); - - luaCtx.writeFunction("getTLSFrontend", [](uint64_t index) { - std::shared_ptr result = nullptr; -#ifdef HAVE_DNS_OVER_TLS - setLuaNoSideEffect(); - try { - if (index < g_tlslocals.size()) { - result = g_tlslocals.at(index); - } - else { - errlog("Error: trying to get TLS frontend with index %d but we only have %d frontends\n", index, g_tlslocals.size()); - g_outputBuffer = "Error: trying to get TLS frontend with index " + std::to_string(index) + " but we only have " + std::to_string(g_tlslocals.size()) + " frontend(s)\n"; - } - } - catch (const std::exception& e) { - g_outputBuffer = "Error while trying to get TLS frontend with index " + std::to_string(index) + ": " + string(e.what()) + "\n"; - errlog("Error while trying to get TLS frontend with index %d: %s\n", index, string(e.what())); - } -#else - g_outputBuffer="DNS over TLS support is not present!\n"; -#endif - return result; - }); - - luaCtx.writeFunction("getTLSFrontendCount", []() { - setLuaNoSideEffect(); - return g_tlslocals.size(); - }); - - luaCtx.registerFunction::*)()>("rotateTicketsKey", [](std::shared_ptr& ctx) { - if (ctx != nullptr) { - ctx->rotateTicketsKey(time(nullptr)); - } - }); - - luaCtx.registerFunction::*)(const std::string&)>("loadTicketsKeys", [](std::shared_ptr& ctx, const std::string& file) { - if (ctx != nullptr) { - ctx->loadTicketsKeys(file); - } - }); - - luaCtx.registerFunction::*)() const>("getAddressAndPort", [](const std::shared_ptr& frontend) { - if (frontend == nullptr) { - return std::string(); - } - return frontend->d_addr.toStringWithPort(); - }); - - luaCtx.registerFunction::*)()>("rotateTicketsKey", [](std::shared_ptr& frontend) { - if (frontend == nullptr) { - return; - } - auto ctx = frontend->getContext(); - if (ctx) { - ctx->rotateTicketsKey(time(nullptr)); - } - }); - - luaCtx.registerFunction::*)(const std::string&)>("loadTicketsKeys", [](std::shared_ptr& frontend, const std::string& file) { - if (frontend == nullptr) { - return; - } - auto ctx = frontend->getContext(); - if (ctx) { - ctx->loadTicketsKeys(file); - } - }); - - luaCtx.registerFunction::*)()>("reloadCertificates", [](std::shared_ptr& frontend) { - if (frontend == nullptr) { - return; - } - frontend->setupTLS(); - }); - - luaCtx.registerFunction::*)(boost::variant, LuaArray, LuaArray>> certFiles, LuaTypeOrArrayOf keyFiles)>("loadNewCertificatesAndKeys", [](std::shared_ptr& frontend, boost::variant, LuaArray, LuaArray>> certFiles, LuaTypeOrArrayOf keyFiles) { -#ifdef HAVE_DNS_OVER_TLS - if (loadTLSCertificateAndKeys("TLSFrontend::loadNewCertificatesAndKeys", frontend->d_tlsConfig.d_certKeyPairs, certFiles, keyFiles)) { - frontend->setupTLS(); - } -#endif - }); - - luaCtx.writeFunction("reloadAllCertificates", []() { - for (auto& frontend : g_frontends) { - if (!frontend) { - continue; - } - try { -#ifdef HAVE_DNSCRYPT - if (frontend->dnscryptCtx) { - frontend->dnscryptCtx->reloadCertificates(); - } -#endif /* HAVE_DNSCRYPT */ -#ifdef HAVE_DNS_OVER_TLS - if (frontend->tlsFrontend) { - frontend->tlsFrontend->setupTLS(); - } -#endif /* HAVE_DNS_OVER_TLS */ -#ifdef HAVE_DNS_OVER_HTTPS - if (frontend->dohFrontend) { - frontend->dohFrontend->reloadCertificates(); - } -#endif /* HAVE_DNS_OVER_HTTPS */ - } - catch (const std::exception& e) { - errlog("Error reloading certificates for frontend %s: %s", frontend->local.toStringWithPort(), e.what()); - } - } - }); - - luaCtx.writeFunction("setAllowEmptyResponse", [](bool allow) { g_allowEmptyResponse = allow; }); - luaCtx.writeFunction("setDropEmptyQueries", [](bool drop) { extern bool g_dropEmptyQueries; g_dropEmptyQueries = drop; }); - -#if defined(HAVE_LIBSSL) && defined(HAVE_OCSP_BASIC_SIGN) && !defined(DISABLE_OCSP_STAPLING) - luaCtx.writeFunction("generateOCSPResponse", [client](const std::string& certFile, const std::string& caCert, const std::string& caKey, const std::string& outFile, int ndays, int nmin) { - if (client) { - return; - } - - libssl_generate_ocsp_response(certFile, caCert, caKey, outFile, ndays, nmin); - }); -#endif /* HAVE_LIBSSL && HAVE_OCSP_BASIC_SIGN && !DISABLE_OCSP_STAPLING */ - - luaCtx.writeFunction("addCapabilitiesToRetain", [](LuaTypeOrArrayOf caps) { - if (!checkConfigurationTime("addCapabilitiesToRetain")) { - return; - } - setLuaSideEffect(); - if (caps.type() == typeid(std::string)) { - g_capabilitiesToRetain.insert(boost::get(caps)); - } - else if (caps.type() == typeid(LuaArray)) { - for (const auto& cap : boost::get>(caps)) { - g_capabilitiesToRetain.insert(cap.second); - } - } - }); - - luaCtx.writeFunction("setUDPSocketBufferSizes", [client](uint64_t recv, uint64_t snd) { - if (client) { - return; - } - if (!checkConfigurationTime("setUDPSocketBufferSizes")) { - return; - } - checkParameterBound("setUDPSocketBufferSizes", recv, std::numeric_limits::max()); - checkParameterBound("setUDPSocketBufferSizes", snd, std::numeric_limits::max()); - setLuaSideEffect(); - - g_socketUDPSendBuffer = snd; - g_socketUDPRecvBuffer = recv; - }); - - luaCtx.writeFunction("setRandomizedOutgoingSockets", [](bool randomized) { - DownstreamState::s_randomizeSockets = randomized; - }); - - luaCtx.writeFunction("setRandomizedIdsOverUDP", [](bool randomized) { - DownstreamState::s_randomizeIDs = randomized; - }); - -#if defined(HAVE_LIBSSL) && !defined(HAVE_TLS_PROVIDERS) - luaCtx.writeFunction("loadTLSEngine", [client](const std::string& engineName, boost::optional defaultString) { - if (client) { - return; - } - - auto [success, error] = libssl_load_engine(engineName, defaultString ? std::optional(*defaultString) : std::nullopt); - if (!success) { - g_outputBuffer = "Error while trying to load TLS engine '" + engineName + "': " + error + "\n"; - errlog("Error while trying to load TLS engine '%s': %s", engineName, error); - } - }); -#endif /* HAVE_LIBSSL && !HAVE_TLS_PROVIDERS */ - -#if defined(HAVE_LIBSSL) && OPENSSL_VERSION_MAJOR >= 3 && defined(HAVE_TLS_PROVIDERS) - luaCtx.writeFunction("loadTLSProvider", [client](const std::string& providerName) { - if (client) { - return; - } - - auto [success, error] = libssl_load_provider(providerName); - if (!success) { - g_outputBuffer = "Error while trying to load TLS provider '" + providerName + "': " + error + "\n"; - errlog("Error while trying to load TLS provider '%s': %s", providerName, error); - } - }); -#endif /* HAVE_LIBSSL && OPENSSL_VERSION_MAJOR >= 3 && HAVE_TLS_PROVIDERS */ - - luaCtx.writeFunction("newThread", [client, configCheck](const std::string& code) { - if (client || configCheck) { - return; - } - std::thread newThread(LuaThread, code); - - newThread.detach(); - }); - - luaCtx.writeFunction("declareMetric", [](const std::string& name, const std::string& type, const std::string& description, boost::optional customName) { - auto result = dnsdist::metrics::declareCustomMetric(name, type, description, customName ? std::optional(*customName) : std::nullopt); - if (result) { - g_outputBuffer += *result + "\n"; - errlog("Error in declareMetric: %s", *result); - return false; - } - return true; - }); - luaCtx.writeFunction("incMetric", [](const std::string& name, boost::optional step) { - auto result = dnsdist::metrics::incrementCustomCounter(name, step ? *step : 1); - if (const auto* errorStr = std::get_if(&result)) { - g_outputBuffer = *errorStr + "'\n"; - errlog("Error in incMetric: %s", *errorStr); - return static_cast(0); - } - return std::get(result); - }); - luaCtx.writeFunction("decMetric", [](const std::string& name, boost::optional step) { - auto result = dnsdist::metrics::decrementCustomCounter(name, step ? *step : 1); - if (const auto* errorStr = std::get_if(&result)) { - g_outputBuffer = *errorStr + "'\n"; - errlog("Error in decMetric: %s", *errorStr); - return static_cast(0); - } - return std::get(result); - }); - luaCtx.writeFunction("setMetric", [](const std::string& name, const double value) -> double { - auto result = dnsdist::metrics::setCustomGauge(name, value); - if (const auto* errorStr = std::get_if(&result)) { - g_outputBuffer = *errorStr + "'\n"; - errlog("Error in setMetric: %s", *errorStr); - return 0.; - } - return std::get(result); - }); - luaCtx.writeFunction("getMetric", [](const std::string& name) { - auto result = dnsdist::metrics::getCustomMetric(name); - if (const auto* errorStr = std::get_if(&result)) { - g_outputBuffer = *errorStr + "'\n"; - errlog("Error in getMetric: %s", *errorStr); - return 0.; - } - return std::get(result); - }); -} - -vector> setupLua(LuaContext& luaCtx, bool client, bool configCheck, const std::string& config) -{ - // this needs to exist only during the parsing of the configuration - // and cannot be captured by lambdas - g_launchWork = std::vector>(); - - setupLuaActions(luaCtx); - setupLuaConfig(luaCtx, client, configCheck); - setupLuaBindings(luaCtx, client, configCheck); - setupLuaBindingsDNSCrypt(luaCtx, client); - setupLuaBindingsDNSParser(luaCtx); - setupLuaBindingsDNSQuestion(luaCtx); - setupLuaBindingsKVS(luaCtx, client); - setupLuaBindingsNetwork(luaCtx, client); - setupLuaBindingsPacketCache(luaCtx, client); - setupLuaBindingsProtoBuf(luaCtx, client, configCheck); - setupLuaBindingsRings(luaCtx, client); - setupLuaInspection(luaCtx); - setupLuaRules(luaCtx); - setupLuaVars(luaCtx); - setupLuaWeb(luaCtx); - -#ifdef LUAJIT_VERSION - luaCtx.executeCode(getLuaFFIWrappers()); -#endif - - std::ifstream ifs(config); - if (!ifs) { - if (configCheck) { - throw std::runtime_error("Unable to read configuration file from " + config); - } - else { - warnlog("Unable to read configuration from '%s'", config); - } - } - else { - vinfolog("Read configuration from '%s'", config); - } - - luaCtx.executeCode(ifs); - - auto ret = *g_launchWork; - g_launchWork = boost::none; - return ret; -} diff --git a/pdns/dnsdist-lua.hh b/pdns/dnsdist-lua.hh deleted file mode 100644 index 5c35c3fb9d02..000000000000 --- a/pdns/dnsdist-lua.hh +++ /dev/null @@ -1,245 +0,0 @@ -/* - * This file is part of PowerDNS or dnsdist. - * Copyright -- PowerDNS.COM B.V. and its contributors - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of version 2 of the GNU General Public License as - * published by the Free Software Foundation. - * - * In addition, for the avoidance of any doubt, permission is granted to - * link this program with OpenSSL and to (re)distribute the binaries - * produced as the result of such linking. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ -#pragma once - -#include "dolog.hh" -#include "dnsdist.hh" -#include "dnsparser.hh" -#include - -struct ResponseConfig -{ - boost::optional setAA{boost::none}; - boost::optional setAD{boost::none}; - boost::optional setRA{boost::none}; - uint32_t ttl{60}; -}; -void setResponseHeadersFromConfig(dnsheader& dnsheader, const ResponseConfig& config); - -class SpoofAction : public DNSAction -{ -public: - SpoofAction(const vector& 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 DNSName& cname): d_cname(cname) - { - } - - SpoofAction(const char* rawresponse, size_t len): d_raw(rawresponse, rawresponse + len) - { - } - - SpoofAction(const vector& raws, std::optional typeForAny): d_rawResponses(raws), d_rawTypeForAny(typeForAny) - { - } - - DNSAction::Action operator()(DNSQuestion* dnsquestion, string* ruleresult) const override; - - string toString() const override - { - string ret = "spoof in "; - if (!d_cname.empty()) { - ret += d_cname.toString() + " "; - } - if (d_rawResponses.size() > 0) { - ret += "raw bytes "; - } - else { - for(const auto& a : d_addrs) - ret += a.toString()+" "; - } - return ret; - } - - [[nodiscard]] ResponseConfig& getResponseConfig() - { - return d_responseConfig; - } - -private: - ResponseConfig d_responseConfig; - static thread_local std::default_random_engine t_randomEngine; - std::vector d_addrs; - std::unordered_set d_types; - std::vector d_rawResponses; - PacketBuffer d_raw; - DNSName d_cname; - std::optional d_rawTypeForAny{}; -}; - -class LimitTTLResponseAction : public DNSResponseAction, public boost::noncopyable -{ -public: - LimitTTLResponseAction() {} - - LimitTTLResponseAction(uint32_t min, uint32_t max = std::numeric_limits::max(), const std::unordered_set& types = {}) : d_types(types), d_min(min), d_max(max) - { - } - - DNSResponseAction::Action operator()(DNSResponse* dr, std::string* ruleresult) const override - { - auto visitor = [&](uint8_t section, uint16_t qclass, uint16_t qtype, uint32_t ttl) { - if (!d_types.empty() && qclass == QClass::IN && d_types.count(qtype) == 0) { - return ttl; - } - - if (d_min > 0) { - if (ttl < d_min) { - ttl = d_min; - } - } - if (ttl > d_max) { - ttl = d_max; - } - return ttl; - }; - editDNSPacketTTL(reinterpret_cast(dr->getMutableData().data()), dr->getData().size(), visitor); - return DNSResponseAction::Action::None; - } - - std::string toString() const override - { - std::string result = "limit ttl (" + std::to_string(d_min) + " <= ttl <= " + std::to_string(d_max); - if (!d_types.empty()) { - bool first = true; - result += ", types in ["; - for (const auto& type : d_types) { - if (first) { - first = false; - } - else { - result += " "; - } - result += type.toString(); - } - result += "]"; - } - result += + ")"; - return result; - } - -private: - std::unordered_set d_types; - uint32_t d_min{0}; - uint32_t d_max{std::numeric_limits::max()}; -}; - -template using LuaArray = std::vector>; -template using LuaAssociativeTable = std::unordered_map; -template using LuaTypeOrArrayOf = boost::variant>; - -using luaruleparams_t = LuaAssociativeTable; -using nmts_t = NetmaskTree; - -using luadnsrule_t = boost::variant, std::shared_ptr, DNSName, LuaArray>; -std::shared_ptr makeRule(const luadnsrule_t& var, const std::string& calledFrom); - -void parseRuleParams(boost::optional& params, boost::uuids::uuid& uuid, std::string& name, uint64_t& creationOrder); -void checkParameterBound(const std::string& parameter, uint64_t value, size_t max = std::numeric_limits::max()); - -vector> setupLua(LuaContext& luaCtx, bool client, bool configCheck, const std::string& config); -void setupLuaActions(LuaContext& luaCtx); -void setupLuaBindings(LuaContext& luaCtx, bool client, bool configCheck); -void setupLuaBindingsDNSCrypt(LuaContext& luaCtx, bool client); -void setupLuaBindingsDNSParser(LuaContext& luaCtx); -void setupLuaBindingsDNSQuestion(LuaContext& luaCtx); -void setupLuaBindingsKVS(LuaContext& luaCtx, bool client); -void setupLuaBindingsNetwork(LuaContext& luaCtx, bool client); -void setupLuaBindingsPacketCache(LuaContext& luaCtx, bool client); -void setupLuaBindingsProtoBuf(LuaContext& luaCtx, bool client, bool configCheck); -void setupLuaBindingsRings(LuaContext& luaCtx, bool client); -void setupLuaRules(LuaContext& luaCtx); -void setupLuaInspection(LuaContext& luaCtx); -void setupLuaVars(LuaContext& luaCtx); -void setupLuaWeb(LuaContext& luaCtx); -void setupLuaLoadBalancingContext(LuaContext& luaCtx); - -/** - * getOptionalValue(vars, key, value) - * - * Attempts to extract value for key in vars. - * Erases the key from vars. - * - * returns: -1 if type wasn't compatible, 0 if not found or number of element(s) found - */ -template -static inline int getOptionalValue(boost::optional& vars, const std::string& key, T& value, bool warnOnWrongType = true) { - /* nothing found, nothing to return */ - if (!vars) { - return 0; - } - - if (vars->count(key)) { - try { - value = boost::get((*vars)[key]); - } catch (const boost::bad_get& e) { - /* key is there but isn't compatible */ - if (warnOnWrongType) { - warnlog("Invalid type for key '%s' - ignored", key); - vars->erase(key); - } - return -1; - } - } - return vars->erase(key); -} - -template -static inline int getOptionalIntegerValue(const std::string& func, boost::optional& vars, const std::string& key, T& value) { - std::string valueStr; - auto ret = getOptionalValue(vars, key, valueStr, true); - if (ret == 1) { - try { - value = std::stoi(valueStr); - } - catch (const std::exception& e) { - warnlog("Parameter '%s' of '%s' must be integer, not '%s' - ignoring", func, key, valueStr); - return -1; - } - } - return ret; -} - -template -static inline void checkAllParametersConsumed(const std::string& func, const boost::optional& vars) { - /* no vars */ - if (!vars) { - return; - } - for (const auto& [key, value] : *vars) { - warnlog("%s: Unknown key '%s' given - ignored", func, key); - } -} diff --git a/pdns/dnsdist-protobuf.cc b/pdns/dnsdist-protobuf.cc deleted file mode 100644 index 30445ed24f18..000000000000 --- a/pdns/dnsdist-protobuf.cc +++ /dev/null @@ -1,392 +0,0 @@ -/* - * This file is part of PowerDNS or dnsdist. - * Copyright -- PowerDNS.COM B.V. and its contributors - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of version 2 of the GNU General Public License as - * published by the Free Software Foundation. - * - * In addition, for the avoidance of any doubt, permission is granted to - * link this program with OpenSSL and to (re)distribute the binaries - * produced as the result of such linking. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ -#include "config.h" - -#ifndef DISABLE_PROTOBUF -#include "base64.hh" -#include "dnsdist.hh" -#include "dnsdist-protobuf.hh" -#include "protozero.hh" - -DNSDistProtoBufMessage::DNSDistProtoBufMessage(const DNSQuestion& dnsquestion) : - d_dq(dnsquestion) -{ -} - -DNSDistProtoBufMessage::DNSDistProtoBufMessage(const DNSResponse& dnsresponse, bool includeCNAME) : - d_dq(dnsresponse), d_dr(&dnsresponse), d_type(pdns::ProtoZero::Message::MessageType::DNSResponseType), d_includeCNAME(includeCNAME) -{ -} - -void DNSDistProtoBufMessage::setServerIdentity(const std::string& serverId) -{ - d_serverIdentity = serverId; -} - -void DNSDistProtoBufMessage::setRequestor(const ComboAddress& requestor) -{ - d_requestor = requestor; -} - -void DNSDistProtoBufMessage::setResponder(const ComboAddress& responder) -{ - d_responder = responder; -} - -void DNSDistProtoBufMessage::setRequestorPort(uint16_t port) -{ - if (d_requestor) { - d_requestor->setPort(port); - } -} - -void DNSDistProtoBufMessage::setResponderPort(uint16_t port) -{ - if (d_responder) { - d_responder->setPort(port); - } -} - -void DNSDistProtoBufMessage::setResponseCode(uint8_t rcode) -{ - d_rcode = rcode; -} - -void DNSDistProtoBufMessage::setType(pdns::ProtoZero::Message::MessageType type) -{ - d_type = type; -} - -void DNSDistProtoBufMessage::setBytes(size_t bytes) -{ - d_bytes = bytes; -} - -void DNSDistProtoBufMessage::setTime(time_t sec, uint32_t usec) -{ - d_time = std::pair(sec, usec); -} - -void DNSDistProtoBufMessage::setQueryTime(time_t sec, uint32_t usec) -{ - d_queryTime = std::pair(sec, usec); -} - -void DNSDistProtoBufMessage::setQuestion(const DNSName& name, uint16_t qtype, uint16_t qclass) -{ - d_question = DNSDistProtoBufMessage::PBQuestion(name, qtype, qclass); -} - -void DNSDistProtoBufMessage::setEDNSSubnet(const Netmask& nm) -{ - d_ednsSubnet = nm; -} - -void DNSDistProtoBufMessage::addTag(const std::string& strValue) -{ - d_additionalTags.push_back(strValue); -} - -void DNSDistProtoBufMessage::addMeta(const std::string& key, std::vector&& strValues, const std::vector& intValues) -{ - auto& entry = d_metaTags[key]; - for (auto& value : strValues) { - entry.d_strings.insert(std::move(value)); - } - for (const auto& value : intValues) { - entry.d_integers.insert(value); - } -} - -void DNSDistProtoBufMessage::addRR(DNSName&& qname, uint16_t uType, uint16_t uClass, uint32_t uTTL, const std::string& strBlob) -{ - d_additionalRRs.push_back({std::move(qname), strBlob, uTTL, uType, uClass}); -} - -void DNSDistProtoBufMessage::serialize(std::string& data) const -{ - if ((data.capacity() - data.size()) < 128) { - data.reserve(data.size() + 128); - } - pdns::ProtoZero::Message m{data}; - - m.setType(d_type); - - if (d_time) { - m.setTime(d_time->first, d_time->second); - } - else { - struct timespec ts; - gettime(&ts, true); - m.setTime(ts.tv_sec, ts.tv_nsec / 1000); - } - - const auto distProto = d_dq.getProtocol(); - pdns::ProtoZero::Message::TransportProtocol protocol = pdns::ProtoZero::Message::TransportProtocol::UDP; - - if (distProto == dnsdist::Protocol::DoTCP) { - protocol = pdns::ProtoZero::Message::TransportProtocol::TCP; - } - else if (distProto == dnsdist::Protocol::DoT) { - protocol = pdns::ProtoZero::Message::TransportProtocol::DoT; - } - else if (distProto == dnsdist::Protocol::DoH) { - protocol = pdns::ProtoZero::Message::TransportProtocol::DoH; - m.setHTTPVersion(pdns::ProtoZero::Message::HTTPVersion::HTTP2); - } - else if (distProto == dnsdist::Protocol::DoH3) { - protocol = pdns::ProtoZero::Message::TransportProtocol::DoH; - m.setHTTPVersion(pdns::ProtoZero::Message::HTTPVersion::HTTP3); - } - else if (distProto == dnsdist::Protocol::DNSCryptUDP) { - protocol = pdns::ProtoZero::Message::TransportProtocol::DNSCryptUDP; - } - else if (distProto == dnsdist::Protocol::DNSCryptTCP) { - protocol = pdns::ProtoZero::Message::TransportProtocol::DNSCryptTCP; - } - else if (distProto == dnsdist::Protocol::DoQ) { - protocol = pdns::ProtoZero::Message::TransportProtocol::DoQ; - } - - m.setRequest(d_dq.ids.d_protoBufData && d_dq.ids.d_protoBufData->uniqueId ? *d_dq.ids.d_protoBufData->uniqueId : getUniqueID(), d_requestor ? *d_requestor : d_dq.ids.origRemote, d_responder ? *d_responder : d_dq.ids.origDest, d_question ? d_question->d_name : d_dq.ids.qname, d_question ? d_question->d_type : d_dq.ids.qtype, d_question ? d_question->d_class : d_dq.ids.qclass, d_dq.getHeader()->id, protocol, d_bytes ? *d_bytes : d_dq.getData().size()); - - if (d_serverIdentity) { - m.setServerIdentity(*d_serverIdentity); - } - else if (d_ServerIdentityRef != nullptr) { - m.setServerIdentity(*d_ServerIdentityRef); - } - - if (d_ednsSubnet) { - m.setEDNSSubnet(*d_ednsSubnet, 128); - } - - m.startResponse(); - if (d_queryTime) { - // coverity[store_truncates_time_t] - m.setQueryTime(d_queryTime->first, d_queryTime->second); - } - else { - m.setQueryTime(d_dq.getQueryRealTime().tv_sec, d_dq.getQueryRealTime().tv_nsec / 1000); - } - - if (d_dr != nullptr) { - m.setResponseCode(d_rcode ? *d_rcode : d_dr->getHeader()->rcode); - m.addRRsFromPacket(reinterpret_cast(d_dr->getData().data()), d_dr->getData().size(), d_includeCNAME); - } - else { - if (d_rcode) { - m.setResponseCode(*d_rcode); - } - } - - for (const auto& rr : d_additionalRRs) { - m.addRR(rr.d_name, rr.d_type, rr.d_class, rr.d_ttl, rr.d_data); - } - - for (const auto& tag : d_additionalTags) { - m.addPolicyTag(tag); - } - - m.commitResponse(); - - if (d_dq.ids.d_protoBufData) { - const auto& pbData = d_dq.ids.d_protoBufData; - if (!pbData->d_deviceName.empty()) { - m.setDeviceName(pbData->d_deviceName); - } - if (!pbData->d_deviceID.empty()) { - m.setDeviceId(pbData->d_deviceID); - } - if (!pbData->d_requestorID.empty()) { - m.setRequestorId(pbData->d_requestorID); - } - } - - for (const auto& [key, values] : d_metaTags) { - if (!values.d_strings.empty() || !values.d_integers.empty()) { - m.setMeta(key, values.d_strings, values.d_integers); - } - else { - /* the MetaValue field is _required_ to exist, even if we have no value */ - m.setMeta(key, {std::string()}, {}); - } - } -} - -ProtoBufMetaKey::ProtoBufMetaKey(const std::string& key) -{ - auto& idx = s_types.get(); - auto it = idx.find(key); - if (it != idx.end()) { - d_type = it->d_type; - return; - } - else { - auto [prefix, variable] = splitField(key, ':'); - if (!variable.empty()) { - it = idx.find(prefix); - if (it != idx.end() && it->d_prefix) { - d_type = it->d_type; - if (it->d_numeric) { - try { - d_numericSubKey = std::stoi(variable); - } - catch (const std::exception& e) { - throw std::runtime_error("Unable to parse numeric ProtoBuf key '" + key + "'"); - } - } - else { - if (!it->d_caseSensitive) { - boost::algorithm::to_lower(variable); - } - d_subKey = variable; - } - return; - } - } - } - throw std::runtime_error("Invalid ProtoBuf key '" + key + "'"); -} - -std::vector ProtoBufMetaKey::getValues(const DNSQuestion& dnsquestion) const -{ - auto& idx = s_types.get(); - auto it = idx.find(d_type); - if (it == idx.end()) { - throw std::runtime_error("Trying to get the values of an unsupported type: " + std::to_string(static_cast(d_type))); - } - return (it->d_func)(dnsquestion, d_subKey, d_numericSubKey); -} - -const std::string& ProtoBufMetaKey::getName() const -{ - auto& idx = s_types.get(); - auto it = idx.find(d_type); - if (it == idx.end()) { - throw std::runtime_error("Trying to get the name of an unsupported type: " + std::to_string(static_cast(d_type))); - } - return it->d_name; -} - -const ProtoBufMetaKey::TypeContainer ProtoBufMetaKey::s_types = { - ProtoBufMetaKey::KeyTypeDescription{"sni", Type::SNI, [](const DNSQuestion& dnsquestion, const std::string&, uint8_t) -> std::vector { return {dnsquestion.sni}; }, false}, - ProtoBufMetaKey::KeyTypeDescription{"pool", Type::Pool, [](const DNSQuestion& dnsquestion, const std::string&, uint8_t) -> std::vector { return {dnsquestion.ids.poolName}; }, false}, - ProtoBufMetaKey::KeyTypeDescription{"b64-content", Type::B64Content, [](const DNSQuestion& dnsquestion, const std::string&, uint8_t) -> std::vector { const auto& data = dnsquestion.getData(); return {Base64Encode(std::string(data.begin(), data.end()))}; }, false}, -#ifdef HAVE_DNS_OVER_HTTPS - ProtoBufMetaKey::KeyTypeDescription{"doh-header", Type::DoHHeader, [](const DNSQuestion& dnsquestion, const std::string& name, uint8_t) -> std::vector { - if (!dnsquestion.ids.du) { - return {}; - } - auto headers = dnsquestion.ids.du->getHTTPHeaders(); - auto iter = headers.find(name); - if (iter != headers.end()) { - return {iter->second}; - } - return {}; - }, - true, false}, - ProtoBufMetaKey::KeyTypeDescription{"doh-host", Type::DoHHost, [](const DNSQuestion& dnsquestion, const std::string&, uint8_t) -> std::vector { - if (dnsquestion.ids.du) { - return {dnsquestion.ids.du->getHTTPHost()}; - } - return {}; - }, - true, false}, - ProtoBufMetaKey::KeyTypeDescription{"doh-path", Type::DoHPath, [](const DNSQuestion& dnsquestion, const std::string&, uint8_t) -> std::vector { - if (dnsquestion.ids.du) { - return {dnsquestion.ids.du->getHTTPPath()}; - } - return {}; - }, - false}, - ProtoBufMetaKey::KeyTypeDescription{"doh-query-string", Type::DoHQueryString, [](const DNSQuestion& dnsquestion, const std::string&, uint8_t) -> std::vector { - if (dnsquestion.ids.du) { - return {dnsquestion.ids.du->getHTTPQueryString()}; - } - return {}; - }, - false}, - ProtoBufMetaKey::KeyTypeDescription{"doh-scheme", Type::DoHScheme, [](const DNSQuestion& dnsquestion, const std::string&, uint8_t) -> std::vector { - if (dnsquestion.ids.du) { - return {dnsquestion.ids.du->getHTTPScheme()}; - } - return {}; - }, - false, false}, -#endif // HAVE_DNS_OVER_HTTPS - ProtoBufMetaKey::KeyTypeDescription{"proxy-protocol-value", Type::ProxyProtocolValue, [](const DNSQuestion& dnsquestion, const std::string&, uint8_t numericSubKey) -> std::vector { - if (!dnsquestion.proxyProtocolValues) { - return {}; - } - for (const auto& value : *dnsquestion.proxyProtocolValues) { - if (value.type == numericSubKey) { - return {value.content}; - } - } - return {}; - }, - true, false, true}, - ProtoBufMetaKey::KeyTypeDescription{"proxy-protocol-values", Type::ProxyProtocolValues, [](const DNSQuestion& dnsquestion, const std::string&, uint8_t) -> std::vector { - std::vector result; - if (!dnsquestion.proxyProtocolValues) { - return result; - } - for (const auto& value : *dnsquestion.proxyProtocolValues) { - result.push_back(std::to_string(value.type) + ":" + value.content); - } - return result; - }}, - ProtoBufMetaKey::KeyTypeDescription{"tag", Type::Tag, [](const DNSQuestion& dnsquestion, const std::string& subKey, uint8_t) -> std::vector { - if (!dnsquestion.ids.qTag) { - return {}; - } - for (const auto& [key, value] : *dnsquestion.ids.qTag) { - if (key == subKey) { - return {value}; - } - } - return {}; - }, - true, true}, - ProtoBufMetaKey::KeyTypeDescription{"tags", Type::Tags, [](const DNSQuestion& dnsquestion, const std::string&, uint8_t) -> std::vector { - std::vector result; - if (!dnsquestion.ids.qTag) { - return result; - } - for (const auto& [key, value] : *dnsquestion.ids.qTag) { - if (value.empty()) { - /* avoids a spurious ':' when the value is empty */ - result.push_back(key); - } - else { - auto tag = key; - tag.append(":"); - tag.append(value); - result.push_back(tag); - } - } - return result; - }}, -}; - -#endif /* DISABLE_PROTOBUF */ diff --git a/pdns/dnsdist-protobuf.hh b/pdns/dnsdist-protobuf.hh deleted file mode 100644 index c2dee817daf3..000000000000 --- a/pdns/dnsdist-protobuf.hh +++ /dev/null @@ -1,167 +0,0 @@ -/* - * This file is part of PowerDNS or dnsdist. - * Copyright -- PowerDNS.COM B.V. and its contributors - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of version 2 of the GNU General Public License as - * published by the Free Software Foundation. - * - * In addition, for the avoidance of any doubt, permission is granted to - * link this program with OpenSSL and to (re)distribute the binaries - * produced as the result of such linking. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ -#pragma once - -#include "dnsdist.hh" -#include "dnsname.hh" - -#ifndef DISABLE_PROTOBUF -#include "protozero.hh" - -class DNSDistProtoBufMessage -{ -public: - DNSDistProtoBufMessage(const DNSQuestion& dnsquestion); - DNSDistProtoBufMessage(const DNSResponse& dnsresponse, bool includeCNAME); - DNSDistProtoBufMessage(const DNSQuestion&&) = delete; - DNSDistProtoBufMessage(const DNSResponse&&, bool) = delete; - - void setServerIdentity(const std::string& serverId); - void setRequestor(const ComboAddress& requestor); - void setResponder(const ComboAddress& responder); - void setRequestorPort(uint16_t port); - void setResponderPort(uint16_t port); - void setResponseCode(uint8_t rcode); - void setType(pdns::ProtoZero::Message::MessageType type); - void setHTTPVersion(pdns::ProtoZero::Message::HTTPVersion version); - void setBytes(size_t bytes); - void setTime(time_t sec, uint32_t usec); - void setQueryTime(time_t sec, uint32_t usec); - void setQuestion(const DNSName& name, uint16_t qtype, uint16_t qclass); - void setEDNSSubnet(const Netmask& netmask); - - void addTag(const std::string& strValue); - void addMeta(const std::string& key, std::vector&& strValues, const std::vector& intValues); - void addRR(DNSName&& qname, uint16_t uType, uint16_t uClass, uint32_t uTTL, const std::string& data); - - void serialize(std::string& data) const; - - [[nodiscard]] std::string toDebugString() const; - -private: - struct PBRecord - { - DNSName d_name; - std::string d_data; - uint32_t d_ttl; - uint16_t d_type; - uint16_t d_class; - }; - struct PBQuestion - { - PBQuestion(DNSName name, uint16_t type, uint16_t class_) : - d_name(std::move(name)), d_type(type), d_class(class_) - { - } - - DNSName d_name; - uint16_t d_type; - uint16_t d_class; - }; - - std::vector d_additionalRRs; - std::vector d_additionalTags; - struct MetaValue - { - std::unordered_set d_strings; - std::unordered_set d_integers; - }; - std::unordered_map d_metaTags; - - // NOLINTNEXTLINE(cppcoreguidelines-avoid-const-or-ref-data-members) - const DNSQuestion& d_dq; - const DNSResponse* d_dr{nullptr}; - const std::string* d_ServerIdentityRef{nullptr}; - - boost::optional d_question{boost::none}; - boost::optional d_serverIdentity{boost::none}; - boost::optional d_requestor{boost::none}; - boost::optional d_responder{boost::none}; - boost::optional d_ednsSubnet{boost::none}; - boost::optional> d_time{boost::none}; - boost::optional> d_queryTime{boost::none}; - boost::optional d_bytes{boost::none}; - boost::optional d_rcode{boost::none}; - - pdns::ProtoZero::Message::MessageType d_type{pdns::ProtoZero::Message::MessageType::DNSQueryType}; - bool d_includeCNAME{false}; -}; - -class ProtoBufMetaKey -{ - enum class Type : uint8_t - { - SNI, - Pool, - B64Content, - DoHHeader, - DoHHost, - DoHPath, - DoHQueryString, - DoHScheme, - ProxyProtocolValue, - ProxyProtocolValues, - Tag, - Tags - }; - - struct KeyTypeDescription - { - // NOLINTNEXTLINE(cppcoreguidelines-avoid-const-or-ref-data-members) - const std::string d_name; - // NOLINTNEXTLINE(cppcoreguidelines-avoid-const-or-ref-data-members) - const Type d_type; - // NOLINTNEXTLINE(cppcoreguidelines-avoid-const-or-ref-data-members) - const std::function(const DNSQuestion&, const std::string&, uint8_t)> d_func; - bool d_prefix{false}; - bool d_caseSensitive{true}; - bool d_numeric{false}; - }; - - struct NameTag - { - }; - struct TypeTag - { - }; - - using TypeContainer = boost::multi_index_container< - KeyTypeDescription, - indexed_by< - hashed_unique, member>, - hashed_unique, member>>>; - - static const TypeContainer s_types; - -public: - ProtoBufMetaKey(const std::string& key); - - [[nodiscard]] const std::string& getName() const; - [[nodiscard]] std::vector getValues(const DNSQuestion& dnsquestion) const; - -private: - std::string d_subKey; - uint8_t d_numericSubKey{0}; - Type d_type; -}; - -#endif /* DISABLE_PROTOBUF */ diff --git a/pdns/dnsdist-protocols.cc b/pdns/dnsdist-protocols.cc deleted file mode 100644 index 886e7ee42064..000000000000 --- a/pdns/dnsdist-protocols.cc +++ /dev/null @@ -1,95 +0,0 @@ -/* - * This file is part of PowerDNS or dnsdist. - * Copyright -- PowerDNS.COM B.V. and its contributors - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of version 2 of the GNU General Public License as - * published by the Free Software Foundation. - * - * In addition, for the avoidance of any doubt, permission is granted to - * link this program with OpenSSL and to (re)distribute the binaries - * produced as the result of such linking. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include -#include - -#include "dnsdist-protocols.hh" - -namespace dnsdist -{ -const std::array Protocol::s_names = { - "DoUDP", - "DoTCP", - "DNSCryptUDP", - "DNSCryptTCP", - "DoT", - "DoH", - "DoQ", - "DoH3"}; - -const std::array Protocol::s_prettyNames = { - "Do53 UDP", - "Do53 TCP", - "DNSCrypt UDP", - "DNSCrypt TCP", - "DNS over TLS", - "DNS over HTTPS", - "DNS over QUIC", - "DNS over HTTP/3"}; - -Protocol::Protocol(const std::string& s) -{ - const auto& it = std::find(s_names.begin(), s_names.end(), s); - if (it == s_names.end()) { - throw std::runtime_error("Unknown protocol name: '" + s + "'"); - } - - auto index = std::distance(s_names.begin(), it); - d_protocol = static_cast(index); -} - -bool Protocol::operator==(Protocol::typeenum type) const -{ - return d_protocol == type; -} - -bool Protocol::operator!=(Protocol::typeenum type) const -{ - return d_protocol != type; -} - -const std::string& Protocol::toString() const -{ - return s_names.at(static_cast(d_protocol)); -} - -const std::string& Protocol::toPrettyString() const -{ - return s_prettyNames.at(static_cast(d_protocol)); -} - -bool Protocol::isUDP() const -{ - return d_protocol == DoUDP || d_protocol == DNSCryptUDP; -} - -bool Protocol::isEncrypted() const -{ - return d_protocol != DoUDP && d_protocol != DoTCP; -} - -uint8_t Protocol::toNumber() const -{ - return static_cast(d_protocol); -} -} diff --git a/pdns/dnsdist-protocols.hh b/pdns/dnsdist-protocols.hh deleted file mode 100644 index beb43ed3d71d..000000000000 --- a/pdns/dnsdist-protocols.hh +++ /dev/null @@ -1,71 +0,0 @@ -/* - * This file is part of PowerDNS or dnsdist. - * Copyright -- PowerDNS.COM B.V. and its contributors - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of version 2 of the GNU General Public License as - * published by the Free Software Foundation. - * - * In addition, for the avoidance of any doubt, permission is granted to - * link this program with OpenSSL and to (re)distribute the binaries - * produced as the result of such linking. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ -#pragma once - -#include -#include -#include - -namespace dnsdist -{ -class Protocol -{ -public: - enum typeenum : uint8_t - { - DoUDP = 0, - DoTCP, - DNSCryptUDP, - DNSCryptTCP, - DoT, - DoH, - DoQ, - DoH3 - }; - - Protocol(typeenum protocol = DoUDP) : - d_protocol(protocol) - { - if (protocol >= s_names.size()) { - throw std::runtime_error("Unknown protocol: '" + std::to_string(protocol) + "'"); - } - } - - explicit Protocol(const std::string& protocol); - - bool operator==(typeenum) const; - bool operator!=(typeenum) const; - - const std::string& toString() const; - const std::string& toPrettyString() const; - bool isUDP() const; - bool isEncrypted() const; - uint8_t toNumber() const; - -private: - typeenum d_protocol; - - static constexpr size_t s_numberOfProtocols = 8; - static const std::array s_names; - static const std::array s_prettyNames; -}; -} diff --git a/pdns/dnsdist-rings.cc b/pdns/dnsdist-rings.cc deleted file mode 100644 index b97b44e6459d..000000000000 --- a/pdns/dnsdist-rings.cc +++ /dev/null @@ -1,225 +0,0 @@ -/* - * This file is part of PowerDNS or dnsdist. - * Copyright -- PowerDNS.COM B.V. and its contributors - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of version 2 of the GNU General Public License as - * published by the Free Software Foundation. - * - * In addition, for the avoidance of any doubt, permission is granted to - * link this program with OpenSSL and to (re)distribute the binaries - * produced as the result of such linking. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include - -#include "dnsdist-rings.hh" - -void Rings::setCapacity(size_t newCapacity, size_t numberOfShards) -{ - if (d_initialized) { - throw std::runtime_error("Rings::setCapacity() should not be called once the rings have been initialized"); - } - d_capacity = newCapacity; - d_numberOfShards = numberOfShards; -} - -void Rings::init() -{ - if (d_initialized.exchange(true)) { - throw std::runtime_error("Rings::init() should only be called once"); - } - - if (d_numberOfShards <= 1) { - d_nbLockTries = 0; - } - - d_shards.resize(d_numberOfShards); - - /* resize all the rings */ - for (auto& shard : d_shards) { - shard = std::make_unique(); - if (shouldRecordQueries()) { - shard->queryRing.lock()->set_capacity(d_capacity / d_numberOfShards); - } - if (shouldRecordResponses()) { - shard->respRing.lock()->set_capacity(d_capacity / d_numberOfShards); - } - } - - /* we just recreated the shards so they are now empty */ - d_nbQueryEntries = 0; - d_nbResponseEntries = 0; -} - -void Rings::setNumberOfLockRetries(size_t retries) -{ - if (d_numberOfShards <= 1) { - d_nbLockTries = 0; - } else { - d_nbLockTries = retries; - } -} - -void Rings::setRecordQueries(bool record) -{ - d_recordQueries = record; -} - -void Rings::setRecordResponses(bool record) -{ - d_recordResponses = record; -} - -size_t Rings::numDistinctRequestors() -{ - std::set s; - for (const auto& shard : d_shards) { - auto rl = shard->queryRing.lock(); - for (const auto& q : *rl) { - s.insert(q.requestor); - } - } - return s.size(); -} - -std::unordered_map>> Rings::getTopBandwidth(unsigned int numentries) -{ - map counts; - uint64_t total=0; - for (const auto& shard : d_shards) { - { - auto rl = shard->queryRing.lock(); - for(const auto& q : *rl) { - counts[q.requestor] += q.size; - total+=q.size; - } - } - { - auto rl = shard->respRing.lock(); - for(const auto& r : *rl) { - counts[r.requestor] += r.size; - total+=r.size; - } - } - } - - typedef vector> ret_t; - ret_t rcounts; - rcounts.reserve(counts.size()); - for(const auto& p : counts) - rcounts.push_back({p.second, p.first}); - numentries = rcounts.size() < numentries ? rcounts.size() : numentries; - partial_sort(rcounts.begin(), rcounts.begin()+numentries, rcounts.end(), [](const ret_t::value_type&a, const ret_t::value_type&b) - { - return(b.first < a.first); - }); - std::unordered_map>> ret; - uint64_t rest = 0; - int count = 1; - for(const auto& rc : rcounts) { - if (count == static_cast(numentries + 1)) { - rest+=rc.first; - } - else { - ret.insert({count++, {rc.second.toString(), rc.first, 100.0*rc.first/total}}); - } - } - - if (total > 0) { - ret.insert({count, {"Rest", rest, 100.0*rest/total}}); - } - else { - ret.insert({count, {"Rest", rest, 100.0 }}); - } - - return ret; -} - -size_t Rings::loadFromFile(const std::string& filepath, const struct timespec& now) -{ - ifstream ifs(filepath); - if (!ifs) { - throw std::runtime_error("unable to open the file at " + filepath); - } - - size_t inserted = 0; - string line; - dnsheader dh; - memset(&dh, 0, sizeof(dh)); - - while (std::getline(ifs, line)) { - boost::trim_right_if(line, boost::is_any_of(" \r\n\x1a")); - boost::trim_left(line); - bool isResponse = false; - vector parts; - stringtok(parts, line, " \t,"); - - if (parts.size() == 8) { - } - else if (parts.size() >= 11 && parts.size() <= 13) { - isResponse = true; - } - else { - cerr<<"skipping line with "< timeStr; - stringtok(timeStr, parts.at(idx++), "."); - if (timeStr.size() != 2) { - cerr<<"skipping invalid time "< -#include - -#include - -#include "circular_buffer.hh" -#include "dnsname.hh" -#include "iputils.hh" -#include "lock.hh" -#include "stat_t.hh" -#include "dnsdist-protocols.hh" -#include "dnsdist-mac-address.hh" - -struct Rings { - struct Query - { - ComboAddress requestor; - DNSName name; - struct timespec when; - struct dnsheader dh; - uint16_t size; - uint16_t qtype; - // incoming protocol - dnsdist::Protocol protocol; -#if defined(DNSDIST_RINGS_WITH_MACADDRESS) - dnsdist::MacAddress macaddress; - bool hasmac{false}; -#endif - }; - struct Response - { - ComboAddress requestor; - ComboAddress ds; // who handled it - DNSName name; - struct timespec when; - struct dnsheader dh; - unsigned int usec; - uint16_t size; - uint16_t qtype; - // outgoing protocol - dnsdist::Protocol protocol; - - bool isACacheHit() const; - }; - - struct Shard - { - LockGuarded> queryRing; - LockGuarded> respRing; - }; - - Rings(size_t capacity=10000, size_t numberOfShards=10, size_t nbLockTries=5, bool keepLockingStats=false): d_blockingQueryInserts(0), d_blockingResponseInserts(0), d_deferredQueryInserts(0), d_deferredResponseInserts(0), d_nbQueryEntries(0), d_nbResponseEntries(0), d_currentShardId(0), d_capacity(capacity), d_numberOfShards(numberOfShards), d_nbLockTries(nbLockTries), d_keepLockingStats(keepLockingStats) - { - } - - std::unordered_map > > getTopBandwidth(unsigned int numentries); - size_t numDistinctRequestors(); - /* this function should not be called after init() has been called */ - void setCapacity(size_t newCapacity, size_t numberOfShards); - - /* This function should only be called at configuration time before any query or response has been inserted */ - void init(); - - void setNumberOfLockRetries(size_t retries); - void setRecordQueries(bool); - void setRecordResponses(bool); - - size_t getNumberOfShards() const - { - return d_numberOfShards; - } - - size_t getNumberOfQueryEntries() const - { - return d_nbQueryEntries; - } - - size_t getNumberOfResponseEntries() const - { - return d_nbResponseEntries; - } - - void insertQuery(const struct timespec& when, const ComboAddress& requestor, const DNSName& name, uint16_t qtype, uint16_t size, const struct dnsheader& dh, dnsdist::Protocol protocol) - { -#if defined(DNSDIST_RINGS_WITH_MACADDRESS) - dnsdist::MacAddress macaddress; - bool hasmac{false}; - if (dnsdist::MacAddressesCache::get(requestor, macaddress.data(), macaddress.size()) == 0) { - hasmac = true; - } -#endif - for (size_t idx = 0; idx < d_nbLockTries; idx++) { - auto& shard = getOneShard(); - auto lock = shard->queryRing.try_lock(); - if (lock.owns_lock()) { -#if defined(DNSDIST_RINGS_WITH_MACADDRESS) - insertQueryLocked(*lock, when, requestor, name, qtype, size, dh, protocol, macaddress, hasmac); -#else - insertQueryLocked(*lock, when, requestor, name, qtype, size, dh, protocol); -#endif - return; - } - if (d_keepLockingStats) { - ++d_deferredQueryInserts; - } - } - - /* out of luck, let's just wait */ - if (d_keepLockingStats) { - ++d_blockingResponseInserts; - } - auto& shard = getOneShard(); - auto lock = shard->queryRing.lock(); -#if defined(DNSDIST_RINGS_WITH_MACADDRESS) - insertQueryLocked(*lock, when, requestor, name, qtype, size, dh, protocol, macaddress, hasmac); -#else - insertQueryLocked(*lock, when, requestor, name, qtype, size, dh, protocol); -#endif - } - - void insertResponse(const struct timespec& when, const ComboAddress& requestor, const DNSName& name, uint16_t qtype, unsigned int usec, unsigned int size, const struct dnsheader& dh, const ComboAddress& backend, dnsdist::Protocol protocol) - { - for (size_t idx = 0; idx < d_nbLockTries; idx++) { - auto& shard = getOneShard(); - auto lock = shard->respRing.try_lock(); - if (lock.owns_lock()) { - insertResponseLocked(*lock, when, requestor, name, qtype, usec, size, dh, backend, protocol); - return; - } - if (d_keepLockingStats) { - ++d_deferredResponseInserts; - } - } - - /* out of luck, let's just wait */ - if (d_keepLockingStats) { - ++d_blockingResponseInserts; - } - auto& shard = getOneShard(); - auto lock = shard->respRing.lock(); - insertResponseLocked(*lock, when, requestor, name, qtype, usec, size, dh, backend, protocol); - } - - void clear() - { - for (auto& shard : d_shards) { - shard->queryRing.lock()->clear(); - shard->respRing.lock()->clear(); - } - - d_nbQueryEntries.store(0); - d_nbResponseEntries.store(0); - d_currentShardId.store(0); - d_blockingQueryInserts.store(0); - d_blockingResponseInserts.store(0); - d_deferredQueryInserts.store(0); - d_deferredResponseInserts.store(0); - } - - /* this should be called in the unit tests, and never at runtime */ - void reset() - { - clear(); - d_initialized = false; - } - - /* load the content of the ring buffer from a file in the format emitted by grepq(), - only useful for debugging purposes */ - size_t loadFromFile(const std::string& filepath, const struct timespec& now); - - bool shouldRecordQueries() const - { - return d_recordQueries; - } - - bool shouldRecordResponses() const - { - return d_recordResponses; - } - - std::vector > d_shards; - pdns::stat_t d_blockingQueryInserts; - pdns::stat_t d_blockingResponseInserts; - pdns::stat_t d_deferredQueryInserts; - pdns::stat_t d_deferredResponseInserts; - -private: - size_t getShardId() - { - return (d_currentShardId++ % d_numberOfShards); - } - - std::unique_ptr& getOneShard() - { - return d_shards[getShardId()]; - } - -#if defined(DNSDIST_RINGS_WITH_MACADDRESS) - void insertQueryLocked(boost::circular_buffer& ring, const struct timespec& when, const ComboAddress& requestor, const DNSName& name, uint16_t qtype, uint16_t size, const struct dnsheader& dh, dnsdist::Protocol protocol, const dnsdist::MacAddress& macaddress, const bool hasmac) -#else - void insertQueryLocked(boost::circular_buffer& ring, const struct timespec& when, const ComboAddress& requestor, const DNSName& name, uint16_t qtype, uint16_t size, const struct dnsheader& dh, dnsdist::Protocol protocol) -#endif - { - if (!ring.full()) { - d_nbQueryEntries++; - } -#if defined(DNSDIST_RINGS_WITH_MACADDRESS) - Rings::Query query{requestor, name, when, dh, size, qtype, protocol, dnsdist::MacAddress{""}, hasmac}; - if (hasmac) { - memcpy(query.macaddress.data(), macaddress.data(), macaddress.size()); - } - ring.push_back(std::move(query)); -#else - ring.push_back({requestor, name, when, dh, size, qtype, protocol}); -#endif - } - - void insertResponseLocked(boost::circular_buffer& ring, const struct timespec& when, const ComboAddress& requestor, const DNSName& name, uint16_t qtype, unsigned int usec, uint16_t size, const struct dnsheader& dh, const ComboAddress& backend, dnsdist::Protocol protocol) - { - if (!ring.full()) { - d_nbResponseEntries++; - } - ring.push_back({requestor, backend, name, when, dh, usec, size, qtype, protocol}); - } - - std::atomic d_nbQueryEntries; - std::atomic d_nbResponseEntries; - std::atomic d_currentShardId; - std::atomic d_initialized{false}; - - size_t d_capacity; - size_t d_numberOfShards; - size_t d_nbLockTries = 5; - bool d_keepLockingStats{false}; - bool d_recordQueries{true}; - bool d_recordResponses{true}; -}; - -extern Rings g_rings; diff --git a/pdns/dnsdist-snmp.cc b/pdns/dnsdist-snmp.cc deleted file mode 100644 index 856fccb111d1..000000000000 --- a/pdns/dnsdist-snmp.cc +++ /dev/null @@ -1,616 +0,0 @@ - -#include "dnsdist-snmp.hh" -#include "dnsdist-metrics.hh" -#include "dolog.hh" - -bool g_snmpEnabled{false}; -bool g_snmpTrapsEnabled{false}; -DNSDistSNMPAgent* g_snmpAgent{nullptr}; - -#ifdef HAVE_NET_SNMP - -#define DNSDIST_OID 1, 3, 6, 1, 4, 1, 43315, 3 -#define DNSDIST_STATS_OID DNSDIST_OID, 1 -#define DNSDIST_STATS_TABLE_OID DNSDIST_OID, 2 -#define DNSDIST_TRAPS_OID DNSDIST_OID, 10, 0 -#define DNSDIST_TRAP_OBJECTS_OID DNSDIST_OID, 11 - -static const oid queriesOID[] = { DNSDIST_STATS_OID, 1 }; -static const oid responsesOID[] = { DNSDIST_STATS_OID, 2 }; -static const oid servfailResponsesOID[] = { DNSDIST_STATS_OID, 3 }; -static const oid aclDropsOID[] = { DNSDIST_STATS_OID, 4 }; -// 5 was BlockFilter, removed in 1.2.0 -static const oid ruleDropOID[] = { DNSDIST_STATS_OID, 6 }; -static const oid ruleNXDomainOID[] = { DNSDIST_STATS_OID, 7 }; -static const oid ruleRefusedOID[] = { DNSDIST_STATS_OID, 8 }; -static const oid selfAnsweredOID[] = { DNSDIST_STATS_OID, 9 }; -static const oid downstreamTimeoutsOID[] = { DNSDIST_STATS_OID, 10 }; -static const oid downstreamSendErrorsOID[] = { DNSDIST_STATS_OID, 11 }; -static const oid truncFailOID[] = { DNSDIST_STATS_OID, 12 }; -static const oid noPolicyOID[] = { DNSDIST_STATS_OID, 13 }; -static const oid latency0_1OID[] = { DNSDIST_STATS_OID, 14 }; -static const oid latency1_10OID[] = { DNSDIST_STATS_OID, 15 }; -static const oid latency10_50OID[] = { DNSDIST_STATS_OID, 16 }; -static const oid latency50_100OID[] = { DNSDIST_STATS_OID, 17 }; -static const oid latency100_1000OID[] = { DNSDIST_STATS_OID, 18 }; -static const oid latencySlowOID[] = { DNSDIST_STATS_OID, 19 }; -static const oid latencyAvg100OID[] = { DNSDIST_STATS_OID, 20 }; -static const oid latencyAvg1000OID[] = { DNSDIST_STATS_OID, 21 }; -static const oid latencyAvg10000OID[] = { DNSDIST_STATS_OID, 22 }; -static const oid latencyAvg1000000OID[] = { DNSDIST_STATS_OID, 23 }; -static const oid uptimeOID[] = { DNSDIST_STATS_OID, 24 }; -static const oid realMemoryUsageOID[] = { DNSDIST_STATS_OID, 25 }; -static const oid nonCompliantQueriesOID[] = { DNSDIST_STATS_OID, 26 }; -static const oid nonCompliantResponsesOID[] = { DNSDIST_STATS_OID, 27 }; -static const oid rdQueriesOID[] = { DNSDIST_STATS_OID, 28 }; -static const oid emptyQueriesOID[] = { DNSDIST_STATS_OID, 29 }; -static const oid cacheHitsOID[] = { DNSDIST_STATS_OID, 30 }; -static const oid cacheMissesOID[] = { DNSDIST_STATS_OID, 31 }; -static const oid cpuUserMSecOID[] = { DNSDIST_STATS_OID, 32 }; -static const oid cpuSysMSecOID[] = { DNSDIST_STATS_OID, 33 }; -static const oid fdUsageOID[] = { DNSDIST_STATS_OID, 34 }; -static const oid dynBlockedOID[] = { DNSDIST_STATS_OID, 35 }; -static const oid dynBlockedNMGSizeOID[] = { DNSDIST_STATS_OID, 36 }; -static const oid ruleServFailOID[] = { DNSDIST_STATS_OID, 37 }; -static const oid securityStatusOID[] = { DNSDIST_STATS_OID, 38 }; -static const oid specialMemoryUsageOID[] = { DNSDIST_STATS_OID, 39 }; -static const oid ruleTruncatedOID[] = { DNSDIST_STATS_OID, 40 }; - -static std::unordered_map s_statsMap; - -/* We are never called for a GETNEXT if it's registered as a - "instance", as it's "magically" handled for us. */ -/* a instance handler also only hands us one request at a time, so - we don't need to loop over a list of requests; we'll only get one. */ - -static int handleCounter64Stats(netsnmp_mib_handler* handler, - netsnmp_handler_registration* reginfo, - netsnmp_agent_request_info* reqinfo, - netsnmp_request_info* requests) -{ - if (reqinfo->mode != MODE_GET) { - return SNMP_ERR_GENERR; - } - - if (reginfo->rootoid_len != OID_LENGTH(queriesOID) + 1) { - return SNMP_ERR_GENERR; - } - - const auto& it = s_statsMap.find(reginfo->rootoid[reginfo->rootoid_len - 2]); - if (it == s_statsMap.end()) { - return SNMP_ERR_GENERR; - } - - if (const auto& val = std::get_if(&it->second)) { - return DNSDistSNMPAgent::setCounter64Value(requests, (*val)->load()); - } - - return SNMP_ERR_GENERR; -} - -static void registerCounter64Stat(const char* name, const oid statOID[], size_t statOIDLength, pdns::stat_t* ptr) -{ - if (statOIDLength != OID_LENGTH(queriesOID)) { - errlog("Invalid OID for SNMP Counter64 statistic %s", name); - return; - } - - if (s_statsMap.find(statOID[statOIDLength - 1]) != s_statsMap.end()) { - errlog("OID for SNMP Counter64 statistic %s has already been registered", name); - return; - } - - s_statsMap[statOID[statOIDLength - 1]] = ptr; - netsnmp_register_scalar(netsnmp_create_handler_registration(name, - handleCounter64Stats, - statOID, - statOIDLength, - HANDLER_CAN_RONLY)); -} - -static int handleFloatStats(netsnmp_mib_handler* handler, - netsnmp_handler_registration* reginfo, - netsnmp_agent_request_info* reqinfo, - netsnmp_request_info* requests) -{ - if (reqinfo->mode != MODE_GET) { - return SNMP_ERR_GENERR; - } - - if (reginfo->rootoid_len != OID_LENGTH(queriesOID) + 1) { - return SNMP_ERR_GENERR; - } - - const auto& it = s_statsMap.find(reginfo->rootoid[reginfo->rootoid_len - 2]); - if (it == s_statsMap.end()) { - return SNMP_ERR_GENERR; - } - - if (const auto& val = std::get_if(&it->second)) { - std::string str(std::to_string(**val)); - snmp_set_var_typed_value(requests->requestvb, - ASN_OCTET_STR, - str.c_str(), - str.size()); - return SNMP_ERR_NOERROR; - } - - return SNMP_ERR_GENERR; -} - -static void registerFloatStat(const char* name, const oid statOID[], size_t statOIDLength, double* ptr) -{ - if (statOIDLength != OID_LENGTH(queriesOID)) { - errlog("Invalid OID for SNMP Float statistic %s", name); - return; - } - - if (s_statsMap.find(statOID[statOIDLength - 1]) != s_statsMap.end()) { - errlog("OID for SNMP Float statistic %s has already been registered", name); - return; - } - - s_statsMap[statOID[statOIDLength - 1]] = ptr; - netsnmp_register_scalar(netsnmp_create_handler_registration(name, - handleFloatStats, - statOID, - statOIDLength, - HANDLER_CAN_RONLY)); -} - -static int handleGauge64Stats(netsnmp_mib_handler* handler, - netsnmp_handler_registration* reginfo, - netsnmp_agent_request_info* reqinfo, - netsnmp_request_info* requests) -{ - if (reqinfo->mode != MODE_GET) { - return SNMP_ERR_GENERR; - } - - if (reginfo->rootoid_len != OID_LENGTH(queriesOID) + 1) { - return SNMP_ERR_GENERR; - } - - const auto& it = s_statsMap.find(reginfo->rootoid[reginfo->rootoid_len - 2]); - if (it == s_statsMap.end()) { - return SNMP_ERR_GENERR; - } - - std::string str; - uint64_t value = (*std::get_if(&it->second))(str); - return DNSDistSNMPAgent::setCounter64Value(requests, value); -} - -static void registerGauge64Stat(const char* name, const oid statOID[], size_t statOIDLength, dnsdist::metrics::Stats::statfunction_t ptr) -{ - if (statOIDLength != OID_LENGTH(queriesOID)) { - errlog("Invalid OID for SNMP Gauge64 statistic %s", name); - return; - } - - if (s_statsMap.find(statOID[statOIDLength - 1]) != s_statsMap.end()) { - errlog("OID for SNMP Gauge64 statistic %s has already been registered", name); - return; - } - - s_statsMap[statOID[statOIDLength - 1]] = ptr; - netsnmp_register_scalar(netsnmp_create_handler_registration(name, - handleGauge64Stats, - statOID, - statOIDLength, - HANDLER_CAN_RONLY)); -} - -/* column number definitions for table backendStatTable */ -#define COLUMN_BACKENDID 1 -#define COLUMN_BACKENDNAME 2 -#define COLUMN_BACKENDLATENCY 3 -#define COLUMN_BACKENDWEIGHT 4 -#define COLUMN_BACKENDOUTSTANDING 5 -#define COLUMN_BACKENDQPSLIMIT 6 -#define COLUMN_BACKENDREUSED 7 -#define COLUMN_BACKENDSTATE 8 -#define COLUMN_BACKENDADDRESS 9 -#define COLUMN_BACKENDPOOLS 10 -#define COLUMN_BACKENDQPS 11 -#define COLUMN_BACKENDQUERIES 12 -#define COLUMN_BACKENDORDER 13 - -static const oid backendStatTableOID[] = { DNSDIST_STATS_TABLE_OID }; -static const oid backendNameOID[] = { DNSDIST_STATS_TABLE_OID, 1, 2 }; -static const oid backendStateOID[] = { DNSDIST_STATS_TABLE_OID, 1, 8}; -static const oid backendAddressOID[] = { DNSDIST_STATS_TABLE_OID, 1, 9}; - -static const oid socketFamilyOID[] = { DNSDIST_TRAP_OBJECTS_OID, 1, 0 }; -static const oid socketProtocolOID[] = { DNSDIST_TRAP_OBJECTS_OID, 2, 0 }; -static const oid fromAddressOID[] = { DNSDIST_TRAP_OBJECTS_OID, 3, 0 }; -static const oid toAddressOID[] = { DNSDIST_TRAP_OBJECTS_OID, 4, 0 }; -static const oid queryTypeOID[] = { DNSDIST_TRAP_OBJECTS_OID, 5, 0 }; -static const oid querySizeOID[] = { DNSDIST_TRAP_OBJECTS_OID, 6, 0 }; -static const oid queryIDOID[] = { DNSDIST_TRAP_OBJECTS_OID, 7, 0 }; -static const oid qNameOID[] = { DNSDIST_TRAP_OBJECTS_OID, 8, 0 }; -static const oid qClassOID[] = { DNSDIST_TRAP_OBJECTS_OID, 9, 0 }; -static const oid qTypeOID[] = { DNSDIST_TRAP_OBJECTS_OID, 10, 0 }; -static const oid trapReasonOID[] = { DNSDIST_TRAP_OBJECTS_OID, 11, 0 }; - -static const oid backendStatusChangeTrapOID[] = { DNSDIST_TRAPS_OID, 1 }; -static const oid actionTrapOID[] = { DNSDIST_TRAPS_OID, 2 }; -static const oid customTrapOID[] = { DNSDIST_TRAPS_OID, 3 }; - -static servers_t s_servers; -static size_t s_currentServerIdx = 0; - -static netsnmp_variable_list* backendStatTable_get_next_data_point(void** loop_context, - void** my_data_context, - netsnmp_variable_list* put_index_data, - netsnmp_iterator_info* mydata) -{ - if (s_currentServerIdx >= s_servers.size()) { - return NULL; - } - - *my_data_context = (void*) (s_servers[s_currentServerIdx]).get(); - snmp_set_var_typed_integer(put_index_data, ASN_UNSIGNED, s_currentServerIdx); - s_currentServerIdx++; - - return put_index_data; -} - -static netsnmp_variable_list* backendStatTable_get_first_data_point(void** loop_context, - void** data_context, - netsnmp_variable_list* put_index_data, - netsnmp_iterator_info* data) -{ - s_currentServerIdx = 0; - - /* get a copy of the shared_ptrs so they are not - destroyed while we process the request */ - auto dstates = g_dstates.getLocal(); - s_servers.clear(); - s_servers.reserve(dstates->size()); - for (const auto& server : *dstates) { - s_servers.push_back(server); - } - - return backendStatTable_get_next_data_point(loop_context, - data_context, - put_index_data, - data); -} - -static int backendStatTable_handler(netsnmp_mib_handler* handler, - netsnmp_handler_registration* reginfo, - netsnmp_agent_request_info* reqinfo, - netsnmp_request_info* requests) -{ - netsnmp_request_info* request; - - switch (reqinfo->mode) { - case MODE_GET: - for (request = requests; request; request = request->next) { - netsnmp_table_request_info* table_info = netsnmp_extract_table_info(request); - const DownstreamState* server = (const DownstreamState*) netsnmp_extract_iterator_context(request); - - if (!server) { - continue; - } - - switch (table_info->colnum) { - case COLUMN_BACKENDNAME: - snmp_set_var_typed_value(request->requestvb, - ASN_OCTET_STR, - server->getName().c_str(), - server->getName().size()); - break; - case COLUMN_BACKENDLATENCY: - DNSDistSNMPAgent::setCounter64Value(request, - server->getRelevantLatencyUsec() / 1000.0); - break; - case COLUMN_BACKENDWEIGHT: - DNSDistSNMPAgent::setCounter64Value(request, - server->d_config.d_weight); - break; - case COLUMN_BACKENDOUTSTANDING: - DNSDistSNMPAgent::setCounter64Value(request, - server->outstanding.load()); - break; - case COLUMN_BACKENDQPSLIMIT: - DNSDistSNMPAgent::setCounter64Value(request, - server->qps.getRate()); - break; - case COLUMN_BACKENDREUSED: - DNSDistSNMPAgent::setCounter64Value(request, server->reuseds.load()); - break; - case COLUMN_BACKENDSTATE: - { - std::string state(server->getStatus()); - snmp_set_var_typed_value(request->requestvb, - ASN_OCTET_STR, - state.c_str(), - state.size()); - break; - } - case COLUMN_BACKENDADDRESS: - { - std::string addr(server->d_config.remote.toStringWithPort()); - snmp_set_var_typed_value(request->requestvb, - ASN_OCTET_STR, - addr.c_str(), - addr.size()); - break; - } - case COLUMN_BACKENDPOOLS: - { - std::string pools; - for (const auto& p : server->d_config.pools) { - if (!pools.empty()) { - pools+=" "; - } - pools += p; - } - snmp_set_var_typed_value(request->requestvb, - ASN_OCTET_STR, - pools.c_str(), - pools.size()); - break; - } - case COLUMN_BACKENDQPS: - DNSDistSNMPAgent::setCounter64Value(request, server->queryLoad.load()); - break; - case COLUMN_BACKENDQUERIES: - DNSDistSNMPAgent::setCounter64Value(request, server->queries.load()); - break; - case COLUMN_BACKENDORDER: - DNSDistSNMPAgent::setCounter64Value(request, server->d_config.order); - break; - default: - netsnmp_set_request_error(reqinfo, - request, - SNMP_NOSUCHOBJECT); - break; - } - } - break; - } - return SNMP_ERR_NOERROR; -} -#endif /* HAVE_NET_SNMP */ - -bool DNSDistSNMPAgent::sendBackendStatusChangeTrap(const DownstreamState& dss) -{ -#ifdef HAVE_NET_SNMP - const string backendAddress = dss.d_config.remote.toStringWithPort(); - const string backendStatus = dss.getStatus(); - netsnmp_variable_list* varList = nullptr; - - snmp_varlist_add_variable(&varList, - snmpTrapOID.data(), - snmpTrapOID.size(), - ASN_OBJECT_ID, - backendStatusChangeTrapOID, - OID_LENGTH(backendStatusChangeTrapOID) * sizeof(oid)); - - - snmp_varlist_add_variable(&varList, - backendNameOID, - OID_LENGTH(backendNameOID), - ASN_OCTET_STR, - dss.getName().c_str(), - dss.getName().size()); - - snmp_varlist_add_variable(&varList, - backendAddressOID, - OID_LENGTH(backendAddressOID), - ASN_OCTET_STR, - backendAddress.c_str(), - backendAddress.size()); - - snmp_varlist_add_variable(&varList, - backendStateOID, - OID_LENGTH(backendStateOID), - ASN_OCTET_STR, - backendStatus.c_str(), - backendStatus.size()); - - return sendTrap(d_sender, varList); -#else - return true; -#endif /* HAVE_NET_SNMP */ -} - -bool DNSDistSNMPAgent::sendCustomTrap(const std::string& reason) -{ -#ifdef HAVE_NET_SNMP - netsnmp_variable_list* varList = nullptr; - - snmp_varlist_add_variable(&varList, - snmpTrapOID.data(), - snmpTrapOID.size(), - ASN_OBJECT_ID, - customTrapOID, - OID_LENGTH(customTrapOID) * sizeof(oid)); - - snmp_varlist_add_variable(&varList, - trapReasonOID, - OID_LENGTH(trapReasonOID), - ASN_OCTET_STR, - reason.c_str(), - reason.size()); - - return sendTrap(d_sender, varList); -#else - return true; -#endif /* HAVE_NET_SNMP */ -} - -bool DNSDistSNMPAgent::sendDNSTrap(const DNSQuestion& dq, const std::string& reason) -{ -#ifdef HAVE_NET_SNMP - std::string local = dq.ids.origDest.toString(); - std::string remote = dq.ids.origRemote.toString(); - std::string qname = dq.ids.qname.toStringNoDot(); - const uint32_t socketFamily = dq.ids.origRemote.isIPv4() ? 1 : 2; - const uint32_t socketProtocol = dq.overTCP() ? 2 : 1; - const uint32_t queryType = dq.getHeader()->qr ? 2 : 1; - const uint32_t querySize = (uint32_t) dq.getData().size(); - const uint32_t queryID = (uint32_t) ntohs(dq.getHeader()->id); - const uint32_t qType = (uint32_t) dq.ids.qtype; - const uint32_t qClass = (uint32_t) dq.ids.qclass; - - netsnmp_variable_list* varList = nullptr; - - snmp_varlist_add_variable(&varList, - snmpTrapOID.data(), - snmpTrapOID.size(), - ASN_OBJECT_ID, - actionTrapOID, - OID_LENGTH(actionTrapOID) * sizeof(oid)); - - snmp_varlist_add_variable(&varList, - socketFamilyOID, - OID_LENGTH(socketFamilyOID), - ASN_INTEGER, - reinterpret_cast(&socketFamily), - sizeof(socketFamily)); - - snmp_varlist_add_variable(&varList, - socketProtocolOID, - OID_LENGTH(socketProtocolOID), - ASN_INTEGER, - reinterpret_cast(&socketProtocol), - sizeof(socketProtocol)); - - snmp_varlist_add_variable(&varList, - fromAddressOID, - OID_LENGTH(fromAddressOID), - ASN_OCTET_STR, - remote.c_str(), - remote.size()); - - snmp_varlist_add_variable(&varList, - toAddressOID, - OID_LENGTH(toAddressOID), - ASN_OCTET_STR, - local.c_str(), - local.size()); - - snmp_varlist_add_variable(&varList, - queryTypeOID, - OID_LENGTH(queryTypeOID), - ASN_INTEGER, - reinterpret_cast(&queryType), - sizeof(queryType)); - - snmp_varlist_add_variable(&varList, - querySizeOID, - OID_LENGTH(querySizeOID), - ASN_UNSIGNED, - reinterpret_cast(&querySize), - sizeof(querySize)); - - snmp_varlist_add_variable(&varList, - queryIDOID, - OID_LENGTH(queryIDOID), - ASN_UNSIGNED, - reinterpret_cast(&queryID), - sizeof(queryID)); - - snmp_varlist_add_variable(&varList, - qNameOID, - OID_LENGTH(qNameOID), - ASN_OCTET_STR, - qname.c_str(), - qname.size()); - - snmp_varlist_add_variable(&varList, - qClassOID, - OID_LENGTH(qClassOID), - ASN_UNSIGNED, - reinterpret_cast(&qClass), - sizeof(qClass)); - - snmp_varlist_add_variable(&varList, - qTypeOID, - OID_LENGTH(qTypeOID), - ASN_UNSIGNED, - reinterpret_cast(&qType), - sizeof(qType)); - - snmp_varlist_add_variable(&varList, - trapReasonOID, - OID_LENGTH(trapReasonOID), - ASN_OCTET_STR, - reason.c_str(), - reason.size()); - - return sendTrap(d_sender, varList); -#else - return true; -#endif /* HAVE_NET_SNMP */ -} - -DNSDistSNMPAgent::DNSDistSNMPAgent(const std::string& name, const std::string& daemonSocket): SNMPAgent(name, daemonSocket) -{ -#ifdef HAVE_NET_SNMP - - registerCounter64Stat("queries", queriesOID, OID_LENGTH(queriesOID), &dnsdist::metrics::g_stats.queries); - registerCounter64Stat("responses", responsesOID, OID_LENGTH(responsesOID), &dnsdist::metrics::g_stats.responses); - registerCounter64Stat("servfailResponses", servfailResponsesOID, OID_LENGTH(servfailResponsesOID), &dnsdist::metrics::g_stats.servfailResponses); - registerCounter64Stat("aclDrops", aclDropsOID, OID_LENGTH(aclDropsOID), &dnsdist::metrics::g_stats.aclDrops); - registerCounter64Stat("ruleDrop", ruleDropOID, OID_LENGTH(ruleDropOID), &dnsdist::metrics::g_stats.ruleDrop); - registerCounter64Stat("ruleNXDomain", ruleNXDomainOID, OID_LENGTH(ruleNXDomainOID), &dnsdist::metrics::g_stats.ruleNXDomain); - registerCounter64Stat("ruleRefused", ruleRefusedOID, OID_LENGTH(ruleRefusedOID), &dnsdist::metrics::g_stats.ruleRefused); - registerCounter64Stat("ruleServFail", ruleServFailOID, OID_LENGTH(ruleServFailOID), &dnsdist::metrics::g_stats.ruleServFail); - registerCounter64Stat("ruleTruncated", ruleTruncatedOID, OID_LENGTH(ruleTruncatedOID), &dnsdist::metrics::g_stats.ruleTruncated); - registerCounter64Stat("selfAnswered", selfAnsweredOID, OID_LENGTH(selfAnsweredOID), &dnsdist::metrics::g_stats.selfAnswered); - registerCounter64Stat("downstreamTimeouts", downstreamTimeoutsOID, OID_LENGTH(downstreamTimeoutsOID), &dnsdist::metrics::g_stats.downstreamTimeouts); - registerCounter64Stat("downstreamSendErrors", downstreamSendErrorsOID, OID_LENGTH(downstreamSendErrorsOID), &dnsdist::metrics::g_stats.downstreamSendErrors); - registerCounter64Stat("truncFail", truncFailOID, OID_LENGTH(truncFailOID), &dnsdist::metrics::g_stats.truncFail); - registerCounter64Stat("noPolicy", noPolicyOID, OID_LENGTH(noPolicyOID), &dnsdist::metrics::g_stats.noPolicy); - registerCounter64Stat("latency0_1", latency0_1OID, OID_LENGTH(latency0_1OID), &dnsdist::metrics::g_stats.latency0_1); - registerCounter64Stat("latency1_10", latency1_10OID, OID_LENGTH(latency1_10OID), &dnsdist::metrics::g_stats.latency1_10); - registerCounter64Stat("latency10_50", latency10_50OID, OID_LENGTH(latency10_50OID), &dnsdist::metrics::g_stats.latency10_50); - registerCounter64Stat("latency50_100", latency50_100OID, OID_LENGTH(latency50_100OID), &dnsdist::metrics::g_stats.latency50_100); - registerCounter64Stat("latency100_1000", latency100_1000OID, OID_LENGTH(latency100_1000OID), &dnsdist::metrics::g_stats.latency100_1000); - registerCounter64Stat("latencySlow", latencySlowOID, OID_LENGTH(latencySlowOID), &dnsdist::metrics::g_stats.latencySlow); - registerCounter64Stat("nonCompliantQueries", nonCompliantQueriesOID, OID_LENGTH(nonCompliantQueriesOID), &dnsdist::metrics::g_stats.nonCompliantQueries); - registerCounter64Stat("nonCompliantResponses", nonCompliantResponsesOID, OID_LENGTH(nonCompliantResponsesOID), &dnsdist::metrics::g_stats.nonCompliantResponses); - registerCounter64Stat("rdQueries", rdQueriesOID, OID_LENGTH(rdQueriesOID), &dnsdist::metrics::g_stats.rdQueries); - registerCounter64Stat("emptyQueries", emptyQueriesOID, OID_LENGTH(emptyQueriesOID), &dnsdist::metrics::g_stats.emptyQueries); - registerCounter64Stat("cacheHits", cacheHitsOID, OID_LENGTH(cacheHitsOID), &dnsdist::metrics::g_stats.cacheHits); - registerCounter64Stat("cacheMisses", cacheMissesOID, OID_LENGTH(cacheMissesOID), &dnsdist::metrics::g_stats.cacheMisses); - registerCounter64Stat("dynBlocked", dynBlockedOID, OID_LENGTH(dynBlockedOID), &dnsdist::metrics::g_stats.dynBlocked); - registerFloatStat("latencyAvg100", latencyAvg100OID, OID_LENGTH(latencyAvg100OID), &dnsdist::metrics::g_stats.latencyAvg100); - registerFloatStat("latencyAvg1000", latencyAvg1000OID, OID_LENGTH(latencyAvg1000OID), &dnsdist::metrics::g_stats.latencyAvg1000); - registerFloatStat("latencyAvg10000", latencyAvg10000OID, OID_LENGTH(latencyAvg10000OID), &dnsdist::metrics::g_stats.latencyAvg10000); - registerFloatStat("latencyAvg1000000", latencyAvg1000000OID, OID_LENGTH(latencyAvg1000000OID), &dnsdist::metrics::g_stats.latencyAvg1000000); - registerGauge64Stat("uptime", uptimeOID, OID_LENGTH(uptimeOID), &uptimeOfProcess); - registerGauge64Stat("specialMemoryUsage", specialMemoryUsageOID, OID_LENGTH(specialMemoryUsageOID), &getSpecialMemoryUsage); - registerGauge64Stat("cpuUserMSec", cpuUserMSecOID, OID_LENGTH(cpuUserMSecOID), &getCPUTimeUser); - registerGauge64Stat("cpuSysMSec", cpuSysMSecOID, OID_LENGTH(cpuSysMSecOID), &getCPUTimeSystem); - registerGauge64Stat("fdUsage", fdUsageOID, OID_LENGTH(fdUsageOID), &getOpenFileDescriptors); - registerGauge64Stat("dynBlockedNMGSize", dynBlockedNMGSizeOID, OID_LENGTH(dynBlockedNMGSizeOID), [](const std::string&) { return g_dynblockNMG.getLocal()->size(); }); - registerGauge64Stat("securityStatus", securityStatusOID, OID_LENGTH(securityStatusOID), [](const std::string&) { return dnsdist::metrics::g_stats.securityStatus.load(); }); - registerGauge64Stat("realMemoryUsage", realMemoryUsageOID, OID_LENGTH(realMemoryUsageOID), &getRealMemoryUsage); - - - netsnmp_table_registration_info* table_info = SNMP_MALLOC_TYPEDEF(netsnmp_table_registration_info); - netsnmp_table_helper_add_indexes(table_info, - ASN_GAUGE, /* index: backendId */ - 0); - table_info->min_column = COLUMN_BACKENDNAME; - table_info->max_column = COLUMN_BACKENDORDER; - netsnmp_iterator_info* iinfo = SNMP_MALLOC_TYPEDEF(netsnmp_iterator_info); - iinfo->get_first_data_point = backendStatTable_get_first_data_point; - iinfo->get_next_data_point = backendStatTable_get_next_data_point; - iinfo->table_reginfo = table_info; - - netsnmp_register_table_iterator(netsnmp_create_handler_registration("backendStatTable", - backendStatTable_handler, - backendStatTableOID, - OID_LENGTH(backendStatTableOID), - HANDLER_CAN_RONLY), - iinfo); - -#endif /* HAVE_NET_SNMP */ -} diff --git a/pdns/dnsdist-tcp.cc b/pdns/dnsdist-tcp.cc deleted file mode 100644 index c15a14484db5..000000000000 --- a/pdns/dnsdist-tcp.cc +++ /dev/null @@ -1,1667 +0,0 @@ -/* - * This file is part of PowerDNS or dnsdist. - * Copyright -- PowerDNS.COM B.V. and its contributors - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of version 2 of the GNU General Public License as - * published by the Free Software Foundation. - * - * In addition, for the avoidance of any doubt, permission is granted to - * link this program with OpenSSL and to (re)distribute the binaries - * produced as the result of such linking. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include -#include -#include - -#include "dnsdist.hh" -#include "dnsdist-concurrent-connections.hh" -#include "dnsdist-dnsparser.hh" -#include "dnsdist-ecs.hh" -#include "dnsdist-nghttp2-in.hh" -#include "dnsdist-proxy-protocol.hh" -#include "dnsdist-rings.hh" -#include "dnsdist-tcp.hh" -#include "dnsdist-tcp-downstream.hh" -#include "dnsdist-downstream-connection.hh" -#include "dnsdist-tcp-upstream.hh" -#include "dnsdist-xpf.hh" -#include "dnsparser.hh" -#include "dolog.hh" -#include "gettime.hh" -#include "lock.hh" -#include "sstuff.hh" -#include "tcpiohandler.hh" -#include "tcpiohandler-mplexer.hh" -#include "threadname.hh" - -/* TCP: the grand design. - We forward 'messages' between clients and downstream servers. Messages are 65k bytes large, tops. - An answer might theoretically consist of multiple messages (for example, in the case of AXFR), initially - we will not go there. - - In a sense there is a strong symmetry between UDP and TCP, once a connection to a downstream has been setup. - This symmetry is broken because of head-of-line blocking within TCP though, necessitating additional connections - to guarantee performance. - - So the idea is to have a 'pool' of available downstream connections, and forward messages to/from them and never queue. - So whenever an answer comes in, we know where it needs to go. - - Let's start naively. -*/ - -size_t g_maxTCPQueriesPerConn{0}; -size_t g_maxTCPConnectionDuration{0}; - -#ifdef __linux__ -// On Linux this gives us 128k pending queries (default is 8192 queries), -// which should be enough to deal with huge spikes -size_t g_tcpInternalPipeBufferSize{1048576U}; -uint64_t g_maxTCPQueuedConnections{10000}; -#else -size_t g_tcpInternalPipeBufferSize{0}; -uint64_t g_maxTCPQueuedConnections{1000}; -#endif - -int g_tcpRecvTimeout{2}; -int g_tcpSendTimeout{2}; -std::atomic g_tcpStatesDumpRequested{0}; - -LockGuarded> dnsdist::IncomingConcurrentTCPConnectionsManager::s_tcpClientsConcurrentConnectionsCount; -size_t dnsdist::IncomingConcurrentTCPConnectionsManager::s_maxTCPConnectionsPerClient = 0; - -IncomingTCPConnectionState::~IncomingTCPConnectionState() -{ - dnsdist::IncomingConcurrentTCPConnectionsManager::accountClosedTCPConnection(d_ci.remote); - - if (d_ci.cs != nullptr) { - timeval now{}; - gettimeofday(&now, nullptr); - - auto diff = now - d_connectionStartTime; - d_ci.cs->updateTCPMetrics(d_queriesCount, diff.tv_sec * 1000 + diff.tv_usec / 1000); - } - - // would have been done when the object is destroyed anyway, - // but that way we make sure it's done before the ConnectionInfo is destroyed, - // closing the descriptor, instead of relying on the declaration order of the objects in the class - d_handler.close(); -} - -dnsdist::Protocol IncomingTCPConnectionState::getProtocol() const -{ - if (d_ci.cs->dohFrontend) { - return dnsdist::Protocol::DoH; - } - if (d_handler.isTLS()) { - return dnsdist::Protocol::DoT; - } - return dnsdist::Protocol::DoTCP; -} - -size_t IncomingTCPConnectionState::clearAllDownstreamConnections() -{ - return t_downstreamTCPConnectionsManager.clear(); -} - -std::shared_ptr IncomingTCPConnectionState::getDownstreamConnection(std::shared_ptr& backend, const std::unique_ptr>& tlvs, const struct timeval& now) -{ - auto downstream = getOwnedDownstreamConnection(backend, tlvs); - - if (!downstream) { - /* we don't have a connection to this backend owned yet, let's get one (it might not be a fresh one, though) */ - downstream = t_downstreamTCPConnectionsManager.getConnectionToDownstream(d_threadData.mplexer, backend, now, std::string()); - if (backend->d_config.useProxyProtocol) { - registerOwnedDownstreamConnection(downstream); - } - } - - return downstream; -} - -static void tcpClientThread(pdns::channel::Receiver&& queryReceiver, pdns::channel::Receiver&& crossProtocolQueryReceiver, pdns::channel::Receiver&& crossProtocolResponseReceiver, pdns::channel::Sender&& crossProtocolResponseSender, std::vector tcpAcceptStates); - -TCPClientCollection::TCPClientCollection(size_t maxThreads, std::vector tcpAcceptStates) : - d_tcpclientthreads(maxThreads), d_maxthreads(maxThreads) -{ - for (size_t idx = 0; idx < maxThreads; idx++) { - addTCPClientThread(tcpAcceptStates); - } -} - -void TCPClientCollection::addTCPClientThread(std::vector& tcpAcceptStates) -{ - try { - auto [queryChannelSender, queryChannelReceiver] = pdns::channel::createObjectQueue(pdns::channel::SenderBlockingMode::SenderNonBlocking, pdns::channel::ReceiverBlockingMode::ReceiverNonBlocking, g_tcpInternalPipeBufferSize); - - auto [crossProtocolQueryChannelSender, crossProtocolQueryChannelReceiver] = pdns::channel::createObjectQueue(pdns::channel::SenderBlockingMode::SenderNonBlocking, pdns::channel::ReceiverBlockingMode::ReceiverNonBlocking, g_tcpInternalPipeBufferSize); - - auto [crossProtocolResponseChannelSender, crossProtocolResponseChannelReceiver] = pdns::channel::createObjectQueue(pdns::channel::SenderBlockingMode::SenderNonBlocking, pdns::channel::ReceiverBlockingMode::ReceiverNonBlocking, g_tcpInternalPipeBufferSize); - - vinfolog("Adding TCP Client thread"); - - if (d_numthreads >= d_tcpclientthreads.size()) { - vinfolog("Adding a new TCP client thread would exceed the vector size (%d/%d), skipping. Consider increasing the maximum amount of TCP client threads with setMaxTCPClientThreads() in the configuration.", d_numthreads.load(), d_tcpclientthreads.size()); - return; - } - - TCPWorkerThread worker(std::move(queryChannelSender), std::move(crossProtocolQueryChannelSender)); - - try { - std::thread clientThread(tcpClientThread, std::move(queryChannelReceiver), std::move(crossProtocolQueryChannelReceiver), std::move(crossProtocolResponseChannelReceiver), std::move(crossProtocolResponseChannelSender), tcpAcceptStates); - clientThread.detach(); - } - catch (const std::runtime_error& e) { - errlog("Error creating a TCP thread: %s", e.what()); - return; - } - - d_tcpclientthreads.at(d_numthreads) = std::move(worker); - ++d_numthreads; - } - catch (const std::exception& e) { - errlog("Error creating TCP worker: %", e.what()); - } -} - -std::unique_ptr g_tcpclientthreads; - -static IOState sendQueuedResponses(std::shared_ptr& state, const struct timeval& now) -{ - IOState result = IOState::Done; - - while (state->active() && !state->d_queuedResponses.empty()) { - DEBUGLOG("queue size is " << state->d_queuedResponses.size() << ", sending the next one"); - TCPResponse resp = std::move(state->d_queuedResponses.front()); - state->d_queuedResponses.pop_front(); - state->d_state = IncomingTCPConnectionState::State::idle; - result = state->sendResponse(now, std::move(resp)); - if (result != IOState::Done) { - return result; - } - } - - state->d_state = IncomingTCPConnectionState::State::idle; - return IOState::Done; -} - -void IncomingTCPConnectionState::handleResponseSent(TCPResponse& currentResponse) -{ - if (currentResponse.d_idstate.qtype == QType::AXFR || currentResponse.d_idstate.qtype == QType::IXFR) { - return; - } - - --d_currentQueriesCount; - - const auto& backend = currentResponse.d_connection ? currentResponse.d_connection->getDS() : currentResponse.d_ds; - if (!currentResponse.d_idstate.selfGenerated && backend) { - const auto& ids = currentResponse.d_idstate; - double udiff = ids.queryRealTime.udiff(); - vinfolog("Got answer from %s, relayed to %s (%s, %d bytes), took %f us", backend->d_config.remote.toStringWithPort(), ids.origRemote.toStringWithPort(), getProtocol().toString(), currentResponse.d_buffer.size(), udiff); - - auto backendProtocol = backend->getProtocol(); - if (backendProtocol == dnsdist::Protocol::DoUDP && !currentResponse.d_idstate.forwardedOverUDP) { - backendProtocol = dnsdist::Protocol::DoTCP; - } - ::handleResponseSent(ids, udiff, d_ci.remote, backend->d_config.remote, static_cast(currentResponse.d_buffer.size()), currentResponse.d_cleartextDH, backendProtocol, true); - } - else { - const auto& ids = currentResponse.d_idstate; - ::handleResponseSent(ids, 0., d_ci.remote, ComboAddress(), static_cast(currentResponse.d_buffer.size()), currentResponse.d_cleartextDH, ids.protocol, false); - } - - currentResponse.d_buffer.clear(); - currentResponse.d_connection.reset(); -} - -static void prependSizeToTCPQuery(PacketBuffer& buffer, size_t proxyProtocolPayloadSize) -{ - if (buffer.size() <= proxyProtocolPayloadSize) { - throw std::runtime_error("The payload size is smaller or equal to the buffer size"); - } - - uint16_t queryLen = proxyProtocolPayloadSize > 0 ? (buffer.size() - proxyProtocolPayloadSize) : buffer.size(); - const std::array sizeBytes{static_cast(queryLen / 256), static_cast(queryLen % 256)}; - /* prepend the size. Yes, this is not the most efficient way but it prevents mistakes - that could occur if we had to deal with the size during the processing, - especially alignment issues */ - buffer.insert(buffer.begin() + static_cast(proxyProtocolPayloadSize), sizeBytes.begin(), sizeBytes.end()); -} - -bool IncomingTCPConnectionState::canAcceptNewQueries(const struct timeval& now) -{ - if (d_hadErrors) { - DEBUGLOG("not accepting new queries because we encountered some error during the processing already"); - return false; - } - - // for DoH, this is already handled by the underlying library - if (!d_ci.cs->dohFrontend && d_currentQueriesCount >= d_ci.cs->d_maxInFlightQueriesPerConn) { - DEBUGLOG("not accepting new queries because we already have " << d_currentQueriesCount << " out of " << d_ci.cs->d_maxInFlightQueriesPerConn); - return false; - } - - if (g_maxTCPQueriesPerConn != 0 && d_queriesCount > g_maxTCPQueriesPerConn) { - vinfolog("not accepting new queries from %s because it reached the maximum number of queries per conn (%d / %d)", d_ci.remote.toStringWithPort(), d_queriesCount, g_maxTCPQueriesPerConn); - return false; - } - - if (maxConnectionDurationReached(g_maxTCPConnectionDuration, now)) { - vinfolog("not accepting new queries from %s because it reached the maximum TCP connection duration", d_ci.remote.toStringWithPort()); - return false; - } - - return true; -} - -void IncomingTCPConnectionState::resetForNewQuery() -{ - d_buffer.clear(); - d_currentPos = 0; - d_querySize = 0; - d_state = State::waitingForQuery; -} - -std::shared_ptr IncomingTCPConnectionState::getOwnedDownstreamConnection(const std::shared_ptr& backend, const std::unique_ptr>& tlvs) -{ - auto connIt = d_ownedConnectionsToBackend.find(backend); - if (connIt == d_ownedConnectionsToBackend.end()) { - DEBUGLOG("no owned connection found for " << backend->getName()); - return nullptr; - } - - for (auto& conn : connIt->second) { - if (conn->canBeReused(true) && conn->matchesTLVs(tlvs)) { - DEBUGLOG("Got one owned connection accepting more for " << backend->getName()); - conn->setReused(); - return conn; - } - DEBUGLOG("not accepting more for " << backend->getName()); - } - - return nullptr; -} - -void IncomingTCPConnectionState::registerOwnedDownstreamConnection(std::shared_ptr& conn) -{ - d_ownedConnectionsToBackend[conn->getDS()].push_front(conn); -} - -/* called when the buffer has been set and the rules have been processed, and only from handleIO (sometimes indirectly via handleQuery) */ -IOState IncomingTCPConnectionState::sendResponse(const struct timeval& now, TCPResponse&& response) -{ - d_state = State::sendingResponse; - - const auto responseSize = static_cast(response.d_buffer.size()); - const std::array sizeBytes{static_cast(responseSize / 256), static_cast(responseSize % 256)}; - /* prepend the size. Yes, this is not the most efficient way but it prevents mistakes - that could occur if we had to deal with the size during the processing, - especially alignment issues */ - response.d_buffer.insert(response.d_buffer.begin(), sizeBytes.begin(), sizeBytes.end()); - d_currentPos = 0; - d_currentResponse = std::move(response); - - try { - auto iostate = d_handler.tryWrite(d_currentResponse.d_buffer, d_currentPos, d_currentResponse.d_buffer.size()); - if (iostate == IOState::Done) { - DEBUGLOG("response sent from " << __PRETTY_FUNCTION__); - handleResponseSent(d_currentResponse); - return iostate; - } - d_lastIOBlocked = true; - DEBUGLOG("partial write"); - return iostate; - } - catch (const std::exception& e) { - vinfolog("Closing TCP client connection with %s: %s", d_ci.remote.toStringWithPort(), e.what()); - DEBUGLOG("Closing TCP client connection: " << e.what()); - ++d_ci.cs->tcpDiedSendingResponse; - - terminateClientConnection(); - - return IOState::Done; - } -} - -void IncomingTCPConnectionState::terminateClientConnection() -{ - DEBUGLOG("terminating client connection"); - d_queuedResponses.clear(); - /* we have already released idle connections that could be reused, - we don't care about the ones still waiting for responses */ - for (auto& backend : d_ownedConnectionsToBackend) { - for (auto& conn : backend.second) { - conn->release(); - } - } - d_ownedConnectionsToBackend.clear(); - - /* meaning we will no longer be 'active' when the backend - response or timeout comes in */ - d_ioState.reset(); - - /* if we do have remaining async descriptors associated with this TLS - connection, we need to defer the destruction of the TLS object until - the engine has reported back, otherwise we have a use-after-free.. */ - auto afds = d_handler.getAsyncFDs(); - if (afds.empty()) { - d_handler.close(); - } - else { - /* we might already be waiting, but we might also not because sometimes we have already been - notified via the descriptor, not received Async again, but the async job still exists.. */ - auto state = shared_from_this(); - for (const auto desc : afds) { - try { - state->d_threadData.mplexer->addReadFD(desc, handleAsyncReady, state); - } - catch (...) { - } - } - } -} - -void IncomingTCPConnectionState::queueResponse(std::shared_ptr& state, const struct timeval& now, TCPResponse&& response, bool fromBackend) -{ - // queue response - state->d_queuedResponses.emplace_back(std::move(response)); - DEBUGLOG("queueing response, state is " << (int)state->d_state << ", queue size is now " << state->d_queuedResponses.size()); - - // when the response comes from a backend, there is a real possibility that we are currently - // idle, and thus not trying to send the response right away would make our ref count go to 0. - // Even if we are waiting for a query, we will not wake up before the new query arrives or a - // timeout occurs - if (state->d_state == State::idle || state->d_state == State::waitingForQuery) { - auto iostate = sendQueuedResponses(state, now); - - if (iostate == IOState::Done && state->active()) { - if (state->canAcceptNewQueries(now)) { - state->resetForNewQuery(); - state->d_state = State::waitingForQuery; - iostate = IOState::NeedRead; - } - else { - state->d_state = State::idle; - } - } - - // for the same reason we need to update the state right away, nobody will do that for us - if (state->active()) { - updateIO(state, iostate, now); - // if we have not finished reading every available byte, we _need_ to do an actual read - // attempt before waiting for the socket to become readable again, because if there is - // buffered data available the socket might never become readable again. - // This is true as soon as we deal with TLS because TLS records are processed one by - // one and might not match what we see at the application layer, so data might already - // be available in the TLS library's buffers. This is especially true when OpenSSL's - // read-ahead mode is enabled because then it buffers even more than one SSL record - // for performance reasons. - if (fromBackend && !state->d_lastIOBlocked) { - state->handleIO(); - } - } - } -} - -void IncomingTCPConnectionState::handleAsyncReady([[maybe_unused]] int desc, FDMultiplexer::funcparam_t& param) -{ - auto state = boost::any_cast>(param); - - /* If we are here, the async jobs for this SSL* are finished - so we should be able to remove all FDs */ - auto afds = state->d_handler.getAsyncFDs(); - for (const auto afd : afds) { - try { - state->d_threadData.mplexer->removeReadFD(afd); - } - catch (...) { - } - } - - if (state->active()) { - /* and now we restart our own I/O state machine */ - state->handleIO(); - } - else { - /* we were only waiting for the engine to come back, - to prevent a use-after-free */ - state->d_handler.close(); - } -} - -void IncomingTCPConnectionState::updateIO(std::shared_ptr& state, IOState newState, const struct timeval& now) -{ - if (newState == IOState::Async) { - auto fds = state->d_handler.getAsyncFDs(); - for (const auto desc : fds) { - state->d_threadData.mplexer->addReadFD(desc, handleAsyncReady, state); - } - state->d_ioState->update(IOState::Done, handleIOCallback, state); - } - else { - state->d_ioState->update(newState, handleIOCallback, state, newState == IOState::NeedWrite ? state->getClientWriteTTD(now) : state->getClientReadTTD(now)); - } -} - -/* called from the backend code when a new response has been received */ -void IncomingTCPConnectionState::handleResponse(const struct timeval& now, TCPResponse&& response) -{ - if (std::this_thread::get_id() != d_creatorThreadID) { - handleCrossProtocolResponse(now, std::move(response)); - return; - } - - std::shared_ptr state = shared_from_this(); - - if (!response.isAsync() && response.d_connection && response.d_connection->getDS() && response.d_connection->getDS()->d_config.useProxyProtocol) { - // if we have added a TCP Proxy Protocol payload to a connection, don't release it to the general pool as no one else will be able to use it anyway - if (!response.d_connection->willBeReusable(true)) { - // if it can't be reused even by us, well - const auto connIt = state->d_ownedConnectionsToBackend.find(response.d_connection->getDS()); - if (connIt != state->d_ownedConnectionsToBackend.end()) { - auto& list = connIt->second; - - for (auto it = list.begin(); it != list.end(); ++it) { - if (*it == response.d_connection) { - try { - response.d_connection->release(); - } - catch (const std::exception& e) { - vinfolog("Error releasing connection: %s", e.what()); - } - list.erase(it); - break; - } - } - } - } - } - - if (response.d_buffer.size() < sizeof(dnsheader)) { - state->terminateClientConnection(); - return; - } - - if (!response.isAsync()) { - try { - auto& ids = response.d_idstate; - unsigned int qnameWireLength{0}; - std::shared_ptr backend = response.d_ds ? response.d_ds : (response.d_connection ? response.d_connection->getDS() : nullptr); - if (backend == nullptr || !responseContentMatches(response.d_buffer, ids.qname, ids.qtype, ids.qclass, backend, qnameWireLength)) { - state->terminateClientConnection(); - return; - } - - if (backend != nullptr) { - ++backend->responses; - } - - DNSResponse dnsResponse(ids, response.d_buffer, backend); - dnsResponse.d_incomingTCPState = state; - - memcpy(&response.d_cleartextDH, dnsResponse.getHeader().get(), sizeof(response.d_cleartextDH)); - - if (!processResponse(response.d_buffer, *state->d_threadData.localRespRuleActions, *state->d_threadData.localCacheInsertedRespRuleActions, dnsResponse, false)) { - state->terminateClientConnection(); - return; - } - - if (dnsResponse.isAsynchronous()) { - /* we are done for now */ - return; - } - } - catch (const std::exception& e) { - vinfolog("Unexpected exception while handling response from backend: %s", e.what()); - state->terminateClientConnection(); - return; - } - } - - ++dnsdist::metrics::g_stats.responses; - ++state->d_ci.cs->responses; - - queueResponse(state, now, std::move(response), true); -} - -struct TCPCrossProtocolResponse -{ - TCPCrossProtocolResponse(TCPResponse&& response, std::shared_ptr& state, const struct timeval& now) : - d_response(std::move(response)), d_state(state), d_now(now) - { - } - TCPCrossProtocolResponse(const TCPCrossProtocolResponse&) = delete; - TCPCrossProtocolResponse& operator=(const TCPCrossProtocolResponse&) = delete; - TCPCrossProtocolResponse(TCPCrossProtocolResponse&&) = delete; - TCPCrossProtocolResponse& operator=(TCPCrossProtocolResponse&&) = delete; - ~TCPCrossProtocolResponse() = default; - - TCPResponse d_response; - std::shared_ptr d_state; - struct timeval d_now; -}; - -class TCPCrossProtocolQuery : public CrossProtocolQuery -{ -public: - TCPCrossProtocolQuery(PacketBuffer&& buffer, InternalQueryState&& ids, std::shared_ptr backend, std::shared_ptr sender) : - CrossProtocolQuery(InternalQuery(std::move(buffer), std::move(ids)), backend), d_sender(std::move(sender)) - { - } - TCPCrossProtocolQuery(const TCPCrossProtocolQuery&) = delete; - TCPCrossProtocolQuery& operator=(const TCPCrossProtocolQuery&) = delete; - TCPCrossProtocolQuery(TCPCrossProtocolQuery&&) = delete; - TCPCrossProtocolQuery& operator=(TCPCrossProtocolQuery&&) = delete; - ~TCPCrossProtocolQuery() override = default; - - std::shared_ptr getTCPQuerySender() override - { - return d_sender; - } - - DNSQuestion getDQ() override - { - auto& ids = query.d_idstate; - DNSQuestion dnsQuestion(ids, query.d_buffer); - dnsQuestion.d_incomingTCPState = d_sender; - return dnsQuestion; - } - - DNSResponse getDR() override - { - auto& ids = query.d_idstate; - DNSResponse dnsResponse(ids, query.d_buffer, downstream); - dnsResponse.d_incomingTCPState = d_sender; - return dnsResponse; - } - -private: - std::shared_ptr d_sender; -}; - -std::unique_ptr IncomingTCPConnectionState::getCrossProtocolQuery(PacketBuffer&& query, InternalQueryState&& state, const std::shared_ptr& backend) -{ - return std::make_unique(std::move(query), std::move(state), backend, shared_from_this()); -} - -std::unique_ptr getTCPCrossProtocolQueryFromDQ(DNSQuestion& dnsQuestion) -{ - auto state = dnsQuestion.getIncomingTCPState(); - if (!state) { - throw std::runtime_error("Trying to create a TCP cross protocol query without a valid TCP state"); - } - - dnsQuestion.ids.origID = dnsQuestion.getHeader()->id; - return std::make_unique(std::move(dnsQuestion.getMutableData()), std::move(dnsQuestion.ids), nullptr, std::move(state)); -} - -void IncomingTCPConnectionState::handleCrossProtocolResponse(const struct timeval& now, TCPResponse&& response) -{ - std::shared_ptr state = shared_from_this(); - try { - auto ptr = std::make_unique(std::move(response), state, now); - if (!state->d_threadData.crossProtocolResponseSender.send(std::move(ptr))) { - ++dnsdist::metrics::g_stats.tcpCrossProtocolResponsePipeFull; - vinfolog("Unable to pass a cross-protocol response to the TCP worker thread because the pipe is full"); - } - } - catch (const std::exception& e) { - vinfolog("Unable to pass a cross-protocol response to the TCP worker thread because we couldn't write to the pipe: %s", stringerror()); - } -} - -IncomingTCPConnectionState::QueryProcessingResult IncomingTCPConnectionState::handleQuery(PacketBuffer&& queryIn, const struct timeval& now, std::optional streamID) -{ - auto query = std::move(queryIn); - if (query.size() < sizeof(dnsheader)) { - ++dnsdist::metrics::g_stats.nonCompliantQueries; - ++d_ci.cs->nonCompliantQueries; - return QueryProcessingResult::TooSmall; - } - - ++d_queriesCount; - ++d_ci.cs->queries; - ++dnsdist::metrics::g_stats.queries; - - if (d_handler.isTLS()) { - auto tlsVersion = d_handler.getTLSVersion(); - switch (tlsVersion) { - case LibsslTLSVersion::TLS10: - ++d_ci.cs->tls10queries; - break; - case LibsslTLSVersion::TLS11: - ++d_ci.cs->tls11queries; - break; - case LibsslTLSVersion::TLS12: - ++d_ci.cs->tls12queries; - break; - case LibsslTLSVersion::TLS13: - ++d_ci.cs->tls13queries; - break; - default: - ++d_ci.cs->tlsUnknownqueries; - } - } - - auto state = shared_from_this(); - InternalQueryState ids; - ids.origDest = d_proxiedDestination; - ids.origRemote = d_proxiedRemote; - ids.cs = d_ci.cs; - ids.queryRealTime.start(); - if (streamID) { - ids.d_streamID = *streamID; - } - - auto dnsCryptResponse = checkDNSCryptQuery(*d_ci.cs, query, ids.dnsCryptQuery, ids.queryRealTime.d_start.tv_sec, true); - if (dnsCryptResponse) { - TCPResponse response; - d_state = State::idle; - ++d_currentQueriesCount; - queueResponse(state, now, std::move(response), false); - return QueryProcessingResult::SelfAnswered; - } - - { - /* this pointer will be invalidated the second the buffer is resized, don't hold onto it! */ - const dnsheader_aligned dnsHeader(query.data()); - if (!checkQueryHeaders(dnsHeader.get(), *d_ci.cs)) { - return QueryProcessingResult::InvalidHeaders; - } - - if (dnsHeader->qdcount == 0) { - TCPResponse response; - auto queryID = dnsHeader->id; - dnsdist::PacketMangling::editDNSHeaderFromPacket(query, [](dnsheader& header) { - header.rcode = RCode::NotImp; - header.qr = true; - return true; - }); - response.d_idstate = std::move(ids); - response.d_idstate.origID = queryID; - response.d_idstate.selfGenerated = true; - response.d_buffer = std::move(query); - d_state = State::idle; - ++d_currentQueriesCount; - queueResponse(state, now, std::move(response), false); - return QueryProcessingResult::SelfAnswered; - } - } - - // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast - ids.qname = DNSName(reinterpret_cast(query.data()), static_cast(query.size()), sizeof(dnsheader), false, &ids.qtype, &ids.qclass); - ids.protocol = getProtocol(); - if (ids.dnsCryptQuery) { - ids.protocol = dnsdist::Protocol::DNSCryptTCP; - } - - DNSQuestion dnsQuestion(ids, query); - dnsdist::PacketMangling::editDNSHeaderFromPacket(dnsQuestion.getMutableData(), [&ids](dnsheader& header) { - const uint16_t* flags = getFlagsFromDNSHeader(&header); - ids.origFlags = *flags; - return true; - }); - dnsQuestion.d_incomingTCPState = state; - dnsQuestion.sni = d_handler.getServerNameIndication(); - - if (d_proxyProtocolValues) { - /* we need to copy them, because the next queries received on that connection will - need to get the _unaltered_ values */ - dnsQuestion.proxyProtocolValues = make_unique>(*d_proxyProtocolValues); - } - - if (dnsQuestion.ids.qtype == QType::AXFR || dnsQuestion.ids.qtype == QType::IXFR) { - dnsQuestion.ids.skipCache = true; - } - - if (forwardViaUDPFirst()) { - // if there was no EDNS, we add it with a large buffer size - // so we can use UDP to talk to the backend. - const dnsheader_aligned dnsHeader(query.data()); - if (dnsHeader->arcount == 0U) { - if (addEDNS(query, 4096, false, 4096, 0)) { - dnsQuestion.ids.ednsAdded = true; - } - } - } - - if (streamID) { - auto unit = getDOHUnit(*streamID); - if (unit) { - dnsQuestion.ids.du = std::move(unit); - } - } - - std::shared_ptr backend; - auto result = processQuery(dnsQuestion, d_threadData.holders, backend); - - if (result == ProcessQueryResult::Asynchronous) { - /* we are done for now */ - ++d_currentQueriesCount; - return QueryProcessingResult::Asynchronous; - } - - if (streamID) { - restoreDOHUnit(std::move(dnsQuestion.ids.du)); - } - - if (result == ProcessQueryResult::Drop) { - return QueryProcessingResult::Dropped; - } - - // the buffer might have been invalidated by now - uint16_t queryID{0}; - { - const auto dnsHeader = dnsQuestion.getHeader(); - queryID = dnsHeader->id; - } - - if (result == ProcessQueryResult::SendAnswer) { - TCPResponse response; - { - const auto dnsHeader = dnsQuestion.getHeader(); - memcpy(&response.d_cleartextDH, dnsHeader.get(), sizeof(response.d_cleartextDH)); - } - response.d_idstate = std::move(ids); - response.d_idstate.origID = queryID; - response.d_idstate.selfGenerated = true; - response.d_idstate.cs = d_ci.cs; - response.d_buffer = std::move(query); - - d_state = State::idle; - ++d_currentQueriesCount; - queueResponse(state, now, std::move(response), false); - return QueryProcessingResult::SelfAnswered; - } - - if (result != ProcessQueryResult::PassToBackend || backend == nullptr) { - return QueryProcessingResult::NoBackend; - } - - dnsQuestion.ids.origID = queryID; - - ++d_currentQueriesCount; - - std::string proxyProtocolPayload; - if (backend->isDoH()) { - vinfolog("Got query for %s|%s from %s (%s, %d bytes), relayed to %s", ids.qname.toLogString(), QType(ids.qtype).toString(), d_proxiedRemote.toStringWithPort(), getProtocol().toString(), query.size(), backend->getNameWithAddr()); - - /* we need to do this _before_ creating the cross protocol query because - after that the buffer will have been moved */ - if (backend->d_config.useProxyProtocol) { - proxyProtocolPayload = getProxyProtocolPayload(dnsQuestion); - } - - auto cpq = std::make_unique(std::move(query), std::move(ids), backend, state); - cpq->query.d_proxyProtocolPayload = std::move(proxyProtocolPayload); - - backend->passCrossProtocolQuery(std::move(cpq)); - return QueryProcessingResult::Forwarded; - } - if (!backend->isTCPOnly() && forwardViaUDPFirst()) { - if (streamID) { - auto unit = getDOHUnit(*streamID); - if (unit) { - dnsQuestion.ids.du = std::move(unit); - } - } - if (assignOutgoingUDPQueryToBackend(backend, queryID, dnsQuestion, query)) { - return QueryProcessingResult::Forwarded; - } - restoreDOHUnit(std::move(dnsQuestion.ids.du)); - // fallback to the normal flow - } - - prependSizeToTCPQuery(query, 0); - - auto downstreamConnection = getDownstreamConnection(backend, dnsQuestion.proxyProtocolValues, now); - - if (backend->d_config.useProxyProtocol) { - /* if we ever sent a TLV over a connection, we can never go back */ - if (!d_proxyProtocolPayloadHasTLV) { - d_proxyProtocolPayloadHasTLV = dnsQuestion.proxyProtocolValues && !dnsQuestion.proxyProtocolValues->empty(); - } - - proxyProtocolPayload = getProxyProtocolPayload(dnsQuestion); - } - - if (dnsQuestion.proxyProtocolValues) { - downstreamConnection->setProxyProtocolValuesSent(std::move(dnsQuestion.proxyProtocolValues)); - } - - TCPQuery tcpquery(std::move(query), std::move(ids)); - tcpquery.d_proxyProtocolPayload = std::move(proxyProtocolPayload); - - vinfolog("Got query for %s|%s from %s (%s, %d bytes), relayed to %s", tcpquery.d_idstate.qname.toLogString(), QType(tcpquery.d_idstate.qtype).toString(), d_proxiedRemote.toStringWithPort(), getProtocol().toString(), tcpquery.d_buffer.size(), backend->getNameWithAddr()); - std::shared_ptr incoming = state; - downstreamConnection->queueQuery(incoming, std::move(tcpquery)); - return QueryProcessingResult::Forwarded; -} - -void IncomingTCPConnectionState::handleIOCallback(int desc, FDMultiplexer::funcparam_t& param) -{ - auto conn = boost::any_cast>(param); - if (desc != conn->d_handler.getDescriptor()) { - // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-array-to-pointer-decay): __PRETTY_FUNCTION__ is fine - throw std::runtime_error("Unexpected socket descriptor " + std::to_string(desc) + " received in " + std::string(__PRETTY_FUNCTION__) + ", expected " + std::to_string(conn->d_handler.getDescriptor())); - } - - conn->handleIO(); -} - -void IncomingTCPConnectionState::handleHandshakeDone(const struct timeval& now) -{ - if (d_handler.isTLS()) { - if (!d_handler.hasTLSSessionBeenResumed()) { - ++d_ci.cs->tlsNewSessions; - } - else { - ++d_ci.cs->tlsResumptions; - } - if (d_handler.getResumedFromInactiveTicketKey()) { - ++d_ci.cs->tlsInactiveTicketKey; - } - if (d_handler.getUnknownTicketKey()) { - ++d_ci.cs->tlsUnknownTicketKey; - } - } - - d_handshakeDoneTime = now; -} - -IncomingTCPConnectionState::ProxyProtocolResult IncomingTCPConnectionState::handleProxyProtocolPayload() -{ - do { - DEBUGLOG("reading proxy protocol header"); - auto iostate = d_handler.tryRead(d_buffer, d_currentPos, d_proxyProtocolNeed, false, isProxyPayloadOutsideTLS()); - if (iostate == IOState::Done) { - d_buffer.resize(d_currentPos); - ssize_t remaining = isProxyHeaderComplete(d_buffer); - if (remaining == 0) { - vinfolog("Unable to consume proxy protocol header in packet from TCP client %s", d_ci.remote.toStringWithPort()); - ++dnsdist::metrics::g_stats.proxyProtocolInvalid; - return ProxyProtocolResult::Error; - } - if (remaining < 0) { - d_proxyProtocolNeed += -remaining; - d_buffer.resize(d_currentPos + d_proxyProtocolNeed); - /* we need to keep reading, since we might have buffered data */ - } - else { - /* proxy header received */ - std::vector proxyProtocolValues; - if (!handleProxyProtocol(d_ci.remote, true, *d_threadData.holders.acl, d_buffer, d_proxiedRemote, d_proxiedDestination, proxyProtocolValues)) { - vinfolog("Error handling the Proxy Protocol received from TCP client %s", d_ci.remote.toStringWithPort()); - return ProxyProtocolResult::Error; - } - - if (!proxyProtocolValues.empty()) { - d_proxyProtocolValues = make_unique>(std::move(proxyProtocolValues)); - } - - return ProxyProtocolResult::Done; - } - } - else { - d_lastIOBlocked = true; - } - } while (active() && !d_lastIOBlocked); - - return ProxyProtocolResult::Reading; -} - -IOState IncomingTCPConnectionState::handleHandshake(const struct timeval& now) -{ - DEBUGLOG("doing handshake"); - auto iostate = d_handler.tryHandshake(); - if (iostate == IOState::Done) { - DEBUGLOG("handshake done"); - handleHandshakeDone(now); - - if (d_ci.cs != nullptr && d_ci.cs->d_enableProxyProtocol && !isProxyPayloadOutsideTLS() && expectProxyProtocolFrom(d_ci.remote)) { - d_state = State::readingProxyProtocolHeader; - d_buffer.resize(s_proxyProtocolMinimumHeaderSize); - d_proxyProtocolNeed = s_proxyProtocolMinimumHeaderSize; - } - else { - d_state = State::readingQuerySize; - } - } - else { - d_lastIOBlocked = true; - } - - return iostate; -} - -IOState IncomingTCPConnectionState::handleIncomingQueryReceived(const struct timeval& now) -{ - DEBUGLOG("query received"); - d_buffer.resize(d_querySize); - - d_state = State::idle; - auto processingResult = handleQuery(std::move(d_buffer), now, std::nullopt); - switch (processingResult) { - case QueryProcessingResult::TooSmall: - /* fall-through */ - case QueryProcessingResult::InvalidHeaders: - /* fall-through */ - case QueryProcessingResult::Dropped: - /* fall-through */ - case QueryProcessingResult::NoBackend: - terminateClientConnection(); - ; - default: - break; - } - - /* the state might have been updated in the meantime, we don't want to override it - in that case */ - if (active() && d_state != State::idle) { - if (d_ioState->isWaitingForRead()) { - return IOState::NeedRead; - } - if (d_ioState->isWaitingForWrite()) { - return IOState::NeedWrite; - } - return IOState::Done; - } - return IOState::Done; -}; - -void IncomingTCPConnectionState::handleExceptionDuringIO(const std::exception& exp) -{ - if (d_state == State::idle || d_state == State::waitingForQuery) { - /* no need to increase any counters in that case, the client is simply done with us */ - } - else if (d_state == State::doingHandshake || d_state == State::readingProxyProtocolHeader || d_state == State::waitingForQuery || d_state == State::readingQuerySize || d_state == State::readingQuery) { - ++d_ci.cs->tcpDiedReadingQuery; - } - else if (d_state == State::sendingResponse) { - /* unlikely to happen here, the exception should be handled in sendResponse() */ - ++d_ci.cs->tcpDiedSendingResponse; - } - - if (d_ioState->isWaitingForWrite() || d_queriesCount == 0) { - DEBUGLOG("Got an exception while handling TCP query: " << exp.what()); - vinfolog("Got an exception while handling (%s) TCP query from %s: %s", (d_ioState->isWaitingForRead() ? "reading" : "writing"), d_ci.remote.toStringWithPort(), exp.what()); - } - else { - vinfolog("Closing TCP client connection with %s: %s", d_ci.remote.toStringWithPort(), exp.what()); - DEBUGLOG("Closing TCP client connection: " << exp.what()); - } - /* remove this FD from the IO multiplexer */ - terminateClientConnection(); -} - -bool IncomingTCPConnectionState::readIncomingQuery(const timeval& now, IOState& iostate) -{ - if (!d_lastIOBlocked && (d_state == State::waitingForQuery || d_state == State::readingQuerySize)) { - DEBUGLOG("reading query size"); - d_buffer.resize(sizeof(uint16_t)); - iostate = d_handler.tryRead(d_buffer, d_currentPos, sizeof(uint16_t)); - if (d_currentPos > 0) { - /* if we got at least one byte, we can't go around sending responses */ - d_state = State::readingQuerySize; - } - - if (iostate == IOState::Done) { - DEBUGLOG("query size received"); - d_state = State::readingQuery; - d_querySizeReadTime = now; - if (d_queriesCount == 0) { - d_firstQuerySizeReadTime = now; - } - d_querySize = d_buffer.at(0) * 256 + d_buffer.at(1); - if (d_querySize < sizeof(dnsheader)) { - /* go away */ - terminateClientConnection(); - return true; - } - - d_buffer.resize(d_querySize); - d_currentPos = 0; - } - else { - d_lastIOBlocked = true; - } - } - - if (!d_lastIOBlocked && d_state == State::readingQuery) { - DEBUGLOG("reading query"); - iostate = d_handler.tryRead(d_buffer, d_currentPos, d_querySize); - if (iostate == IOState::Done) { - iostate = handleIncomingQueryReceived(now); - } - else { - d_lastIOBlocked = true; - } - } - - return false; -} - -void IncomingTCPConnectionState::handleIO() -{ - // why do we loop? Because the TLS layer does buffering, and thus can have data ready to read - // even though the underlying socket is not ready, so we need to actually ask for the data first - IOState iostate = IOState::Done; - timeval now{}; - gettimeofday(&now, nullptr); - - do { - iostate = IOState::Done; - IOStateGuard ioGuard(d_ioState); - - if (maxConnectionDurationReached(g_maxTCPConnectionDuration, now)) { - vinfolog("Terminating TCP connection from %s because it reached the maximum TCP connection duration", d_ci.remote.toStringWithPort()); - // will be handled by the ioGuard - // handleNewIOState(state, IOState::Done, fd, handleIOCallback); - return; - } - - d_lastIOBlocked = false; - - try { - if (d_state == State::starting) { - if (d_ci.cs != nullptr && d_ci.cs->d_enableProxyProtocol && isProxyPayloadOutsideTLS() && expectProxyProtocolFrom(d_ci.remote)) { - d_state = State::readingProxyProtocolHeader; - d_buffer.resize(s_proxyProtocolMinimumHeaderSize); - d_proxyProtocolNeed = s_proxyProtocolMinimumHeaderSize; - } - else { - d_state = State::doingHandshake; - } - } - - if (d_state == State::doingHandshake) { - iostate = handleHandshake(now); - } - - if (!d_lastIOBlocked && d_state == State::readingProxyProtocolHeader) { - auto status = handleProxyProtocolPayload(); - if (status == ProxyProtocolResult::Done) { - if (isProxyPayloadOutsideTLS()) { - d_state = State::doingHandshake; - iostate = handleHandshake(now); - } - else { - d_state = State::readingQuerySize; - d_buffer.resize(sizeof(uint16_t)); - d_currentPos = 0; - d_proxyProtocolNeed = 0; - } - } - else if (status == ProxyProtocolResult::Error) { - iostate = IOState::Done; - } - else { - iostate = IOState::NeedRead; - } - } - - if (!d_lastIOBlocked && (d_state == State::waitingForQuery || d_state == State::readingQuerySize || d_state == State::readingQuery)) { - if (readIncomingQuery(now, iostate)) { - return; - } - } - - if (!d_lastIOBlocked && d_state == State::sendingResponse) { - DEBUGLOG("sending response"); - iostate = d_handler.tryWrite(d_currentResponse.d_buffer, d_currentPos, d_currentResponse.d_buffer.size()); - if (iostate == IOState::Done) { - DEBUGLOG("response sent from " << __PRETTY_FUNCTION__); - handleResponseSent(d_currentResponse); - d_state = State::idle; - } - else { - d_lastIOBlocked = true; - } - } - - if (active() && !d_lastIOBlocked && iostate == IOState::Done && (d_state == State::idle || d_state == State::waitingForQuery)) { - // try sending queued responses - DEBUGLOG("send responses, if any"); - auto state = shared_from_this(); - iostate = sendQueuedResponses(state, now); - - if (!d_lastIOBlocked && active() && iostate == IOState::Done) { - // if the query has been passed to a backend, or dropped, and the responses have been sent, - // we can start reading again - if (canAcceptNewQueries(now)) { - resetForNewQuery(); - iostate = IOState::NeedRead; - } - else { - d_state = State::idle; - iostate = IOState::Done; - } - } - } - - if (d_state != State::idle && d_state != State::doingHandshake && d_state != State::readingProxyProtocolHeader && d_state != State::waitingForQuery && d_state != State::readingQuerySize && d_state != State::readingQuery && d_state != State::sendingResponse) { - vinfolog("Unexpected state %d in handleIOCallback", static_cast(d_state)); - } - } - catch (const std::exception& exp) { - /* most likely an EOF because the other end closed the connection, - but it might also be a real IO error or something else. - Let's just drop the connection - */ - handleExceptionDuringIO(exp); - } - - if (!active()) { - DEBUGLOG("state is no longer active"); - return; - } - - auto state = shared_from_this(); - if (iostate == IOState::Done) { - d_ioState->update(iostate, handleIOCallback, state); - } - else { - updateIO(state, iostate, now); - } - ioGuard.release(); - } while ((iostate == IOState::NeedRead || iostate == IOState::NeedWrite) && !d_lastIOBlocked); -} - -void IncomingTCPConnectionState::notifyIOError(const struct timeval& now, TCPResponse&& response) -{ - if (std::this_thread::get_id() != d_creatorThreadID) { - /* empty buffer will signal an IO error */ - response.d_buffer.clear(); - handleCrossProtocolResponse(now, std::move(response)); - return; - } - - std::shared_ptr state = shared_from_this(); - --state->d_currentQueriesCount; - state->d_hadErrors = true; - - if (state->d_state == State::sendingResponse) { - /* if we have responses to send, let's do that first */ - } - else if (!state->d_queuedResponses.empty()) { - /* stop reading and send what we have */ - try { - auto iostate = sendQueuedResponses(state, now); - - if (state->active() && iostate != IOState::Done) { - // we need to update the state right away, nobody will do that for us - updateIO(state, iostate, now); - } - } - catch (const std::exception& e) { - vinfolog("Exception in notifyIOError: %s", e.what()); - } - } - else { - // the backend code already tried to reconnect if it was possible - state->terminateClientConnection(); - } -} - -void IncomingTCPConnectionState::handleXFRResponse(const struct timeval& now, TCPResponse&& response) -{ - if (std::this_thread::get_id() != d_creatorThreadID) { - handleCrossProtocolResponse(now, std::move(response)); - return; - } - - std::shared_ptr state = shared_from_this(); - queueResponse(state, now, std::move(response), true); -} - -void IncomingTCPConnectionState::handleTimeout(std::shared_ptr& state, bool write) -{ - vinfolog("Timeout while %s TCP client %s", (write ? "writing to" : "reading from"), state->d_ci.remote.toStringWithPort()); - DEBUGLOG("client timeout"); - DEBUGLOG("Processed " << state->d_queriesCount << " queries, current count is " << state->d_currentQueriesCount << ", " << state->d_ownedConnectionsToBackend.size() << " owned connections, " << state->d_queuedResponses.size() << " response queued"); - - if (write || state->d_currentQueriesCount == 0) { - ++state->d_ci.cs->tcpClientTimeouts; - state->d_ioState.reset(); - } - else { - DEBUGLOG("Going idle"); - /* we still have some queries in flight, let's just stop reading for now */ - state->d_state = State::idle; - state->d_ioState->update(IOState::Done, handleIOCallback, state); - } -} - -static void handleIncomingTCPQuery(int pipefd, FDMultiplexer::funcparam_t& param) -{ - auto* threadData = boost::any_cast(param); - - std::unique_ptr citmp{nullptr}; - try { - auto tmp = threadData->queryReceiver.receive(); - if (!tmp) { - return; - } - citmp = std::move(*tmp); - } - catch (const std::exception& e) { - throw std::runtime_error("Error while reading from the TCP query channel: " + std::string(e.what())); - } - - g_tcpclientthreads->decrementQueuedCount(); - - timeval now{}; - gettimeofday(&now, nullptr); - - if (citmp->cs->dohFrontend) { -#if defined(HAVE_DNS_OVER_HTTPS) && defined(HAVE_NGHTTP2) - auto state = std::make_shared(std::move(*citmp), *threadData, now); - state->handleIO(); -#endif /* HAVE_DNS_OVER_HTTPS && HAVE_NGHTTP2 */ - } - else { - auto state = std::make_shared(std::move(*citmp), *threadData, now); - state->handleIO(); - } -} - -static void handleCrossProtocolQuery(int pipefd, FDMultiplexer::funcparam_t& param) -{ - auto* threadData = boost::any_cast(param); - - std::unique_ptr cpq{nullptr}; - try { - auto tmp = threadData->crossProtocolQueryReceiver.receive(); - if (!tmp) { - return; - } - cpq = std::move(*tmp); - } - catch (const std::exception& e) { - throw std::runtime_error("Error while reading from the TCP cross-protocol channel: " + std::string(e.what())); - } - - timeval now{}; - gettimeofday(&now, nullptr); - - std::shared_ptr tqs = cpq->getTCPQuerySender(); - auto query = std::move(cpq->query); - auto downstreamServer = std::move(cpq->downstream); - - try { - auto downstream = t_downstreamTCPConnectionsManager.getConnectionToDownstream(threadData->mplexer, downstreamServer, now, std::string()); - - prependSizeToTCPQuery(query.d_buffer, query.d_idstate.d_proxyProtocolPayloadSize); - - vinfolog("Got query for %s|%s from %s (%s, %d bytes), relayed to %s", query.d_idstate.qname.toLogString(), QType(query.d_idstate.qtype).toString(), query.d_idstate.origRemote.toStringWithPort(), query.d_idstate.protocol.toString(), query.d_buffer.size(), downstreamServer->getNameWithAddr()); - - downstream->queueQuery(tqs, std::move(query)); - } - catch (...) { - tqs->notifyIOError(now, std::move(query)); - } -} - -static void handleCrossProtocolResponse(int pipefd, FDMultiplexer::funcparam_t& param) -{ - auto* threadData = boost::any_cast(param); - - std::unique_ptr cpr{nullptr}; - try { - auto tmp = threadData->crossProtocolResponseReceiver.receive(); - if (!tmp) { - return; - } - cpr = std::move(*tmp); - } - catch (const std::exception& e) { - throw std::runtime_error("Error while reading from the TCP cross-protocol response: " + std::string(e.what())); - } - - auto& response = *cpr; - - try { - if (response.d_response.d_buffer.empty()) { - response.d_state->notifyIOError(response.d_now, std::move(response.d_response)); - } - else if (response.d_response.d_idstate.qtype == QType::AXFR || response.d_response.d_idstate.qtype == QType::IXFR) { - response.d_state->handleXFRResponse(response.d_now, std::move(response.d_response)); - } - else { - response.d_state->handleResponse(response.d_now, std::move(response.d_response)); - } - } - catch (...) { - /* no point bubbling up from there */ - } -} - -struct TCPAcceptorParam -{ - // NOLINTNEXTLINE(cppcoreguidelines-avoid-const-or-ref-data-members) - ClientState& clientState; - ComboAddress local; - // NOLINTNEXTLINE(cppcoreguidelines-avoid-const-or-ref-data-members) - LocalStateHolder& acl; - int socket{-1}; -}; - -static void acceptNewConnection(const TCPAcceptorParam& param, TCPClientThreadData* threadData); - -static void scanForTimeouts(const TCPClientThreadData& data, const timeval& now) -{ - auto expiredReadConns = data.mplexer->getTimeouts(now, false); - for (const auto& cbData : expiredReadConns) { - if (cbData.second.type() == typeid(std::shared_ptr)) { - auto state = boost::any_cast>(cbData.second); - if (cbData.first == state->d_handler.getDescriptor()) { - vinfolog("Timeout (read) from remote TCP client %s", state->d_ci.remote.toStringWithPort()); - state->handleTimeout(state, false); - } - } -#if defined(HAVE_DNS_OVER_HTTPS) && defined(HAVE_NGHTTP2) - else if (cbData.second.type() == typeid(std::shared_ptr)) { - auto state = boost::any_cast>(cbData.second); - if (cbData.first == state->d_handler.getDescriptor()) { - vinfolog("Timeout (read) from remote H2 client %s", state->d_ci.remote.toStringWithPort()); - std::shared_ptr parentState = state; - state->handleTimeout(parentState, false); - } - } -#endif /* HAVE_DNS_OVER_HTTPS && HAVE_NGHTTP2 */ - else if (cbData.second.type() == typeid(std::shared_ptr)) { - auto conn = boost::any_cast>(cbData.second); - vinfolog("Timeout (read) from remote backend %s", conn->getBackendName()); - conn->handleTimeout(now, false); - } - } - - auto expiredWriteConns = data.mplexer->getTimeouts(now, true); - for (const auto& cbData : expiredWriteConns) { - if (cbData.second.type() == typeid(std::shared_ptr)) { - auto state = boost::any_cast>(cbData.second); - if (cbData.first == state->d_handler.getDescriptor()) { - vinfolog("Timeout (write) from remote TCP client %s", state->d_ci.remote.toStringWithPort()); - state->handleTimeout(state, true); - } - } -#if defined(HAVE_DNS_OVER_HTTPS) && defined(HAVE_NGHTTP2) - else if (cbData.second.type() == typeid(std::shared_ptr)) { - auto state = boost::any_cast>(cbData.second); - if (cbData.first == state->d_handler.getDescriptor()) { - vinfolog("Timeout (write) from remote H2 client %s", state->d_ci.remote.toStringWithPort()); - std::shared_ptr parentState = state; - state->handleTimeout(parentState, true); - } - } -#endif /* HAVE_DNS_OVER_HTTPS && HAVE_NGHTTP2 */ - else if (cbData.second.type() == typeid(std::shared_ptr)) { - auto conn = boost::any_cast>(cbData.second); - vinfolog("Timeout (write) from remote backend %s", conn->getBackendName()); - conn->handleTimeout(now, true); - } - } -} - -static void dumpTCPStates(const TCPClientThreadData& data) -{ - /* just to keep things clean in the output, debug only */ - static std::mutex s_lock; - std::lock_guard lck(s_lock); - if (g_tcpStatesDumpRequested > 0) { - /* no race here, we took the lock so it can only be increased in the meantime */ - --g_tcpStatesDumpRequested; - infolog("Dumping the TCP states, as requested:"); - data.mplexer->runForAllWatchedFDs([](bool isRead, int desc, const FDMultiplexer::funcparam_t& param, struct timeval ttd) { - timeval lnow{}; - gettimeofday(&lnow, nullptr); - if (ttd.tv_sec > 0) { - infolog("- Descriptor %d is in %s state, TTD in %d", desc, (isRead ? "read" : "write"), (ttd.tv_sec - lnow.tv_sec)); - } - else { - infolog("- Descriptor %d is in %s state, no TTD set", desc, (isRead ? "read" : "write")); - } - - if (param.type() == typeid(std::shared_ptr)) { - auto state = boost::any_cast>(param); - infolog(" - %s", state->toString()); - } -#if defined(HAVE_DNS_OVER_HTTPS) && defined(HAVE_NGHTTP2) - else if (param.type() == typeid(std::shared_ptr)) { - auto state = boost::any_cast>(param); - infolog(" - %s", state->toString()); - } -#endif /* HAVE_DNS_OVER_HTTPS && HAVE_NGHTTP2 */ - else if (param.type() == typeid(std::shared_ptr)) { - auto conn = boost::any_cast>(param); - infolog(" - %s", conn->toString()); - } - else if (param.type() == typeid(TCPClientThreadData*)) { - infolog(" - Worker thread pipe"); - } - }); - infolog("The TCP/DoT client cache has %d active and %d idle outgoing connections cached", t_downstreamTCPConnectionsManager.getActiveCount(), t_downstreamTCPConnectionsManager.getIdleCount()); - } -} - -// NOLINTNEXTLINE(performance-unnecessary-value-param): you are wrong, clang-tidy, go home -static void tcpClientThread(pdns::channel::Receiver&& queryReceiver, pdns::channel::Receiver&& crossProtocolQueryReceiver, pdns::channel::Receiver&& crossProtocolResponseReceiver, pdns::channel::Sender&& crossProtocolResponseSender, std::vector tcpAcceptStates) -{ - /* we get launched with a pipe on which we receive file descriptors from clients that we own - from that point on */ - - setThreadName("dnsdist/tcpClie"); - - try { - TCPClientThreadData data; - data.crossProtocolResponseSender = std::move(crossProtocolResponseSender); - data.queryReceiver = std::move(queryReceiver); - data.crossProtocolQueryReceiver = std::move(crossProtocolQueryReceiver); - data.crossProtocolResponseReceiver = std::move(crossProtocolResponseReceiver); - - data.mplexer->addReadFD(data.queryReceiver.getDescriptor(), handleIncomingTCPQuery, &data); - data.mplexer->addReadFD(data.crossProtocolQueryReceiver.getDescriptor(), handleCrossProtocolQuery, &data); - data.mplexer->addReadFD(data.crossProtocolResponseReceiver.getDescriptor(), handleCrossProtocolResponse, &data); - - /* only used in single acceptor mode for now */ - auto acl = g_ACL.getLocal(); - std::vector acceptParams; - acceptParams.reserve(tcpAcceptStates.size()); - - for (auto& state : tcpAcceptStates) { - acceptParams.emplace_back(TCPAcceptorParam{*state, state->local, acl, state->tcpFD}); - for (const auto& [addr, socket] : state->d_additionalAddresses) { - acceptParams.emplace_back(TCPAcceptorParam{*state, addr, acl, socket}); - } - } - - auto acceptCallback = [&data](int socket, FDMultiplexer::funcparam_t& funcparam) { - const auto* acceptorParam = boost::any_cast(funcparam); - acceptNewConnection(*acceptorParam, &data); - }; - - for (const auto& param : acceptParams) { - setNonBlocking(param.socket); - data.mplexer->addReadFD(param.socket, acceptCallback, ¶m); - } - - timeval now{}; - gettimeofday(&now, nullptr); - time_t lastTimeoutScan = now.tv_sec; - - for (;;) { - data.mplexer->run(&now); - - try { - t_downstreamTCPConnectionsManager.cleanupClosedConnections(now); - - if (now.tv_sec > lastTimeoutScan) { - lastTimeoutScan = now.tv_sec; - scanForTimeouts(data, now); - - if (g_tcpStatesDumpRequested > 0) { - dumpTCPStates(data); - } - } - } - catch (const std::exception& e) { - warnlog("Error in TCP worker thread: %s", e.what()); - } - } - } - catch (const std::exception& e) { - errlog("Fatal error in TCP worker thread: %s", e.what()); - } -} - -static void acceptNewConnection(const TCPAcceptorParam& param, TCPClientThreadData* threadData) -{ - auto& clientState = param.clientState; - auto& acl = param.acl; - const bool checkACL = clientState.dohFrontend == nullptr || (!clientState.dohFrontend->d_trustForwardedForHeader && clientState.dohFrontend->d_earlyACLDrop); - const int socket = param.socket; - bool tcpClientCountIncremented = false; - ComboAddress remote; - remote.sin4.sin_family = param.local.sin4.sin_family; - - tcpClientCountIncremented = false; - try { - socklen_t remlen = remote.getSocklen(); - ConnectionInfo connInfo(&clientState); -#ifdef HAVE_ACCEPT4 - // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) - connInfo.fd = accept4(socket, reinterpret_cast(&remote), &remlen, SOCK_NONBLOCK); -#else - // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) - connInfo.fd = accept(socket, reinterpret_cast(&remote), &remlen); -#endif - // will be decremented when the ConnectionInfo object is destroyed, no matter the reason - auto concurrentConnections = ++clientState.tcpCurrentConnections; - - if (connInfo.fd < 0) { - throw std::runtime_error((boost::format("accepting new connection on socket: %s") % stringerror()).str()); - } - - if (checkACL && !acl->match(remote)) { - ++dnsdist::metrics::g_stats.aclDrops; - vinfolog("Dropped TCP connection from %s because of ACL", remote.toStringWithPort()); - return; - } - - if (clientState.d_tcpConcurrentConnectionsLimit > 0 && concurrentConnections > clientState.d_tcpConcurrentConnectionsLimit) { - vinfolog("Dropped TCP connection from %s because of concurrent connections limit", remote.toStringWithPort()); - return; - } - - if (concurrentConnections > clientState.tcpMaxConcurrentConnections.load()) { - clientState.tcpMaxConcurrentConnections.store(concurrentConnections); - } - -#ifndef HAVE_ACCEPT4 - if (!setNonBlocking(connInfo.fd)) { - return; - } -#endif - - setTCPNoDelay(connInfo.fd); // disable NAGLE - - if (g_maxTCPQueuedConnections > 0 && g_tcpclientthreads->getQueuedCount() >= g_maxTCPQueuedConnections) { - vinfolog("Dropping TCP connection from %s because we have too many queued already", remote.toStringWithPort()); - return; - } - - if (!dnsdist::IncomingConcurrentTCPConnectionsManager::accountNewTCPConnection(remote)) { - vinfolog("Dropping TCP connection from %s because we have too many from this client already", remote.toStringWithPort()); - return; - } - tcpClientCountIncremented = true; - - vinfolog("Got TCP connection from %s", remote.toStringWithPort()); - - connInfo.remote = remote; - - if (threadData == nullptr) { - if (!g_tcpclientthreads->passConnectionToThread(std::make_unique(std::move(connInfo)))) { - if (tcpClientCountIncremented) { - dnsdist::IncomingConcurrentTCPConnectionsManager::accountClosedTCPConnection(remote); - } - } - } - else { - timeval now{}; - gettimeofday(&now, nullptr); - - if (connInfo.cs->dohFrontend) { -#if defined(HAVE_DNS_OVER_HTTPS) && defined(HAVE_NGHTTP2) - auto state = std::make_shared(std::move(connInfo), *threadData, now); - state->handleIO(); -#endif /* HAVE_DNS_OVER_HTTPS && HAVE_NGHTTP2 */ - } - else { - auto state = std::make_shared(std::move(connInfo), *threadData, now); - state->handleIO(); - } - } - } - catch (const std::exception& e) { - errlog("While reading a TCP question: %s", e.what()); - if (tcpClientCountIncremented) { - dnsdist::IncomingConcurrentTCPConnectionsManager::accountClosedTCPConnection(remote); - } - } - catch (...) { - } -} - -/* spawn as many of these as required, they call Accept on a socket on which they will accept queries, and - they will hand off to worker threads & spawn more of them if required -*/ -#ifndef USE_SINGLE_ACCEPTOR_THREAD -void tcpAcceptorThread(const std::vector& states) -{ - setThreadName("dnsdist/tcpAcce"); - - auto acl = g_ACL.getLocal(); - std::vector params; - params.reserve(states.size()); - - for (const auto& state : states) { - params.emplace_back(TCPAcceptorParam{*state, state->local, acl, state->tcpFD}); - for (const auto& [addr, socket] : state->d_additionalAddresses) { - params.emplace_back(TCPAcceptorParam{*state, addr, acl, socket}); - } - } - - if (params.size() == 1) { - while (true) { - acceptNewConnection(params.at(0), nullptr); - } - } - else { - auto acceptCallback = [](int socket, FDMultiplexer::funcparam_t& funcparam) { - const auto* acceptorParam = boost::any_cast(funcparam); - acceptNewConnection(*acceptorParam, nullptr); - }; - - auto mplexer = std::unique_ptr(FDMultiplexer::getMultiplexerSilent(params.size())); - for (const auto& param : params) { - mplexer->addReadFD(param.socket, acceptCallback, ¶m); - } - - timeval now{}; - while (true) { - mplexer->run(&now, -1); - } - } -} -#endif diff --git a/pdns/dnsdist-web.cc b/pdns/dnsdist-web.cc deleted file mode 100644 index 066b5c177f54..000000000000 --- a/pdns/dnsdist-web.cc +++ /dev/null @@ -1,1972 +0,0 @@ -/* - * This file is part of PowerDNS or dnsdist. - * Copyright -- PowerDNS.COM B.V. and its contributors - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of version 2 of the GNU General Public License as - * published by the Free Software Foundation. - * - * In addition, for the avoidance of any doubt, permission is granted to - * link this program with OpenSSL and to (re)distribute the binaries - * produced as the result of such linking. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include -#include -#include -#include -#include - -#include "ext/json11/json11.hpp" -#include - -#include "base64.hh" -#include "connection-management.hh" -#include "dnsdist.hh" -#include "dnsdist-dynblocks.hh" -#include "dnsdist-healthchecks.hh" -#include "dnsdist-metrics.hh" -#include "dnsdist-prometheus.hh" -#include "dnsdist-rings.hh" -#include "dnsdist-web.hh" -#include "dolog.hh" -#include "gettime.hh" -#include "threadname.hh" -#include "sstuff.hh" - -struct WebserverConfig -{ - WebserverConfig() - { - acl.toMasks("127.0.0.1, ::1"); - } - - NetmaskGroup acl; - std::unique_ptr password; - std::unique_ptr apiKey; - boost::optional > customHeaders; - bool apiRequiresAuthentication{true}; - bool dashboardRequiresAuthentication{true}; - bool statsRequireAuthentication{true}; -}; - -bool g_apiReadWrite{false}; -LockGuarded g_webserverConfig; -std::string g_apiConfigDirectory; - -static ConcurrentConnectionManager s_connManager(100); - -std::string getWebserverConfig() -{ - ostringstream out; - - { - auto config = g_webserverConfig.lock(); - out << "Current web server configuration:" << endl; - out << "ACL: " << config->acl.toString() << endl; - out << "Custom headers: "; - if (config->customHeaders) { - out << endl; - for (const auto& header : *config->customHeaders) { - out << " - " << header.first << ": " << header.second << endl; - } - } - else { - out << "None" << endl; - } - out << "API requires authentication: " << (config->apiRequiresAuthentication ? "yes" : "no") << endl; - out << "Dashboard requires authentication: " << (config->dashboardRequiresAuthentication ? "yes" : "no") << endl; - out << "Statistics require authentication: " << (config->statsRequireAuthentication ? "yes" : "no") << endl; - out << "Password: " << (config->password ? "set" : "unset") << endl; - out << "API key: " << (config->apiKey ? "set" : "unset") << endl; - } - out << "API writable: " << (g_apiReadWrite ? "yes" : "no") << endl; - out << "API configuration directory: " << g_apiConfigDirectory << endl; - out << "Maximum concurrent connections: " << s_connManager.getMaxConcurrentConnections() << endl; - - return out.str(); -} - -class WebClientConnection -{ -public: - WebClientConnection(const ComboAddress& client, int fd): d_client(client), d_socket(fd) - { - if (!s_connManager.registerConnection()) { - throw std::runtime_error("Too many concurrent web client connections"); - } - } - WebClientConnection(WebClientConnection&& rhs): d_client(rhs.d_client), d_socket(std::move(rhs.d_socket)) - { - } - - WebClientConnection(const WebClientConnection&) = delete; - WebClientConnection& operator=(const WebClientConnection&) = delete; - - ~WebClientConnection() - { - if (d_socket.getHandle() != -1) { - s_connManager.releaseConnection(); - } - } - - const Socket& getSocket() const - { - return d_socket; - } - - const ComboAddress& getClient() const - { - return d_client; - } - -private: - ComboAddress d_client; - Socket d_socket; -}; - -#ifndef DISABLE_PROMETHEUS -static MetricDefinitionStorage s_metricDefinitions; - -std::map 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")}, - { "rule-truncated", MetricDefinition(PrometheusMetricType::counter, "Number of truncated answers returned 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")}, - { "latency-tcp-avg100", MetricDefinition(PrometheusMetricType::gauge, "Average response latency, in microseconds, of the last 100 packets received over TCP")}, - { "latency-tcp-avg1000", MetricDefinition(PrometheusMetricType::gauge, "Average response latency, in microseconds, of the last 1000 packets received over TCP")}, - { "latency-tcp-avg10000", MetricDefinition(PrometheusMetricType::gauge, "Average response latency, in microseconds, of the last 10000 packets received over TCP")}, - { "latency-tcp-avg1000000", MetricDefinition(PrometheusMetricType::gauge, "Average response latency, in microseconds, of the last 1000000 packets received over TCP")}, - { "latency-dot-avg100", MetricDefinition(PrometheusMetricType::gauge, "Average response latency, in microseconds, of the last 100 packets received over DoT")}, - { "latency-dot-avg1000", MetricDefinition(PrometheusMetricType::gauge, "Average response latency, in microseconds, of the last 1000 packets received over DoT")}, - { "latency-dot-avg10000", MetricDefinition(PrometheusMetricType::gauge, "Average response latency, in microseconds, of the last 10000 packets received over DoT")}, - { "latency-dot-avg1000000", MetricDefinition(PrometheusMetricType::gauge, "Average response latency, in microseconds, of the last 1000000 packets received over DoT")}, - { "latency-doh-avg100", MetricDefinition(PrometheusMetricType::gauge, "Average response latency, in microseconds, of the last 100 packets received over DoH")}, - { "latency-doh-avg1000", MetricDefinition(PrometheusMetricType::gauge, "Average response latency, in microseconds, of the last 1000 packets received over DoH")}, - { "latency-doh-avg10000", MetricDefinition(PrometheusMetricType::gauge, "Average response latency, in microseconds, of the last 10000 packets received over DoH")}, - { "latency-doh-avg1000000", MetricDefinition(PrometheusMetricType::gauge, "Average response latency, in microseconds, of the last 1000000 packets received over DoH")}, - { "latency-doq-avg100", MetricDefinition(PrometheusMetricType::gauge, "Average response latency, in microseconds, of the last 100 packets received over DoQ")}, - { "latency-doq-avg1000", MetricDefinition(PrometheusMetricType::gauge, "Average response latency, in microseconds, of the last 1000 packets received over DoQ")}, - { "latency-doq-avg10000", MetricDefinition(PrometheusMetricType::gauge, "Average response latency, in microseconds, of the last 10000 packets received over DoQ")}, - { "latency-doq-avg1000000", MetricDefinition(PrometheusMetricType::gauge, "Average response latency, in microseconds, of the last 1000000 packets received over DoQ")}, - { "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") }, - { "doh-query-pipe-full", MetricDefinition(PrometheusMetricType::counter, "Number of DoH queries dropped because the internal pipe used to distribute queries was full") }, - { "doh-response-pipe-full", MetricDefinition(PrometheusMetricType::counter, "Number of DoH responses dropped because the internal pipe used to distribute responses was full") }, - { "outgoing-doh-query-pipe-full", MetricDefinition(PrometheusMetricType::counter, "Number of outgoing DoH queries dropped because the internal pipe used to distribute queries was full") }, - { "tcp-query-pipe-full", MetricDefinition(PrometheusMetricType::counter, "Number of TCP queries dropped because the internal pipe used to distribute queries was full") }, - { "tcp-cross-protocol-query-pipe-full", MetricDefinition(PrometheusMetricType::counter, "Number of TCP cross-protocol queries dropped because the internal pipe used to distribute queries was full") }, - { "tcp-cross-protocol-response-pipe-full", MetricDefinition(PrometheusMetricType::counter, "Number of TCP cross-protocol responses dropped because the internal pipe used to distribute queries was full") }, - { "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") }, - { "udp-in-csum-errors", MetricDefinition(PrometheusMetricType::counter, "From /proc/net/snmp InCsumErrors") }, - { "udp6-in-errors", MetricDefinition(PrometheusMetricType::counter, "From /proc/net/snmp6 Udp6InErrors") }, - { "udp6-recvbuf-errors", MetricDefinition(PrometheusMetricType::counter, "From /proc/net/snmp6 Udp6RcvbufErrors") }, - { "udp6-sndbuf-errors", MetricDefinition(PrometheusMetricType::counter, "From /proc/net/snmp6 Udp6SndbufErrors") }, - { "udp6-noport-errors", MetricDefinition(PrometheusMetricType::counter, "From /proc/net/snmp6 Udp6NoPorts") }, - { "udp6-in-csum-errors", MetricDefinition(PrometheusMetricType::counter, "From /proc/net/snmp6 Udp6InCsumErrors") }, - { "tcp-listen-overflows", MetricDefinition(PrometheusMetricType::counter, "From /proc/net/netstat ListenOverflows") }, - { "proxy-protocol-invalid", MetricDefinition(PrometheusMetricType::counter, "Number of queries dropped because of an invalid Proxy Protocol header") }, -}; -#endif /* DISABLE_PROMETHEUS */ - -bool addMetricDefinition(const dnsdist::prometheus::PrometheusMetricDefinition& def) { -#ifndef DISABLE_PROMETHEUS - return MetricDefinitionStorage::addMetricDefinition(def); -#else - return true; -#endif /* DISABLE_PROMETHEUS */ -} - -#ifndef DISABLE_WEB_CONFIG -static bool apiWriteConfigFile(const string& filebasename, const string& content) -{ - if (!g_apiReadWrite) { - warnlog("Not writing content to %s since the API is read-only", filebasename); - return false; - } - - if (g_apiConfigDirectory.empty()) { - vinfolog("Not writing content to %s since the API configuration directory is not set", filebasename); - return false; - } - - string filename = g_apiConfigDirectory + "/" + filebasename + ".conf"; - ofstream ofconf(filename.c_str()); - if (!ofconf) { - errlog("Could not open configuration fragment file '%s' for writing: %s", filename, stringerror()); - return false; - } - ofconf << "-- Generated by the REST API, DO NOT EDIT" << endl; - ofconf << content << endl; - ofconf.close(); - return true; -} - -static void apiSaveACL(const NetmaskGroup& nmg) -{ - auto aclEntries = nmg.toStringVector(); - - string acl; - for (const auto& entry : aclEntries) { - if (!acl.empty()) { - acl += ", "; - } - acl += "\"" + entry + "\""; - } - - string content = "setACL({" + acl + "})"; - apiWriteConfigFile("acl", content); -} -#endif /* DISABLE_WEB_CONFIG */ - -static bool checkAPIKey(const YaHTTP::Request& req, const std::unique_ptr& apiKey) -{ - if (!apiKey) { - return false; - } - - const auto header = req.headers.find("x-api-key"); - if (header != req.headers.end()) { - return apiKey->matches(header->second); - } - - return false; -} - -static bool checkWebPassword(const YaHTTP::Request& req, const std::unique_ptr& password, bool dashboardRequiresAuthentication) -{ - if (!dashboardRequiresAuthentication) { - return true; - } - - static const char basicStr[] = "basic "; - - const auto header = req.headers.find("authorization"); - - if (header != req.headers.end() && toLower(header->second).find(basicStr) == 0) { - string cookie = header->second.substr(sizeof(basicStr) - 1); - - string plain; - B64Decode(cookie, plain); - - vector cparts; - stringtok(cparts, plain, ":"); - - if (cparts.size() == 2) { - if (password) { - return password->matches(cparts.at(1)); - } - return true; - } - } - - return false; -} - -static bool isAnAPIRequest(const YaHTTP::Request& req) -{ - return req.url.path.find("/api/") == 0; -} - -static bool isAnAPIRequestAllowedWithWebAuth(const YaHTTP::Request& req) -{ - return req.url.path == "/api/v1/servers/localhost"; -} - -static bool isAStatsRequest(const YaHTTP::Request& req) -{ - return req.url.path == "/jsonstat" || req.url.path == "/metrics"; -} - -static bool handleAuthorization(const YaHTTP::Request& req) -{ - auto config = g_webserverConfig.lock(); - - if (isAStatsRequest(req)) { - if (config->statsRequireAuthentication) { - /* Access to the stats is allowed for both API and Web users */ - return checkAPIKey(req, config->apiKey) || checkWebPassword(req, config->password, config->dashboardRequiresAuthentication); - } - return true; - } - - if (isAnAPIRequest(req)) { - /* Access to the API requires a valid API key */ - if (!config->apiRequiresAuthentication || checkAPIKey(req, config->apiKey)) { - return true; - } - - return isAnAPIRequestAllowedWithWebAuth(req) && checkWebPassword(req, config->password, config->dashboardRequiresAuthentication); - } - - return checkWebPassword(req, config->password, config->dashboardRequiresAuthentication); -} - -static bool isMethodAllowed(const YaHTTP::Request& req) -{ - if (req.method == "GET") { - return true; - } - if (req.method == "PUT" && g_apiReadWrite) { - if (req.url.path == "/api/v1/servers/localhost/config/allow-from") { - return true; - } - } -#ifndef DISABLE_WEB_CACHE_MANAGEMENT - if (req.method == "DELETE") { - if (req.url.path == "/api/v1/cache") { - return true; - } - } -#endif /* DISABLE_WEB_CACHE_MANAGEMENT */ - return false; -} - -static bool isClientAllowedByACL(const ComboAddress& remote) -{ - return g_webserverConfig.lock()->acl.match(remote); -} - -static void handleCORS(const YaHTTP::Request& req, YaHTTP::Response& resp) -{ - const auto origin = req.headers.find("Origin"); - if (origin != req.headers.end()) { - if (req.method == "OPTIONS") { - /* Pre-flight request */ - if (g_apiReadWrite) { - resp.headers["Access-Control-Allow-Methods"] = "GET, PUT"; - } - else { - resp.headers["Access-Control-Allow-Methods"] = "GET"; - } - resp.headers["Access-Control-Allow-Headers"] = "Authorization, X-API-Key"; - } - - resp.headers["Access-Control-Allow-Origin"] = origin->second; - - if (isAStatsRequest(req) || isAnAPIRequestAllowedWithWebAuth(req)) { - resp.headers["Access-Control-Allow-Credentials"] = "true"; - } - } -} - -static void addSecurityHeaders(YaHTTP::Response& resp, const boost::optional >& customHeaders) -{ - static const std::vector > headers = { - { "X-Content-Type-Options", "nosniff" }, - { "X-Frame-Options", "deny" }, - { "X-Permitted-Cross-Domain-Policies", "none" }, - { "X-XSS-Protection", "1; mode=block" }, - { "Content-Security-Policy", "default-src 'self'; style-src 'self' 'unsafe-inline'" }, - }; - - for (const auto& h : headers) { - if (customHeaders) { - const auto& custom = customHeaders->find(h.first); - if (custom != customHeaders->end()) { - continue; - } - } - resp.headers[h.first] = h.second; - } -} - -static void addCustomHeaders(YaHTTP::Response& resp, const boost::optional >& customHeaders) -{ - if (!customHeaders) - return; - - for (const auto& c : *customHeaders) { - if (!c.second.empty()) { - resp.headers[c.first] = c.second; - } - } -} - -template -static json11::Json::array someResponseRulesToJson(GlobalStateHolder>* someResponseRules) -{ - using namespace json11; - Json::array responseRules; - int num=0; - auto localResponseRules = someResponseRules->getLocal(); - responseRules.reserve(localResponseRules->size()); - for (const auto& a : *localResponseRules) { - responseRules.push_back(Json::object{ - {"id", num++}, - {"creationOrder", (double)a.d_creationOrder}, - {"uuid", boost::uuids::to_string(a.d_id)}, - {"name", a.d_name}, - {"matches", (double)a.d_rule->d_matches}, - {"rule", a.d_rule->toString()}, - {"action", a.d_action->toString()}, - }); - } - return responseRules; -} - -#ifndef DISABLE_PROMETHEUS -template -static void addRulesToPrometheusOutput(std::ostringstream& output, GlobalStateHolder >& rules) -{ - auto localRules = rules.getLocal(); - for (const auto& entry : *localRules) { - std::string id = !entry.d_name.empty() ? entry.d_name : boost::uuids::to_string(entry.d_id); - output << "dnsdist_rule_hits{id=\"" << id << "\"} " << entry.d_rule->d_matches << "\n"; - } -} - -static void handlePrometheus(const YaHTTP::Request& req, YaHTTP::Response& resp) -{ - handleCORS(req, resp); - resp.status = 200; - - std::ostringstream output; - static const std::set metricBlacklist = { "special-memory-usage", "latency-count", "latency-sum" }; - { - auto entries = dnsdist::metrics::g_stats.entries.read_lock(); - for (const auto& entry : *entries) { - const auto& metricName = entry.d_name; - - if (metricBlacklist.count(metricName) != 0) { - continue; - } - - MetricDefinition metricDetails; - if (!s_metricDefinitions.getMetricDetails(metricName, metricDetails)) { - vinfolog("Do not have metric details for %s", metricName); - continue; - } - - const std::string prometheusTypeName = s_metricDefinitions.getPrometheusStringMetricType(metricDetails.prometheusType); - if (prometheusTypeName.empty()) { - vinfolog("Unknown Prometheus type for %s", metricName); - continue; - } - - // Prometheus suggest using '_' instead of '-' - std::string prometheusMetricName; - if (metricDetails.customName.empty()) { - prometheusMetricName = "dnsdist_" + boost::replace_all_copy(metricName, "-", "_"); - } - else { - prometheusMetricName = metricDetails.customName; - } - - // for these we have the help and types encoded in the sources - // but we need to be careful about labels in custom metrics - std::string helpName = prometheusMetricName.substr(0, prometheusMetricName.find('{')); - output << "# HELP " << helpName << " " << metricDetails.description << "\n"; - output << "# TYPE " << helpName << " " << prometheusTypeName << "\n"; - output << prometheusMetricName << " "; - - if (const auto& val = std::get_if(&entry.d_value)) { - output << (*val)->load(); - } - else if (const auto& adval = std::get_if*>(&entry.d_value)) { - output << (*adval)->load(); - } - else if (const auto& dval = std::get_if(&entry.d_value)) { - output << **dval; - } - else if (const auto& func = std::get_if(&entry.d_value)) { - output << (*func)(entry.d_name); - } - - output << "\n"; - } - } - - // Latency histogram buckets - output << "# HELP dnsdist_latency Histogram of responses by latency (in milliseconds)\n"; - output << "# TYPE dnsdist_latency histogram\n"; - uint64_t latency_amounts = dnsdist::metrics::g_stats.latency0_1; - output << "dnsdist_latency_bucket{le=\"1\"} " << latency_amounts << "\n"; - latency_amounts += dnsdist::metrics::g_stats.latency1_10; - output << "dnsdist_latency_bucket{le=\"10\"} " << latency_amounts << "\n"; - latency_amounts += dnsdist::metrics::g_stats.latency10_50; - output << "dnsdist_latency_bucket{le=\"50\"} " << latency_amounts << "\n"; - latency_amounts += dnsdist::metrics::g_stats.latency50_100; - output << "dnsdist_latency_bucket{le=\"100\"} " << latency_amounts << "\n"; - latency_amounts += dnsdist::metrics::g_stats.latency100_1000; - output << "dnsdist_latency_bucket{le=\"1000\"} " << latency_amounts << "\n"; - latency_amounts += dnsdist::metrics::g_stats.latencySlow; // Should be the same as latency_count - output << "dnsdist_latency_bucket{le=\"+Inf\"} " << latency_amounts << "\n"; - output << "dnsdist_latency_sum " << dnsdist::metrics::g_stats.latencySum << "\n"; - output << "dnsdist_latency_count " << dnsdist::metrics::g_stats.latencyCount << "\n"; - - 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 << "responses " << "counter" << "\n"; - output << "# HELP " << statesbase << "noncompliantresponses " << "Amount of non-compliant responses received from this server" << "\n"; - output << "# TYPE " << statesbase << "noncompliantresponses " << "counter" << "\n"; - output << "# HELP " << statesbase << "drops " << "Amount of queries not answered by 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 << "# 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"; - output << "# HELP " << statesbase << "order " << "The order in which this server is picked" << "\n"; - output << "# TYPE " << statesbase << "order " << "gauge" << "\n"; - output << "# HELP " << statesbase << "weight " << "The weight within the order in which this server is picked" << "\n"; - output << "# TYPE " << statesbase << "weight " << "gauge" << "\n"; - output << "# HELP " << statesbase << "tcpdiedsendingquery " << "The number of TCP I/O errors while sending the query" << "\n"; - output << "# TYPE " << statesbase << "tcpdiedsendingquery " << "counter" << "\n"; - output << "# HELP " << statesbase << "tcpdiedreadingresponse " << "The number of TCP I/O errors while reading the response" << "\n"; - output << "# TYPE " << statesbase << "tcpdiedreadingresponse " << "counter" << "\n"; - output << "# HELP " << statesbase << "tcpgaveup " << "The number of TCP connections failing after too many attempts" << "\n"; - output << "# TYPE " << statesbase << "tcpgaveup " << "counter" << "\n"; - output << "# HELP " << statesbase << "tcpconnecttimeouts " << "The number of TCP connect timeouts" << "\n"; - output << "# TYPE " << statesbase << "tcpconnecttimeouts " << "counter" << "\n"; - output << "# HELP " << statesbase << "tcpreadtimeouts " << "The number of TCP read timeouts" << "\n"; - output << "# TYPE " << statesbase << "tcpreadtimeouts " << "counter" << "\n"; - output << "# HELP " << statesbase << "tcpwritetimeouts " << "The number of TCP write timeouts" << "\n"; - output << "# TYPE " << statesbase << "tcpwritetimeouts " << "counter" << "\n"; - output << "# HELP " << statesbase << "tcpcurrentconnections " << "The number of current TCP connections" << "\n"; - output << "# TYPE " << statesbase << "tcpcurrentconnections " << "gauge" << "\n"; - output << "# HELP " << statesbase << "tcpmaxconcurrentconnections " << "The maximum number of concurrent TCP connections" << "\n"; - output << "# TYPE " << statesbase << "tcpmaxconcurrentconnections " << "counter" << "\n"; - output << "# HELP " << statesbase << "tcptoomanyconcurrentconnections " << "Number of times we had to enforce the maximum number of concurrent TCP connections" << "\n"; - output << "# TYPE " << statesbase << "tcptoomanyconcurrentconnections " << "counter" << "\n"; - output << "# HELP " << statesbase << "tcpnewconnections " << "The number of established TCP connections in total" << "\n"; - output << "# TYPE " << statesbase << "tcpnewconnections " << "counter" << "\n"; - output << "# HELP " << statesbase << "tcpreusedconnections " << "The number of times a TCP connection has been reused" << "\n"; - output << "# TYPE " << statesbase << "tcpreusedconnections " << "counter" << "\n"; - output << "# HELP " << statesbase << "tcpavgqueriesperconn " << "The average number of queries per TCP connection" << "\n"; - output << "# TYPE " << statesbase << "tcpavgqueriesperconn " << "gauge" << "\n"; - output << "# HELP " << statesbase << "tcpavgconnduration " << "The average duration of a TCP connection (ms)" << "\n"; - output << "# TYPE " << statesbase << "tcpavgconnduration " << "gauge" << "\n"; - output << "# HELP " << statesbase << "tlsresumptions " << "The number of times a TLS session has been resumed" << "\n"; - output << "# TYPE " << statesbase << "tlsresumptions " << "counter" << "\n"; - output << "# HELP " << statesbase << "tcplatency " << "Server's latency when answering TCP questions in milliseconds" << "\n"; - output << "# TYPE " << statesbase << "tcplatency " << "gauge" << "\n"; - output << "# HELP " << statesbase << "healthcheckfailures " << "Number of health check attempts that failed (total)" << "\n"; - output << "# TYPE " << statesbase << "healthcheckfailures " << "counter" << "\n"; - output << "# HELP " << statesbase << "healthcheckfailuresparsing " << "Number of health check attempts where the response could not be parsed" << "\n"; - output << "# TYPE " << statesbase << "healthcheckfailuresparsing " << "counter" << "\n"; - output << "# HELP " << statesbase << "healthcheckfailurestimeout " << "Number of health check attempts where the response did not arrive in time" << "\n"; - output << "# TYPE " << statesbase << "healthcheckfailurestimeout " << "counter" << "\n"; - output << "# HELP " << statesbase << "healthcheckfailuresnetwork " << "Number of health check attempts that experienced a network issue" << "\n"; - output << "# TYPE " << statesbase << "healthcheckfailuresnetwork " << "counter" << "\n"; - output << "# HELP " << statesbase << "healthcheckfailuresmismatch " << "Number of health check attempts where the response did not match the query" << "\n"; - output << "# TYPE " << statesbase << "healthcheckfailuresmismatch " << "counter" << "\n"; - output << "# HELP " << statesbase << "healthcheckfailuresinvalid " << "Number of health check attempts where the DNS response was invalid" << "\n"; - output << "# TYPE " << statesbase << "healthcheckfailuresinvalid " << "counter" << "\n"; - - for (const auto& state : *states) { - string serverName; - - if (state->getName().empty()) { - serverName = state->d_config.remote.toStringWithPort(); - } - else { - serverName = state->getName(); - } - - boost::replace_all(serverName, ".", "_"); - - const std::string label = boost::str(boost::format("{server=\"%1%\",address=\"%2%\"}") - % serverName % state->d_config.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 << "noncompliantresponses" << label << " " << state->nonCompliantResponses.load() << "\n"; - output << statesbase << "drops" << label << " " << state->reuseds.load() << "\n"; - if (state->isUp()) { - output << statesbase << "latency" << label << " " << state->latencyUsec/1000.0 << "\n"; - output << statesbase << "tcplatency" << label << " " << state->latencyUsecTCP/1000.0 << "\n"; - } - output << statesbase << "senderrors" << label << " " << state->sendErrors.load() << "\n"; - output << statesbase << "outstanding" << label << " " << state->outstanding.load() << "\n"; - output << statesbase << "order" << label << " " << state->d_config.order << "\n"; - output << statesbase << "weight" << label << " " << state->d_config.d_weight << "\n"; - output << statesbase << "tcpdiedsendingquery" << label << " " << state->tcpDiedSendingQuery << "\n"; - output << statesbase << "tcpdiedreadingresponse" << label << " " << state->tcpDiedReadingResponse << "\n"; - output << statesbase << "tcpgaveup" << label << " " << state->tcpGaveUp << "\n"; - output << statesbase << "tcpreadtimeouts" << label << " " << state->tcpReadTimeouts << "\n"; - output << statesbase << "tcpwritetimeouts" << label << " " << state->tcpWriteTimeouts << "\n"; - output << statesbase << "tcpconnecttimeouts" << label << " " << state->tcpConnectTimeouts << "\n"; - output << statesbase << "tcpcurrentconnections" << label << " " << state->tcpCurrentConnections << "\n"; - output << statesbase << "tcpmaxconcurrentconnections" << label << " " << state->tcpMaxConcurrentConnections << "\n"; - output << statesbase << "tcptoomanyconcurrentconnections" << label << " " << state->tcpTooManyConcurrentConnections << "\n"; - output << statesbase << "tcpnewconnections" << label << " " << state->tcpNewConnections << "\n"; - output << statesbase << "tcpreusedconnections" << label << " " << state->tcpReusedConnections << "\n"; - output << statesbase << "tcpavgqueriesperconn" << label << " " << state->tcpAvgQueriesPerConnection << "\n"; - output << statesbase << "tcpavgconnduration" << label << " " << state->tcpAvgConnectionDuration << "\n"; - output << statesbase << "tlsresumptions" << label << " " << state->tlsResumptions << "\n"; - output << statesbase << "healthcheckfailures" << label << " " << state->d_healthCheckMetrics.d_failures << "\n"; - output << statesbase << "healthcheckfailuresparsing" << label << " " << state->d_healthCheckMetrics.d_parseErrors << "\n"; - output << statesbase << "healthcheckfailurestimeout" << label << " " << state->d_healthCheckMetrics.d_timeOuts << "\n"; - output << statesbase << "healthcheckfailuresnetwork" << label << " " << state->d_healthCheckMetrics.d_networkErrors << "\n"; - output << statesbase << "healthcheckfailuresmismatch" << label << " " << state->d_healthCheckMetrics.d_mismatchErrors << "\n"; - output << statesbase << "healthcheckfailuresinvalid" << label << " " << state->d_healthCheckMetrics.d_invalidResponseErrors << "\n"; - } - - const string frontsbase = "dnsdist_frontend_"; - output << "# HELP " << frontsbase << "queries " << "Amount of queries received by this frontend" << "\n"; - output << "# TYPE " << frontsbase << "queries " << "counter" << "\n"; - output << "# HELP " << frontsbase << "noncompliantqueries " << "Amount of non-compliant queries received by this frontend" << "\n"; - output << "# TYPE " << frontsbase << "noncompliantqueries " << "counter" << "\n"; - output << "# HELP " << frontsbase << "responses " << "Amount of responses sent by this frontend" << "\n"; - output << "# TYPE " << frontsbase << "responses " << "counter" << "\n"; - output << "# HELP " << frontsbase << "tcpdiedreadingquery " << "Amount of TCP connections terminated while reading the query from the client" << "\n"; - output << "# TYPE " << frontsbase << "tcpdiedreadingquery " << "counter" << "\n"; - output << "# HELP " << frontsbase << "tcpdiedsendingresponse " << "Amount of TCP connections terminated while sending a response to the client" << "\n"; - output << "# TYPE " << frontsbase << "tcpdiedsendingresponse " << "counter" << "\n"; - output << "# HELP " << frontsbase << "tcpgaveup " << "Amount of TCP connections terminated after too many attempts to get a connection to the backend" << "\n"; - output << "# TYPE " << frontsbase << "tcpgaveup " << "counter" << "\n"; - output << "# HELP " << frontsbase << "tcpclienttimeouts " << "Amount of TCP connections terminated by a timeout while reading from the client" << "\n"; - output << "# TYPE " << frontsbase << "tcpclienttimeouts " << "counter" << "\n"; - output << "# HELP " << frontsbase << "tcpdownstreamtimeouts " << "Amount of TCP connections terminated by a timeout while reading from the backend" << "\n"; - output << "# TYPE " << frontsbase << "tcpdownstreamtimeouts " << "counter" << "\n"; - output << "# HELP " << frontsbase << "tcpcurrentconnections " << "Amount of current incoming TCP connections from clients" << "\n"; - output << "# TYPE " << frontsbase << "tcpcurrentconnections " << "gauge" << "\n"; - output << "# HELP " << frontsbase << "tcpmaxconcurrentconnections " << "Maximum number of concurrent incoming TCP connections from clients" << "\n"; - output << "# TYPE " << frontsbase << "tcpmaxconcurrentconnections " << "counter" << "\n"; - output << "# HELP " << frontsbase << "tcpavgqueriesperconnection " << "The average number of queries per TCP connection" << "\n"; - output << "# TYPE " << frontsbase << "tcpavgqueriesperconnection " << "gauge" << "\n"; - output << "# HELP " << frontsbase << "tcpavgconnectionduration " << "The average duration of a TCP connection (ms)" << "\n"; - output << "# TYPE " << frontsbase << "tcpavgconnectionduration " << "gauge" << "\n"; - output << "# HELP " << frontsbase << "tlsqueries " << "Number of queries received by dnsdist over TLS, by TLS version" << "\n"; - output << "# TYPE " << frontsbase << "tlsqueries " << "counter" << "\n"; - output << "# HELP " << frontsbase << "tlsnewsessions " << "Amount of new TLS sessions negotiated" << "\n"; - output << "# TYPE " << frontsbase << "tlsnewsessions " << "counter" << "\n"; - output << "# HELP " << frontsbase << "tlsresumptions " << "Amount of TLS sessions resumed" << "\n"; - output << "# TYPE " << frontsbase << "tlsresumptions " << "counter" << "\n"; - output << "# HELP " << frontsbase << "tlsunknownticketkeys " << "Amount of attempts to resume TLS session from an unknown key (possibly expired)" << "\n"; - output << "# TYPE " << frontsbase << "tlsunknownticketkeys " << "counter" << "\n"; - output << "# HELP " << frontsbase << "tlsinactiveticketkeys " << "Amount of TLS sessions resumed from an inactive key" << "\n"; - output << "# TYPE " << frontsbase << "tlsinactiveticketkeys " << "counter" << "\n"; - output << "# HELP " << frontsbase << "tlshandshakefailures " << "Amount of TLS handshake failures" << "\n"; - output << "# TYPE " << frontsbase << "tlshandshakefailures " << "counter" << "\n"; - - std::map frontendDuplicates; - for (const auto& front : g_frontends) { - if (front->udpFD == -1 && front->tcpFD == -1) - continue; - - const string frontName = front->local.toStringWithPort(); - const string proto = front->getType(); - const string fullName = frontName + "_" + proto; - uint64_t threadNumber = 0; - auto dupPair = frontendDuplicates.emplace(fullName, 1); - if (!dupPair.second) { - threadNumber = dupPair.first->second; - ++(dupPair.first->second); - } - const std::string label = boost::str(boost::format("{frontend=\"%1%\",proto=\"%2%\",thread=\"%3%\"} ") - % frontName % proto % threadNumber); - - output << frontsbase << "queries" << label << front->queries.load() << "\n"; - output << frontsbase << "noncompliantqueries" << label << front->nonCompliantQueries.load() << "\n"; - output << frontsbase << "responses" << label << front->responses.load() << "\n"; - if (front->isTCP()) { - output << frontsbase << "tcpdiedreadingquery" << label << front->tcpDiedReadingQuery.load() << "\n"; - output << frontsbase << "tcpdiedsendingresponse" << label << front->tcpDiedSendingResponse.load() << "\n"; - output << frontsbase << "tcpgaveup" << label << front->tcpGaveUp.load() << "\n"; - output << frontsbase << "tcpclienttimeouts" << label << front->tcpClientTimeouts.load() << "\n"; - output << frontsbase << "tcpdownstreamtimeouts" << label << front->tcpDownstreamTimeouts.load() << "\n"; - output << frontsbase << "tcpcurrentconnections" << label << front->tcpCurrentConnections.load() << "\n"; - output << frontsbase << "tcpmaxconcurrentconnections" << label << front->tcpMaxConcurrentConnections.load() << "\n"; - output << frontsbase << "tcpavgqueriesperconnection" << label << front->tcpAvgQueriesPerConnection.load() << "\n"; - output << frontsbase << "tcpavgconnectionduration" << label << front->tcpAvgConnectionDuration.load() << "\n"; - if (front->hasTLS()) { - output << frontsbase << "tlsnewsessions" << label << front->tlsNewSessions.load() << "\n"; - output << frontsbase << "tlsresumptions" << label << front->tlsResumptions.load() << "\n"; - output << frontsbase << "tlsunknownticketkeys" << label << front->tlsUnknownTicketKey.load() << "\n"; - output << frontsbase << "tlsinactiveticketkeys" << label << front->tlsInactiveTicketKey.load() << "\n"; - - output << frontsbase << "tlsqueries{frontend=\"" << frontName << "\",proto=\"" << proto << "\",thread=\"" << threadNumber << "\",tls=\"tls10\"} " << front->tls10queries.load() << "\n"; - output << frontsbase << "tlsqueries{frontend=\"" << frontName << "\",proto=\"" << proto << "\",thread=\"" << threadNumber << "\",tls=\"tls11\"} " << front->tls11queries.load() << "\n"; - output << frontsbase << "tlsqueries{frontend=\"" << frontName << "\",proto=\"" << proto << "\",thread=\"" << threadNumber << "\",tls=\"tls12\"} " << front->tls12queries.load() << "\n"; - output << frontsbase << "tlsqueries{frontend=\"" << frontName << "\",proto=\"" << proto << "\",thread=\"" << threadNumber << "\",tls=\"tls13\"} " << front->tls13queries.load() << "\n"; - output << frontsbase << "tlsqueries{frontend=\"" << frontName << "\",proto=\"" << proto << "\",thread=\"" << threadNumber << "\",tls=\"unknown\"} " << front->tlsUnknownqueries.load() << "\n"; - - const TLSErrorCounters* errorCounters = nullptr; - if (front->tlsFrontend != nullptr) { - errorCounters = &front->tlsFrontend->d_tlsCounters; - } - else if (front->dohFrontend != nullptr) { - errorCounters = &front->dohFrontend->d_tlsContext.d_tlsCounters; - } - - if (errorCounters != nullptr) { - output << frontsbase << "tlshandshakefailures{frontend=\"" << frontName << "\",proto=\"" << proto << "\",thread=\"" << threadNumber << "\",error=\"dhKeyTooSmall\"} " << errorCounters->d_dhKeyTooSmall << "\n"; - output << frontsbase << "tlshandshakefailures{frontend=\"" << frontName << "\",proto=\"" << proto << "\",thread=\"" << threadNumber << "\",error=\"inappropriateFallBack\"} " << errorCounters->d_inappropriateFallBack << "\n"; - output << frontsbase << "tlshandshakefailures{frontend=\"" << frontName << "\",proto=\"" << proto << "\",thread=\"" << threadNumber << "\",error=\"noSharedCipher\"} " << errorCounters->d_noSharedCipher << "\n"; - output << frontsbase << "tlshandshakefailures{frontend=\"" << frontName << "\",proto=\"" << proto << "\",thread=\"" << threadNumber << "\",error=\"unknownCipherType\"} " << errorCounters->d_unknownCipherType << "\n"; - output << frontsbase << "tlshandshakefailures{frontend=\"" << frontName << "\",proto=\"" << proto << "\",thread=\"" << threadNumber << "\",error=\"unknownKeyExchangeType\"} " << errorCounters->d_unknownKeyExchangeType << "\n"; - output << frontsbase << "tlshandshakefailures{frontend=\"" << frontName << "\",proto=\"" << proto << "\",thread=\"" << threadNumber << "\",error=\"unknownProtocol\"} " << errorCounters->d_unknownProtocol << "\n"; - output << frontsbase << "tlshandshakefailures{frontend=\"" << frontName << "\",proto=\"" << proto << "\",thread=\"" << threadNumber << "\",error=\"unsupportedEC\"} " << errorCounters->d_unsupportedEC << "\n"; - output << frontsbase << "tlshandshakefailures{frontend=\"" << frontName << "\",proto=\"" << proto << "\",thread=\"" << threadNumber << "\",error=\"unsupportedProtocol\"} " << errorCounters->d_unsupportedProtocol << "\n"; - } - } - } - } - - output << "# HELP " << frontsbase << "http_connects " << "Number of DoH TCP connections established to this frontend" << "\n"; - output << "# TYPE " << frontsbase << "http_connects " << "counter" << "\n"; - - output << "# HELP " << frontsbase << "doh_http_method_queries " << "Number of DoH queries received by dnsdist, by HTTP method" << "\n"; - output << "# TYPE " << frontsbase << "doh_http_method_queries " << "counter" << "\n"; - - output << "# HELP " << frontsbase << "doh_http_version_queries " << "Number of DoH queries received by dnsdist, by HTTP version" << "\n"; - output << "# TYPE " << frontsbase << "doh_http_version_queries " << "counter" << "\n"; - - output << "# HELP " << frontsbase << "doh_bad_requests " << "Number of requests that could not be converted to a DNS query" << "\n"; - output << "# TYPE " << frontsbase << "doh_bad_requests " << "counter" << "\n"; - - output << "# HELP " << frontsbase << "doh_responses " << "Number of responses sent, by type" << "\n"; - output << "# TYPE " << frontsbase << "doh_responses " << "counter" << "\n"; - - output << "# HELP " << frontsbase << "doh_version_status_responses " << "Number of requests that could not be converted to a DNS query" << "\n"; - output << "# TYPE " << frontsbase << "doh_version_status_responses " << "counter" << "\n"; - -#ifdef HAVE_DNS_OVER_HTTPS - std::map dohFrontendDuplicates; - for(const auto& doh : g_dohlocals) { - const string frontName = doh->d_tlsContext.d_addr.toStringWithPort(); - uint64_t threadNumber = 0; - auto dupPair = frontendDuplicates.emplace(frontName, 1); - if (!dupPair.second) { - threadNumber = dupPair.first->second; - ++(dupPair.first->second); - } - const std::string addrlabel = boost::str(boost::format("frontend=\"%1%\",thread=\"%2%\"") % frontName % threadNumber); - const std::string label = "{" + addrlabel + "} "; - - output << frontsbase << "http_connects" << label << doh->d_httpconnects << "\n"; - output << frontsbase << "doh_http_method_queries{method=\"get\"," << addrlabel << "} " << doh->d_getqueries << "\n"; - output << frontsbase << "doh_http_method_queries{method=\"post\"," << addrlabel << "} " << doh->d_postqueries << "\n"; - - output << frontsbase << "doh_http_version_queries{version=\"1\"," << addrlabel << "} " << doh->d_http1Stats.d_nbQueries << "\n"; - output << frontsbase << "doh_http_version_queries{version=\"2\"," << addrlabel << "} " << doh->d_http2Stats.d_nbQueries << "\n"; - - output << frontsbase << "doh_bad_requests{" << addrlabel << "} " << doh->d_badrequests << "\n"; - - output << frontsbase << "doh_responses{type=\"error\"," << addrlabel << "} " << doh->d_errorresponses << "\n"; - output << frontsbase << "doh_responses{type=\"redirect\"," << addrlabel << "} " << doh->d_redirectresponses << "\n"; - output << frontsbase << "doh_responses{type=\"valid\"," << addrlabel << "} " << doh->d_validresponses << "\n"; - - output << frontsbase << "doh_version_status_responses{httpversion=\"1\",status=\"200\"," << addrlabel << "} " << doh->d_http1Stats.d_nb200Responses << "\n"; - output << frontsbase << "doh_version_status_responses{httpversion=\"1\",status=\"400\"," << addrlabel << "} " << doh->d_http1Stats.d_nb400Responses << "\n"; - output << frontsbase << "doh_version_status_responses{httpversion=\"1\",status=\"403\"," << addrlabel << "} " << doh->d_http1Stats.d_nb403Responses << "\n"; - output << frontsbase << "doh_version_status_responses{httpversion=\"1\",status=\"500\"," << addrlabel << "} " << doh->d_http1Stats.d_nb500Responses << "\n"; - output << frontsbase << "doh_version_status_responses{httpversion=\"1\",status=\"502\"," << addrlabel << "} " << doh->d_http1Stats.d_nb502Responses << "\n"; - output << frontsbase << "doh_version_status_responses{httpversion=\"1\",status=\"other\"," << addrlabel << "} " << doh->d_http1Stats.d_nbOtherResponses << "\n"; - output << frontsbase << "doh_version_status_responses{httpversion=\"2\",status=\"200\"," << addrlabel << "} " << doh->d_http2Stats.d_nb200Responses << "\n"; - output << frontsbase << "doh_version_status_responses{httpversion=\"2\",status=\"400\"," << addrlabel << "} " << doh->d_http2Stats.d_nb400Responses << "\n"; - output << frontsbase << "doh_version_status_responses{httpversion=\"2\",status=\"403\"," << addrlabel << "} " << doh->d_http2Stats.d_nb403Responses << "\n"; - output << frontsbase << "doh_version_status_responses{httpversion=\"2\",status=\"500\"," << addrlabel << "} " << doh->d_http2Stats.d_nb500Responses << "\n"; - output << frontsbase << "doh_version_status_responses{httpversion=\"2\",status=\"502\"," << addrlabel << "} " << doh->d_http2Stats.d_nb502Responses << "\n"; - output << frontsbase << "doh_version_status_responses{httpversion=\"2\",status=\"other\"," << addrlabel << "} " << doh->d_http2Stats.d_nbOtherResponses << "\n"; - } -#endif /* HAVE_DNS_OVER_HTTPS */ - - auto localPools = g_pools.getLocal(); - const string cachebase = "dnsdist_pool_"; - output << "# HELP dnsdist_pool_servers " << "Number of servers in that pool" << "\n"; - output << "# TYPE dnsdist_pool_servers " << "gauge" << "\n"; - output << "# HELP dnsdist_pool_active_servers " << "Number of available servers in that pool" << "\n"; - output << "# TYPE dnsdist_pool_active_servers " << "gauge" << "\n"; - - output << "# HELP dnsdist_pool_cache_size " << "Maximum number of entries that this cache can hold" << "\n"; - output << "# TYPE dnsdist_pool_cache_size " << "gauge" << "\n"; - output << "# HELP dnsdist_pool_cache_entries " << "Number of entries currently present in that cache" << "\n"; - output << "# TYPE dnsdist_pool_cache_entries " << "gauge" << "\n"; - output << "# HELP dnsdist_pool_cache_hits " << "Number of hits from that cache" << "\n"; - output << "# TYPE dnsdist_pool_cache_hits " << "counter" << "\n"; - output << "# HELP dnsdist_pool_cache_misses " << "Number of misses from that cache" << "\n"; - output << "# TYPE dnsdist_pool_cache_misses " << "counter" << "\n"; - output << "# HELP dnsdist_pool_cache_deferred_inserts " << "Number of insertions into that cache skipped because it was already locked" << "\n"; - output << "# TYPE dnsdist_pool_cache_deferred_inserts " << "counter" << "\n"; - output << "# HELP dnsdist_pool_cache_deferred_lookups " << "Number of lookups into that cache skipped because it was already locked" << "\n"; - output << "# TYPE dnsdist_pool_cache_deferred_lookups " << "counter" << "\n"; - output << "# HELP dnsdist_pool_cache_lookup_collisions " << "Number of lookups into that cache that triggered a collision (same hash but different entry)" << "\n"; - output << "# TYPE dnsdist_pool_cache_lookup_collisions " << "counter" << "\n"; - output << "# HELP dnsdist_pool_cache_insert_collisions " << "Number of insertions into that cache that triggered a collision (same hash but different entry)" << "\n"; - output << "# TYPE dnsdist_pool_cache_insert_collisions " << "counter" << "\n"; - output << "# HELP dnsdist_pool_cache_ttl_too_shorts " << "Number of insertions into that cache skipped because the TTL of the answer was not long enough" << "\n"; - output << "# TYPE dnsdist_pool_cache_ttl_too_shorts " << "counter" << "\n"; - output << "# HELP dnsdist_pool_cache_cleanup_count_total " << "Number of times the cache has been scanned to remove expired entries, if any" << "\n"; - output << "# TYPE dnsdist_pool_cache_cleanup_count_total " << "counter" << "\n"; - - for (const auto& entry : *localPools) { - string poolName = entry.first; - - if (poolName.empty()) { - poolName = "_default_"; - } - const string label = "{pool=\"" + poolName + "\"}"; - const std::shared_ptr pool = entry.second; - output << "dnsdist_pool_servers" << label << " " << pool->countServers(false) << "\n"; - output << "dnsdist_pool_active_servers" << label << " " << pool->countServers(true) << "\n"; - - if (pool->packetCache != nullptr) { - const auto& cache = pool->packetCache; - - output << cachebase << "cache_size" <