diff --git a/.github/actions/spell-check/expect.txt b/.github/actions/spell-check/expect.txt index 289f683d0fa1..300b01fb685a 100644 --- a/.github/actions/spell-check/expect.txt +++ b/.github/actions/spell-check/expect.txt @@ -1212,6 +1212,7 @@ scopemask sdfn sdfoijdfio sdig +secnumdepth secpoll securitypolicy securitypolling @@ -1227,6 +1228,7 @@ servfail servicemode setaffinity setcontent +setcounter setdomainmetadata seting setkey diff --git a/.github/workflows/build-and-test-all.yml b/.github/workflows/build-and-test-all.yml index 79af546f7c2c..f7ce113e022f 100644 --- a/.github/workflows/build-and-test-all.yml +++ b/.github/workflows/build-and-test-all.yml @@ -252,6 +252,8 @@ jobs: path: ~/.ccache key: dnsdist-${{ matrix.features }}-${{ matrix.sanitizers }}-ccache-${{ steps.get-stamp.outputs.stamp }} restore-keys: dnsdist-${{ matrix.features }}-${{ matrix.sanitizers }}-ccache- + - run: inv install-lld-linker-if-needed + working-directory: ./pdns/dnsdistdist/ - run: inv ci-install-rust ${{ env.REPO_HOME }} working-directory: ./pdns/dnsdistdist/ - run: inv ci-build-and-install-quiche ${{ env.REPO_HOME }} diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index d7cdcc81c930..82d29cb0f704 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -150,6 +150,10 @@ jobs: if: matrix.product == 'dnsdist' run: | inv install-dnsdist-build-deps --skipXDP + # installing the python3-package does not work because of actions/setup-python which installs a different version in /opt/hostedtoolcache/Python + - name: Install python yaml + run: | + pip install pyyaml - name: Autoreconf dnsdist if: matrix.product == 'dnsdist' working-directory: ./pdns/dnsdistdist/ diff --git a/pdns/dnsdistdist/Makefile.am b/pdns/dnsdistdist/Makefile.am index 91a4e3d35a72..c1507b80b757 100644 --- a/pdns/dnsdistdist/Makefile.am +++ b/pdns/dnsdistdist/Makefile.am @@ -8,12 +8,19 @@ AM_CPPFLAGS += $(SYSTEMD_CFLAGS) \ $(NGHTTP2_CFLAGS) \ $(LIBCAP_CFLAGS) \ -I$(top_srcdir)/ext/protozero/include \ + -I$(top_srcdir)/dnsdist-rust-lib \ + -I$(top_builddir)/dnsdist-rust-lib \ + -I$(top_srcdir)/dnsdist-rust-lib/rust/src \ -DSYSCONFDIR=\"${sysconfdir}\" \ -DBOOST_CONTAINER_USE_STD_EXCEPTIONS +DNSDIST_RUST_LIBS = $(top_builddir)/dnsdist-rust-lib/rust/libdnsdist_rust.a $(LIBDL) + ACLOCAL_AMFLAGS = -I m4 -SUBDIRS=ext/arc4random \ +SUBDIRS=dnsdist-rust-lib \ + dnsdist-rust-lib/rust \ + ext/arc4random \ ext/ipcrypt \ ext/yahttp @@ -28,7 +35,13 @@ dnslabeltext.cc: dnslabeltext.rl $(AM_V_GEN)$(RAGEL) $< -o dnslabeltext.cc BUILT_SOURCES=htmlfiles.h \ + dnsdist-actions-factory-generated.cc dnsdist-actions-factory-generated.hh \ dnsdist-lua-ffi-interface.inc \ + dnsdist-lua-actions-generated.cc dnsdist-lua-response-actions-generated.cc \ + dnsdist-response-actions-factory-generated.cc dnsdist-response-actions-factory-generated.hh \ + dnsdist-rust-bridge-actions-generated.cc dnsdist-rust-bridge-actions-generated.hh \ + dnsdist-rust-bridge-selectors-generated.cc dnsdist-rust-bridge-selectors-generated.hh \ + dnsdist-selectors-factory-generated.cc dnsdist-selectors-factory-generated.hh \ dnslabeltext.cc htmlfiles.h: $(srcdir)/html/* $(srcdir)/incfiles @@ -42,6 +55,11 @@ dnsdist-lua-ffi-interface.inc: dnsdist-lua-ffi-interface.h dnsdist-lua-inspectio SRC_JS_FILES := $(wildcard src_js/*.js) MIN_JS_FILES := $(patsubst src_js/%.js,html/js/%.min.js,$(SRC_JS_FILES)) +dnsdist%generated.cc dnsdist%generated.hh: dnsdist-rules-generator.py dnsdist-actions-definitions.yml dnsdist-response-actions-definitions.yml dnsdist-selectors-definitions.yml + @if test "$(PYTHON)" = ":"; then echo "Actions or selectors definitions have changed, python is needed to regenerate the related files but python was not found. Please install python and re-run configure"; exit 1; fi + @if ! $(PYTHON) --version | grep -q "Python 3"; then echo $(PYTHON) should be at least version 3. Please install python 3 and re-run configure; exit 1; fi + $(PYTHON) dnsdist-rules-generator.py + html/js/%.min.js: src_js/%.js uglifyjs $< > $@ @@ -90,6 +108,16 @@ endif endif EXTRA_DIST=COPYING \ + dnsdist-rules-generator.py \ + dnsdist-actions-definitions.yml \ + dnsdist-response-actions-definitions.yml \ + dnsdist-rust-bridge.hh \ + dnsdist-rust-bridge-actions-generated.cc \ + dnsdist-rust-bridge-actions-generated.hh \ + dnsdist-rust-bridge-selectors-generated.cc \ + dnsdist-rust-bridge-selectors-generated.hh \ + dnsdist-selectors-definitions.yml \ + dnsdist-settings-definitions.yml \ dnslabeltext.rl \ dnsdist.conf-dist \ dnsmessage.proto \ @@ -144,13 +172,17 @@ dnsdist_SOURCES = \ dns.cc dns.hh \ dns_random.hh \ dnscrypt.cc dnscrypt.hh \ - dnsdist-actions.hh \ + dnsdist-actions-factory-generated.hh \ + dnsdist-actions-factory.cc dnsdist-actions-factory.hh \ + dnsdist-actions.cc dnsdist-actions.hh \ dnsdist-async.cc dnsdist-async.hh \ dnsdist-backend.cc dnsdist-backend.hh \ dnsdist-backoff.hh \ dnsdist-cache.cc dnsdist-cache.hh \ dnsdist-carbon.cc dnsdist-carbon.hh \ dnsdist-concurrent-connections.hh \ + dnsdist-configuration-yaml-internal.hh \ + dnsdist-configuration-yaml.cc dnsdist-configuration-yaml.hh \ dnsdist-configuration.cc dnsdist-configuration.hh \ dnsdist-console.cc dnsdist-console.hh \ dnsdist-crypto.cc dnsdist-crypto.hh \ @@ -180,6 +212,7 @@ dnsdist_SOURCES = \ dnsdist-lua-bindings-protobuf.cc \ dnsdist-lua-bindings-rings.cc \ dnsdist-lua-bindings.cc \ + dnsdist-lua-configuration-items.cc \ dnsdist-lua-ffi-interface.h dnsdist-lua-ffi-interface.inc \ dnsdist-lua-ffi.cc dnsdist-lua-ffi.hh \ dnsdist-lua-hooks.cc dnsdist-lua-hooks.hh \ @@ -203,8 +236,11 @@ dnsdist_SOURCES = \ dnsdist-resolver.cc dnsdist-resolver.hh \ dnsdist-rings.cc dnsdist-rings.hh \ dnsdist-rule-chains.cc dnsdist-rule-chains.hh \ + dnsdist-rules-factory.hh \ dnsdist-rules.cc dnsdist-rules.hh \ dnsdist-secpoll.cc dnsdist-secpoll.hh \ + dnsdist-selectors-factory-generated.hh \ + dnsdist-self-answers.cc dnsdist-self-answers.hh \ dnsdist-session-cache.cc dnsdist-session-cache.hh \ dnsdist-snmp.cc dnsdist-snmp.hh \ dnsdist-svc.cc dnsdist-svc.hh \ @@ -274,6 +310,7 @@ testrunner_SOURCES = \ credentials.cc credentials.hh \ dns.cc dns.hh \ dnscrypt.cc dnscrypt.hh \ + dnsdist-actions.cc dnsdist-actions.hh \ dnsdist-async.cc dnsdist-async.hh \ dnsdist-backend.cc dnsdist-backend.hh \ dnsdist-backoff.hh \ @@ -310,7 +347,9 @@ testrunner_SOURCES = \ dnsdist-resolver.cc dnsdist-resolver.hh \ dnsdist-rings.cc dnsdist-rings.hh \ dnsdist-rule-chains.cc dnsdist-rule-chains.hh \ + dnsdist-rules-factory.hh \ dnsdist-rules.cc dnsdist-rules.hh \ + dnsdist-self-answers.cc dnsdist-self-answers.hh \ dnsdist-session-cache.cc dnsdist-session-cache.hh \ dnsdist-svc.cc dnsdist-svc.hh \ dnsdist-tcp-downstream.cc \ @@ -419,6 +458,7 @@ endif if HAVE_RE2 dnsdist_LDADD += $(RE2_LIBS) +testrunner_LDADD += $(RE2_LIBS) endif if HAVE_LIBSSL @@ -488,6 +528,11 @@ testrunner_SOURCES += doq-common.cc testrunner_LDADD += $(QUICHE_LDFLAGS) $(QUICHE_LIBS) endif +if HAVE_YAML_CONFIGURATION +dnsdist_SOURCES += dnsdist-rust-lib/dnsdist-configuration-yaml-items-generated.cc +dnsdist_LDADD += $(DNSDIST_RUST_LIBS) +endif + if !HAVE_LUA_HPP BUILT_SOURCES += lua.hpp nodist_dnsdist_SOURCES = lua.hpp diff --git a/pdns/dnsdistdist/configure.ac b/pdns/dnsdistdist/configure.ac index aa1892f4c1f7..f50b90e6e030 100644 --- a/pdns/dnsdistdist/configure.ac +++ b/pdns/dnsdistdist/configure.ac @@ -13,6 +13,9 @@ AC_DEFINE([DNSDIST], [1], [This is dnsdist] ) +# Warn when pkg.m4 is missing +m4_pattern_forbid([^_?PKG_[A-Z_]+$], [*** pkg.m4 missing, please install pkg-config]) + LT_PREREQ([2.2.2]) LT_INIT([disable-static]) @@ -132,6 +135,12 @@ AS_IF([test "x$enable_dns_over_http3" != "xno"], [ ]) ]) +DNSDIST_ENABLE_YAML + +AS_IF([test "x$enable_yaml" != "xno"], [ + PDNS_CHECK_CARGO([1.64]) +]) + DNSDIST_WITH_CDB PDNS_CHECK_LMDB PDNS_ENABLE_IPCIPHER @@ -190,6 +199,8 @@ AS_IF([test "x$PACKAGEVERSION" != "x"], ) AC_CONFIG_FILES([Makefile + dnsdist-rust-lib/Makefile + dnsdist-rust-lib/rust/Makefile ext/arc4random/Makefile ext/yahttp/Makefile ext/yahttp/yahttp/Makefile @@ -305,5 +316,9 @@ AS_IF([test "x$LMDB_LIBS" != "x"], [AC_MSG_NOTICE([lmdb: yes])], [AC_MSG_NOTICE([lmdb: no])] ) +AS_IF([test "x$enable_yaml" != "xno"], + [AC_MSG_NOTICE([YAML configuration: yes])], + [AC_MSG_NOTICE([YAML configuration: no])] +) AC_MSG_NOTICE([]) diff --git a/pdns/dnsdistdist/dnsdist-actions-definitions.yml b/pdns/dnsdistdist/dnsdist-actions-definitions.yml new file mode 100644 index 000000000000..ab66dde80b04 --- /dev/null +++ b/pdns/dnsdistdist/dnsdist-actions-definitions.yml @@ -0,0 +1,512 @@ +--- +- name: "allow" + description: "Let these packets go through" +- name: "continue" + description: "Execute the specified action and override its return with None, making it possible to continue the processing. Subsequent rules are processed after this action" + skip-cpp: true + skip-rust: true + skip-serde: true + parameters: + - name: "action" + type: "Action" + description: "The action to execute" +- name: "delay" + description: "Delay the response by the specified amount of milliseconds (UDP-only). Note that the sending of the query to the backend, if needed, is not delayed. Only the sending of the response to the client will be delayed. Subsequent rules are processed after this action" + parameters: + - name: "msec" + type: "u32" + description: "The amount of milliseconds to delay the response" +- name: "DnstapLog" + skip-cpp: true + skip-rust: true + description: "Send the current query to a remote logger as a dnstap message. ``alter_function`` is a callback, receiving a :class:`DNSQuestion` and a :class:`DnstapMessage`, that can be used to modify the message. Subsequent rules are processed after this action" + parameters: + - name: "identity" + type: "String" + description: "Server identity to store in the dnstap message" + - name: "logger_name" + type: "String" + description: "The name of dnstap logger" + - name: "alter_function_name" + type: "String" + default: "" + description: "The name of the Lua function that will alter the message" + - name: "alter_function_code" + type: "String" + default: "" + description: "The code of the Lua function that will alter the message" + - name: "alter_function_file" + type: "String" + default: "" + description: "The path to a file containing the code of the Lua function that will alter the message" +- name: "drop" + description: "Drop the packet" +- name: "SetEDNSOption" + description: "Add arbitrary EDNS option and data to the query. Any existing EDNS content with the same option code will be overwritten. Subsequent rules are processed after this action" + parameters: + - name: "code" + type: "u32" + description: "The EDNS option number" + - name: "data" + type: "String" + description: "The EDNS0 option raw content" +- name: "ERCode" + description: "Reply immediately by turning the query into a response with the specified EDNS extended rcode" + skip-cpp: true + parameters: + - name: "rcode" + type: "u8" + description: "The RCODE to respond with" + - name: "vars" + type: "ResponseConfig" + default: true + description: "The response options" +- name: "HTTPStatus" + description: "Return an HTTP response with a status code of ``status``. For HTTP redirects, ``body`` should be the redirect URL" + skip-cpp: true + parameters: + - name: "status" + type: "u16" + description: "The HTTP status code to return" + - name: "body" + rust-type: "String" + type: "PacketBuffer" + description: "The body of the HTTP response, or a URL if the status code is a redirect (3xx)" + - name: "content_type" + type: "String" + default: "" + description: "The HTTP Content-Type header to return for a 200 response, ignored otherwise. Default is ``application/dns-message``" + - name: "vars" + type: "ResponseConfig" + default: true + description: "The response options" +- name: "KeyValueStoreLookup" + description: "Does a lookup into the key value store using the key returned by ``lookup_key_name``, and storing the result if any into the tag named ``destination_tag``. The store can be a ``CDB`` or a ``LMDB`` database. The key can be based on the qname, source IP or the value of an existing tag. Subsequent rules are processed after this action. Note that the tag is always created, even if there was no match, but in that case the content is empty" + skip-cpp: true + skip-rust: true + parameters: + - name: "kvs_name" + type: "String" + description: "The name of the KV store" + - name: "lookup_key_name" + type: "String" + description: "The name of the key to use for the lookup" + - name: "destination_tag" + type: "String" + description: "The name of the tag to store the result into" +- name: "KeyValueStoreRangeLookup" + description: "Does a range-based lookup into the key value store using the key returned by ``lookup_key_name``, and storing the result if any into the tag named ``destination_tag``. This assumes that there is a key in network byte order for the last element of the range (for example ``2001:0db8:ffff:ffff:ffff:ffff:ffff:ffff`` for ``2001:db8::/32``) which contains the first element of the range (``2001:0db8:0000:0000:0000:0000:0000:0000``) (optionally followed by any data) as value, also in network byte order, and that there is no overlapping ranges in the database. This requires that the underlying store supports ordered keys, which is true for LMDB but not for CDB" + skip-cpp: true + skip-rust: true + parameters: + - name: "kvs_name" + type: "String" + description: "The name of the KV store" + - name: "lookup_key_name" + type: "String" + description: "The name of the key to use for the lookup" + - name: "destination_tag" + type: "String" + description: "The name of the tag to store the result into" +- name: "log" + description: "Log a line for each query, to the specified file if any, to the console (require verbose) if the empty string is given as filename. +If an empty string is supplied in the file name, the logging is done to stdout, and only in verbose mode by default. This can be changed by setting ``verbose_only`` to ``false``. +When logging to a file, the ``binary`` parameter specifies whether we log in binary form (default) or in textual form. The ``append`` parameter specifies whether we open the file for appending or truncate each time (default). The ``buffered`` parameter specifies whether writes to the file are buffered (default) or not. +Subsequent rules are processed after this action" + parameters: + - name: "file_name" + type: "String" + default: "" + cpp-optional: false + description: "File to log to. Set to an empty string to log to the normal stdout log, this only works when ``-v`` is set on the command line" + - name: "binary" + type: "bool" + default: "true" + cpp-optional: false + description: "Whether to do binary logging" + - name: "append" + type: "bool" + default: "false" + cpp-optional: false + description: "Whether to append to an existing file" + - name: "buffered" + type: "bool" + default: "false" + cpp-optional: false + description: "Whether to use buffered I/O" + - name: "verbose_only" + type: "bool" + default: "true" + cpp-optional: false + description: "Whether to log only in verbose mode when logging to stdout" + - name: "include_timestamp" + type: "bool" + default: "false" + cpp-optional: false + description: "Whether to include a timestamp for every entry" +- name: "lua" + description: "Invoke a Lua function that accepts a :class:`DNSQuestion`. The function should return a :ref:`DNSAction`. If the Lua code fails, ``ServFail`` is returned" + skip-cpp: true + skip-rust: true + parameters: + - name: "function_name" + type: "String" + default: "" + description: "The name of the Lua function" + - name: "function_code" + type: "String" + default: "" + description: "The code of the Lua function" + - name: "function_file" + type: "String" + default: "" + description: "The path to a file containing the code of the Lua function" +- name: "LuaFFI" + description: "Invoke a Lua function that accepts a pointer to a ``dnsdist_ffi_dnsquestion_t`` object, whose bindings are defined in ``dnsdist-lua-ffi-interface.h``. The function should return a :ref:`DNSAction`. If the Lua code fails, ``ServFail`` is returned" + skip-cpp: true + skip-rust: true + parameters: + - name: "function_name" + type: "String" + default: "" + description: "The name of the Lua function" + - name: "function_code" + type: "String" + default: "" + description: "The code of the Lua function" + - name: "function_file" + type: "String" + default: "" + description: "The path to a file containing the code of the Lua function" +- name: "LuaFFIPerThread" + description: "Invoke a Lua function that accepts a pointer to a ``dnsdist_ffi_dnsquestion_t`` object, whose bindings are defined in ``dnsdist-lua-ffi-interface.h``. The function should return a :ref:`DNSAction`. If the Lua code fails, ``ServFail`` is returned. +The function will be invoked in a per-thread Lua state, without access to the global Lua state. All constants (:ref:`DNSQType`, :ref:`DNSRCode`, ...) are available in that per-thread context, as well as all FFI functions. Objects and their bindings that are not usable in a FFI context (:class:`DNSQuestion`, :class:`DNSDistProtoBufMessage`, :class:`PacketCache`, ...) are not available." + parameters: + - name: "code" + type: "String" + description: "The code of the Lua function" +- name: "NegativeAndSOA" + description: "Turn a question into a response, either a ``NXDOMAIN`` or a ``NODATA`` one based on ``nxd``, setting the ``QR`` bit to ``1`` and adding a ``SOA`` record in the additional section" + skip-cpp: true + parameters: + - name: "nxd" + type: "bool" + description: "Whether the answer is a NXDOMAIN (true) or a NODATA (false)" + - name: "zone" + type: "DNSName" + rust-type: "String" + description: "The owner name for the SOA record" + - name: "ttl" + type: "u32" + description: "The TTL of the SOA record" + - name: "mname" + type: "DNSName" + rust-type: "String" + description: "The mname of the SOA record" + - name: "rname" + type: "DNSName" + rust-type: "String" + description: "The rname of the SOA record" + - name: "soa_parameters" + type: "SOAParams" + description: "The fields of the SOA record" + - name: "soa_in_authority" + type: "bool" + default: "false" + description: "Whether the SOA record should be the authority section for a complete NXDOMAIN/NODATA response that works as a cacheable negative response, rather than the RPZ-style response with a purely informational SOA in the additional section. Default is false (SOA in additional section)" + - name: "vars" + type: "ResponseConfig" + default: true + description: "Response options" +- name: "none" + description: "Does nothing. Subsequent rules are processed after this action" +- name: "pool" + description: "Send the packet into the specified pool. If ``stop_processing`` is set to ``false``, subsequent rules will be processed after this action" + parameters: + - name: "pool_name" + type: "String" + description: "The name of the pool" + - name: "stop_processing" + type: "bool" + default: "true" + cpp-optional: false + description: "Whether subsequent rules should be executed after this one" +- name: "QPS" + description: "Drop a packet if it does exceed the ``limit`` queries per second limit. Letting the subsequent rules apply otherwise" + parameters: + - name: "limit" + type: "u32" + description: "The QPS limit" +- name: "QPSPool" + description: "Send the packet into the specified pool only if it does not exceed the ``limit`` queries per second limit. If ``stop-processing`` is set to ``false``, subsequent rules will be processed after this action. Letting the subsequent rules apply otherwise" + parameters: + - name: "limit" + type: "u32" + description: "The QPS limit" + - name: "pool_name" + type: "String" + description: "The name of the pool" + - name: "stop_processing" + type: "bool" + default: "true" + cpp-optional: false + description: "Whether subsequent rules should be executed after this one" +- name: "RCode" + description: "Reply immediately by turning the query into a response with the specified rcode" + skip-cpp: true + parameters: + - name: "rcode" + type: "u8" + description: "The response code" + - name: "vars" + type: "ResponseConfig" + default: true + description: "Response options" +- name: "RemoteLog" + skip-cpp: true + skip-rust: true + description: "Send the current query to a remote logger as a Protocol Buffer message. ``alter_function`` is a callback, receiving a :class:`DNSQuestion` and a :class:`DNSDistProtoBufMessage`, that can be used to modify the message, for example for anonymization purposes. Subsequent rules are processed after this action" + parameters: + - name: "logger_name" + type: "String" + description: "The name of the protocol buffer logger" + - name: "alter_function_name" + type: "String" + default: "" + description: "The name of the Lua function" + - name: "alter_function_code" + type: "String" + default: "" + description: "The code of the Lua function" + - name: "alter_function_file" + type: "String" + default: "" + description: "The path to a file containing the code of the Lua function" + - name: "server_id" + type: "String" + default: "" + description: "Set the Server Identity field" + - name: "ip_encrypt_key" + type: "String" + default: "" + description: "A key, that can be generated via the :func:`makeIPCipherKey` function, to encrypt the IP address of the requestor for anonymization purposes. The encryption is done using ipcrypt for IPv4 and a 128-bit AES ECB operation for IPv6" + - name: "export_tags" + type: "Vec" + default: "" + description: "The comma-separated list of keys of internal tags to export into the ``tags`` Protocol Buffer field, as ``key:value`` strings. Note that a tag with an empty value will be exported as ````, not ``:``. An empty string means that no internal tag will be exported. The special value ``*`` means that all tags will be exported" + - name: "metas" + type: "Vec" + default: true + description: "A list of ``name``=``key`` pairs, for meta-data to be added to Protocol Buffer message" +- name: "SetAdditionalProxyProtocolValue" + description: "Add a Proxy-Protocol Type-Length value to be sent to the server along with this query. It does not replace any existing value with the same type but adds a new value. Be careful that Proxy Protocol values are sent once at the beginning of the TCP connection for TCP and DoT queries. That means that values received on an incoming TCP connection will be inherited by subsequent queries received over the same incoming TCP connection, if any, but values set to a query will not be inherited by subsequent queries. Subsequent rules +are processed after this action" + parameters: + - name: "proxy_type" + type: "u8" + description: "The proxy protocol type" + - name: "value" + type: "String" + description: "The value" +- name: "SetDisableECS" + description: "Disable the sending of ECS to the backend. Subsequent rules are processed after this action" +- name: "SetDisableValidation" + description: "Set the CD bit in the query and let it go through. Subsequent rules are processed after this action" +- name: "SetECS" + description: "Set the ECS prefix and prefix length sent to backends to an arbitrary value. If both IPv4 and IPv6 masks are supplied the IPv4 one will be used for IPv4 clients and the IPv6 one for IPv6 clients. Otherwise the first mask is used for both, and can actually be an IPv6 mask. Subsequent rules are processed after this action" + skip-cpp: true + parameters: + - name: "ipv4" + type: "String" + description: "The IPv4 netmask, for example 192.0.2.1/32" + - name: "ipv6" + type: "String" + default: "" + description: "The IPv6 netmask, if any" + cpp-optional: false +- name: "SetECSOverride" + description: "Whether an existing EDNS Client Subnet value should be overridden (true) or not (false). Subsequent rules are processed after this action" + parameters: + - name: "override_existing" + type: "bool" + description: "Whether to override an existing EDNS Client Subnet value" +- name: "SetECSPrefixLength" + description: "Set the ECS prefix length. Subsequent rules are processed after this action" + parameters: + - name: "ipv4" + type: "u16" + description: "The IPv4 netmask length" + - name: "ipv6" + type: "u16" + description: "The IPv6 netmask length" +- name: "SetExtendedDNSError" + description: "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" + parameters: + - name: "info_code" + type: "u16" + description: "The EDNS Extended DNS Error code" + - name: "extra_text" + type: "String" + default: "" + cpp-optional: false + description: "The optional EDNS Extended DNS Error extra text" +- name: "SetMacAddr" + description: "Add the source MAC address to the query as an EDNS0 option. This action is currently only supported on Linux. Subsequent rules are processed after this action" + parameters: + - name: "code" + type: "u32" + description: "The EDNS option code" +- name: "SetMaxReturnedTTL" + description: "Cap the TTLs of the response to the given maximum, but only after inserting the response into the packet cache with the initial TTL value" + skip-cpp: true + parameters: + - name: "max" + type: "u32" + description: "The TTL cap" +- name: "SetNoRecurse" + description: "Strip RD bit from the question, let it go through. Subsequent rules are processed after this action" +- name: "SetProxyProtocolValues" + description: "Set the Proxy-Protocol Type-Length values to be sent to the server along with this query to values. Subsequent rules are processed after this action" + skip-cpp: true + skip-rust: true + parameters: + - name: "values" + type: "Vec" + default: true + description: "List of proxy protocol values" +- name: "SetSkipCache" + description: "Don’t lookup the cache for this query, don’t store the answer. Subsequent rules are processed after this action." +- name: "SetTag" + description: "Associate a tag named ``tag`` with a value of ``value`` to this query, that will be passed on to the response. This function will overwrite any existing tag value. Subsequent rules are processed after this action" + parameters: + - name: "tag" + type: "String" + description: "The tag name" + - name: "value" + type: "String" + description: "The tag value" +- name: "SetTempFailureCacheTTL" + description: "Set the cache TTL to use for ServFail and Refused replies. TTL is not applied for successful replies. Subsequent rules are processed after this action" + parameters: + - name: "ttl" + type: "u32" + description: "The TTL to use" +- name: "SNMPTrap" + description: "Send an SNMP trap, adding the message string as the query description. Subsequent rules are processed after this action" + parameters: + - name: "reason" + type: "String" + default: "" + cpp-optional: false + description: "The SNMP trap reason" +- name: "Spoof" + description: "Forge a response with the specified IPv4 (for an A query) or IPv6 (for an AAAA) addresses. If you specify multiple addresses, all that match the query type (A, AAAA or ANY) will get spoofed in" + skip-cpp: true + skip-rust: true + parameters: + - name: "ips" + type: "Vec" + description: "List of IP addresses to spoof" + - name: "vars" + type: "ResponseConfig" + default: true + description: "Response options" +- name: "SpoofCNAME" + description: "Forge a response with the specified CNAME value. Please be aware that DNSdist will not chase the target of the CNAME, so it will not be present in the response which might be a problem for stub resolvers that do not know how to follow a CNAME" + skip-cpp: true + skip-rust: true + parameters: + - name: "cname" + type: "String" + description: "The CNAME to use in the response" + - name: "vars" + type: "ResponseConfig" + default: true + description: "Response options" +- name: "SpoofPacket" + description: "Spoof a raw self-generated answer" + skip-cpp: true + skip-rust: true + parameters: + - name: "response" + type: "String" + description: "The DNS packet" + - name: "len" + type: "u32" + description: "The length of the DNS packet" +- name: "SpoofRaw" + description: | + Forge a response with the specified raw bytes as record data + .. code-block:: Lua + + -- select queries for the 'raw.powerdns.com.' name and TXT type, and answer with both a "aaa" "bbbb" and "ccc" TXT record: + addAction(AndRule({QNameRule('raw.powerdns.com.'), QTypeRule(DNSQType.TXT)}), SpoofRawAction({"\003aaa\004bbbb", "\003ccc"})) + -- select queries for the 'raw-srv.powerdns.com.' name and SRV type, and answer with a '0 0 65535 srv.powerdns.com.' SRV record, setting the AA bit to 1 and the TTL to 3600s + addAction(AndRule({QNameRule('raw-srv.powerdns.com.'), QTypeRule(DNSQType.SRV)}), SpoofRawAction("\000\000\000\000\255\255\003srv\008powerdns\003com\000", { aa=true, ttl=3600 })) + -- select reverse queries for '127.0.0.1' and answer with 'localhost' + addAction(AndRule({QNameRule('1.0.0.127.in-addr.arpa.'), QTypeRule(DNSQType.PTR)}), SpoofRawAction("\009localhost\000")) + -- rfc8482: Providing Minimal-Sized Responses to DNS Queries That Have QTYPE=ANY via HINFO of value "rfc8482" + addAction(QTypeRule(DNSQType.ANY), SpoofRawAction("\007rfc\056\052\056\050\000", { typeForAny=DNSQType.HINFO })) + + :func:`DNSName:toDNSString` is convenient for converting names to wire format for passing to ``SpoofRawAction``. + + ``sdig dumpluaraw`` and ``pdnsutil raw-lua-from-content`` from PowerDNS can generate raw answers for you: + + .. code-block:: Shell + + $ pdnsutil raw-lua-from-content SRV '0 0 65535 srv.powerdns.com.' + "\000\000\000\000\255\255\003srv\008powerdns\003com\000" + $ sdig 127.0.0.1 53 open-xchange.com MX recurse dumpluaraw + Reply to question for qname='open-xchange.com.', qtype=MX + Rcode: 0 (No Error), RD: 1, QR: 1, TC: 0, AA: 0, opcode: 0 + 0 open-xchange.com. IN MX "\000c\004mx\049\049\012open\045xchange\003com\000" + 0 open-xchange.com. IN MX "\000\010\003mx\049\012open\045xchange\003com\000" + 0 open-xchange.com. IN MX "\000\020\003mx\050\012open\045xchange\003com\000" + skip-cpp: true + skip-rust: true + parameters: + - name: "answers" + type: "Vec" + description: "A list of DNS record content entries to use in the response" + - name: "qtype_for_any" + type: "String" + default: "" + description: "The type to use for ANY queries" + - name: "vars" + type: "ResponseConfig" + default: true + description: "Response options" +- name: "SpoofSVC" + description: "Forge a response with the specified ``SVC`` record data. If the list contains more than one ``SVC`` parameter, they are all returned, and should have different priorities. The hints provided in the SVC parameters, if any, will also be added as ``A``/``AAAA`` records in the additional section, using the target name present in the parameters as owner name if it’s not empty (root) and the qname instead" + skip-cpp: true + parameters: + - name: "parameters" + type: "Vec" + description: "List of SVC record parameters" + - name: "vars" + type: "ResponseConfig" + default: true + description: "Response options" +- name: "TC" + description: "Create answer to query with the ``TC`` bit set, and the ``RA`` bit set to the value of ``RD`` in the query, to force the client to TCP" +- name: "tee" + description: "Send copy of query to remote, keep stats on responses. If ``add_ecs`` is set to true, EDNS Client Subnet information will be added to the query. If ``add_proxy_protocol`` is set to true, a Proxy Protocol v2 payload will be prepended in front of the query. The payload will contain the protocol the initial query was received over (UDP or TCP), as well as the initial source and destination addresses and ports. If ``lca`` has provided a value like “192.0.2.53”, dnsdist will try binding that address as local address when sending the queries. Subsequent rules are processed after this action" + skip-cpp: true + parameters: + - name: "rca" + type: "ComboAddress" + rust-type: "String" + description: "The address and port of the remote server" + - name: "lca" + type: "ComboAddress" + rust-type: "String" + default: "" + description: "The source address to use to send packets to the remote server" + - name: "add_ecs" + type: "bool" + default: "false" + description: "Whether to add EDNS Client Subnet to the query" + - name: "add_proxy_protocol" + type: "bool" + default: "false" + description: "Whether to add a proxy protocol payload to the query" diff --git a/pdns/dnsdistdist/dnsdist-actions-factory-generated.cc b/pdns/dnsdistdist/dnsdist-actions-factory-generated.cc new file mode 100644 index 000000000000..25f67fcac509 --- /dev/null +++ b/pdns/dnsdistdist/dnsdist-actions-factory-generated.cc @@ -0,0 +1,93 @@ +// !! This file has been generated by dnsdist-rules-generator.py, do not edit by hand!! +std::shared_ptr getAllowAction() +{ + return std::shared_ptr(new AllowAction()); +} +std::shared_ptr getDelayAction(uint32_t msec) +{ + return std::shared_ptr(new DelayAction(msec)); +} +std::shared_ptr getDropAction() +{ + return std::shared_ptr(new DropAction()); +} +std::shared_ptr getSetEDNSOptionAction(uint32_t code, const std::string& data) +{ + return std::shared_ptr(new SetEDNSOptionAction(code, data)); +} +std::shared_ptr getLogAction(const std::string& file_name, bool binary, bool append, bool buffered, bool verbose_only, bool include_timestamp) +{ + return std::shared_ptr(new LogAction(file_name, binary, append, buffered, verbose_only, include_timestamp)); +} +std::shared_ptr getLuaFFIPerThreadAction(const std::string& code) +{ + return std::shared_ptr(new LuaFFIPerThreadAction(code)); +} +std::shared_ptr getNoneAction() +{ + return std::shared_ptr(new NoneAction()); +} +std::shared_ptr getPoolAction(const std::string& pool_name, bool stop_processing) +{ + return std::shared_ptr(new PoolAction(pool_name, stop_processing)); +} +std::shared_ptr getQPSAction(uint32_t limit) +{ + return std::shared_ptr(new QPSAction(limit)); +} +std::shared_ptr getQPSPoolAction(uint32_t limit, const std::string& pool_name, bool stop_processing) +{ + return std::shared_ptr(new QPSPoolAction(limit, pool_name, stop_processing)); +} +std::shared_ptr getSetAdditionalProxyProtocolValueAction(uint8_t proxy_type, const std::string& value) +{ + return std::shared_ptr(new SetAdditionalProxyProtocolValueAction(proxy_type, value)); +} +std::shared_ptr getSetDisableECSAction() +{ + return std::shared_ptr(new SetDisableECSAction()); +} +std::shared_ptr getSetDisableValidationAction() +{ + return std::shared_ptr(new SetDisableValidationAction()); +} +std::shared_ptr getSetECSOverrideAction(bool override_existing) +{ + return std::shared_ptr(new SetECSOverrideAction(override_existing)); +} +std::shared_ptr getSetECSPrefixLengthAction(uint16_t ipv4, uint16_t ipv6) +{ + return std::shared_ptr(new SetECSPrefixLengthAction(ipv4, ipv6)); +} +std::shared_ptr getSetExtendedDNSErrorAction(uint16_t info_code, const std::string& extra_text) +{ + return std::shared_ptr(new SetExtendedDNSErrorAction(info_code, extra_text)); +} +std::shared_ptr getSetMacAddrAction(uint32_t code) +{ + return std::shared_ptr(new SetMacAddrAction(code)); +} +std::shared_ptr getSetNoRecurseAction() +{ + return std::shared_ptr(new SetNoRecurseAction()); +} +std::shared_ptr getSetSkipCacheAction() +{ + return std::shared_ptr(new SetSkipCacheAction()); +} +std::shared_ptr getSetTagAction(const std::string& tag, const std::string& value) +{ + return std::shared_ptr(new SetTagAction(tag, value)); +} +std::shared_ptr getSetTempFailureCacheTTLAction(uint32_t ttl) +{ + return std::shared_ptr(new SetTempFailureCacheTTLAction(ttl)); +} +std::shared_ptr getSNMPTrapAction(const std::string& reason) +{ + return std::shared_ptr(new SNMPTrapAction(reason)); +} +std::shared_ptr getTCAction() +{ + return std::shared_ptr(new TCAction()); +} diff --git a/pdns/dnsdistdist/dnsdist-actions-factory-generated.hh b/pdns/dnsdistdist/dnsdist-actions-factory-generated.hh new file mode 100644 index 000000000000..bf61dba4c502 --- /dev/null +++ b/pdns/dnsdistdist/dnsdist-actions-factory-generated.hh @@ -0,0 +1,24 @@ +// !! This file has been generated by dnsdist-rules-generator.py, do not edit by hand!! +std::shared_ptr getAllowAction(); +std::shared_ptr getDelayAction(uint32_t msec); +std::shared_ptr getDropAction(); +std::shared_ptr getSetEDNSOptionAction(uint32_t code, const std::string& data); +std::shared_ptr getLogAction(const std::string& file_name, bool binary, bool append, bool buffered, bool verbose_only, bool include_timestamp); +std::shared_ptr getLuaFFIPerThreadAction(const std::string& code); +std::shared_ptr getNoneAction(); +std::shared_ptr getPoolAction(const std::string& pool_name, bool stop_processing); +std::shared_ptr getQPSAction(uint32_t limit); +std::shared_ptr getQPSPoolAction(uint32_t limit, const std::string& pool_name, bool stop_processing); +std::shared_ptr getSetAdditionalProxyProtocolValueAction(uint8_t proxy_type, const std::string& value); +std::shared_ptr getSetDisableECSAction(); +std::shared_ptr getSetDisableValidationAction(); +std::shared_ptr getSetECSOverrideAction(bool override_existing); +std::shared_ptr getSetECSPrefixLengthAction(uint16_t ipv4, uint16_t ipv6); +std::shared_ptr getSetExtendedDNSErrorAction(uint16_t info_code, const std::string& extra_text); +std::shared_ptr getSetMacAddrAction(uint32_t code); +std::shared_ptr getSetNoRecurseAction(); +std::shared_ptr getSetSkipCacheAction(); +std::shared_ptr getSetTagAction(const std::string& tag, const std::string& value); +std::shared_ptr getSetTempFailureCacheTTLAction(uint32_t ttl); +std::shared_ptr getSNMPTrapAction(const std::string& reason); +std::shared_ptr getTCAction(); diff --git a/pdns/dnsdistdist/dnsdist-actions-factory.cc b/pdns/dnsdistdist/dnsdist-actions-factory.cc new file mode 100644 index 000000000000..b821f07a0599 --- /dev/null +++ b/pdns/dnsdistdist/dnsdist-actions-factory.cc @@ -0,0 +1,2465 @@ +/* + * 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-actions-factory.hh" + +#include "config.h" +#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-rule-chains.hh" +#include "dnsdist-self-answers.hh" +#include "dnsdist-snmp.hh" + +#include "dnstap.hh" +#include "dnswriter.hh" +#include "ednsoptions.hh" +#include "fstrm_logger.hh" +#include "ipcipher.hh" +#include "remote_logger.hh" +#include "svc-records.hh" +#include "threadname.hh" + +namespace dnsdist::actions +{ +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 std::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 std::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, const dnsdist::ResponseConfig& responseConfig) : + d_responseConfig(responseConfig), 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); + } + +private: + dnsdist::ResponseConfig d_responseConfig; + uint8_t d_rcode; +}; + +class ERCodeAction : public DNSAction +{ +public: + ERCodeAction(uint8_t rcode, dnsdist::ResponseConfig responseConfig) : + d_responseConfig(responseConfig), 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); + } + +private: + dnsdist::ResponseConfig d_responseConfig; + uint8_t d_rcode; +}; + +class SpoofSVCAction : public DNSAction +{ +public: + SpoofSVCAction(const std::vector& parameters, const dnsdist::ResponseConfig& responseConfig) : + d_responseConfig(responseConfig) + { + d_payloads.reserve(parameters.size()); + + for (const auto& param : parameters) { + std::vector payload; + if (!generateSVCPayload(payload, param)) { + throw std::runtime_error("Unable to generate a valid SVC record from the supplied parameters"); + } + + d_payloads.push_back(std::move(payload)); + + for (const auto& hint : param.ipv4hints) { + d_additionals4.insert({param.target, ComboAddress(hint)}); + } + + for (const auto& hint : param.ipv6hints) { + d_additionals6.insert({param.target, ComboAddress(hint)}); + } + } + } + + DNSAction::Action operator()(DNSQuestion* dnsquestion, std::string* ruleresult) const override + { + if (!dnsdist::svc::generateSVCResponse(*dnsquestion, d_payloads, d_additionals4, d_additionals6, d_responseConfig)) { + return Action::None; + } + + return Action::HeaderModify; + } + + [[nodiscard]] std::string toString() const override + { + return "spoof SVC record "; + } + +private: + dnsdist::ResponseConfig d_responseConfig; + std::vector> d_payloads; + std::set> d_additionals4; + std::set> d_additionals6; +}; + +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: + LuaAction(LuaActionFunction 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: + LuaActionFunction d_func; +}; + +class LuaResponseAction : public DNSResponseAction +{ +public: + LuaResponseAction(LuaResponseActionFunction 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: + LuaResponseActionFunction d_func; +}; + +class LuaFFIAction : public DNSAction +{ +public: + LuaFFIAction(LuaActionFFIFunction 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: + LuaActionFFIFunction d_func; +}; + +class LuaFFIPerThreadAction : public DNSAction +{ +public: + 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; + LuaActionFFIFunction 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: + LuaFFIResponseAction(LuaResponseActionFFIFunction 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: + LuaResponseActionFFIFunction d_func; +}; + +class LuaFFIPerThreadResponseAction : public DNSResponseAction +{ +public: + 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; + LuaResponseActionFFIFunction 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; + +class SpoofAction : public DNSAction +{ +public: + SpoofAction(const vector& addrs, const dnsdist::ResponseConfig& responseConfig) : + d_responseConfig(responseConfig), 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(DNSName cname, const dnsdist::ResponseConfig& responseConfig) : + d_responseConfig(responseConfig), d_cname(std::move(cname)) + { + } + + SpoofAction(PacketBuffer rawresponse) : + d_raw(std::move(rawresponse)) + { + } + + SpoofAction(const vector& raws, std::optional typeForAny, const dnsdist::ResponseConfig& responseConfig) : + d_responseConfig(responseConfig), 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.empty()) { + ret += "raw bytes "; + } + else { + for (const auto& addr : d_addrs) { + ret += addr.toString() + " "; + } + } + return ret; + } + +private: + dnsdist::ResponseConfig d_responseConfig; + std::vector d_addrs; + std::unordered_set d_types; + std::vector d_rawResponses; + PacketBuffer d_raw; + DNSName d_cname; + std::optional d_rawTypeForAny; +}; + +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)) { + dnsdist::self_answers::generateAnswerFromRawPacket(*dnsquestion, d_raw); + return Action::HeaderModify; + } + + if (!d_cname.empty()) { + if (dnsdist::self_answers::generateAnswerFromCNAME(*dnsquestion, d_cname, d_responseConfig)) { + return Action::HeaderModify; + } + } + else if (!d_rawResponses.empty()) { + if (dnsdist::self_answers::generateAnswerFromRDataEntries(*dnsquestion, d_rawResponses, d_rawTypeForAny, d_responseConfig)) { + return Action::HeaderModify; + } + } + else { + if (dnsdist::self_answers::generateAnswerFromIPAddresses(*dnsquestion, d_addrs, d_responseConfig)) { + return Action::HeaderModify; + } + } + + return Action::None; +} + +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(), dnsdist::configuration::s_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 || dnsdist::configuration::getCurrentRuntimeConfiguration().d_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 || dnsdist::configuration::getCurrentRuntimeConfiguration().d_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, std::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; + std::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}); + } + } +} + +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); + } + +#ifdef 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; + std::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 && dnsdist::configuration::getImmutableConfiguration().d_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, std::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; + std::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); + } + +#ifdef 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; + std::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; +}; + +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 != nullptr && dnsdist::configuration::getImmutableConfiguration().d_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; +}; + +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; +}; + +#if defined(HAVE_DNS_OVER_HTTPS) || defined(HAVE_DNS_OVER_HTTP3) +class HTTPStatusAction : public DNSAction +{ +public: + HTTPStatusAction(uint16_t code, PacketBuffer body, std::string contentType, const dnsdist::ResponseConfig& responseConfig) : + d_responseConfig(responseConfig), d_body(std::move(body)), d_contentType(std::move(contentType)), d_code(code) + { + } + + DNSAction::Action operator()(DNSQuestion* dnsquestion, std::string* ruleresult) const override + { +#if defined(HAVE_DNS_OVER_HTTPS) + if (dnsquestion->ids.du) { + 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; + } +#endif /* HAVE_DNS_OVER_HTTPS */ +#if defined(HAVE_DNS_OVER_HTTP3) + if (dnsquestion->ids.doh3u) { + dnsquestion->ids.doh3u->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; + } +#endif /* HAVE_DNS_OVER_HTTP3 */ + return Action::None; + } + + [[nodiscard]] std::string toString() const override + { + return "return an HTTP status of " + std::to_string(d_code); + } + +private: + dnsdist::ResponseConfig d_responseConfig; + PacketBuffer d_body; + std::string d_contentType; + int d_code; +}; +#endif /* HAVE_DNS_OVER_HTTPS || HAVE_DNS_OVER_HTTP3 */ + +#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 SetMaxReturnedTTLAction : public DNSAction +{ +public: + SetMaxReturnedTTLAction(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 SetMaxReturnedTTLResponseAction : public DNSResponseAction +{ +public: + SetMaxReturnedTTLResponseAction(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: + NegativeAndSOAAction(bool nxd, DNSName zone, uint32_t ttl, DNSName mname, DNSName rname, dnsdist::actions::SOAParams params, bool soaInAuthoritySection, dnsdist::ResponseConfig responseConfig) : + d_responseConfig(responseConfig), 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"; + } + +private: + dnsdist::ResponseConfig d_responseConfig; + DNSName d_zone; + DNSName d_mname; + DNSName d_rname; + uint32_t d_ttl; + dnsdist::actions::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; +}; + +class LimitTTLResponseAction : public DNSResponseAction, public boost::noncopyable +{ +public: + LimitTTLResponseAction(uint32_t min, uint32_t max = std::numeric_limits::max(), std::unordered_set types = {}) : + d_types(std::move(types)), d_min(min), d_max(max) + { + } + + DNSResponseAction::Action operator()(DNSResponse* dnsResponse, std::string* ruleresult) const override + { + dnsdist::PacketMangling::restrictDNSPacketTTLs(dnsResponse->getMutableData(), d_min, d_max, d_types); + 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()}; +}; + +std::shared_ptr getLuaAction(dnsdist::actions::LuaActionFunction function) +{ + return std::shared_ptr(new LuaAction(std::move(function))); +} + +std::shared_ptr getLuaFFIAction(dnsdist::actions::LuaActionFFIFunction function) +{ + return std::shared_ptr(new LuaFFIAction(std::move(function))); +} + +std::shared_ptr getLuaResponseAction(dnsdist::actions::LuaResponseActionFunction function) +{ + return std::shared_ptr(new LuaResponseAction(std::move(function))); +} + +std::shared_ptr getLuaFFIResponseAction(dnsdist::actions::LuaResponseActionFFIFunction function) +{ + return std::shared_ptr(new LuaFFIResponseAction(std::move(function))); +} + +#ifndef DISABLE_PROTOBUF +std::shared_ptr getRemoteLogAction(RemoteLogActionConfiguration& config) +{ + return std::shared_ptr(new RemoteLogAction(config)); +} + +std::shared_ptr getRemoteLogResponseAction(RemoteLogActionConfiguration& config) +{ + return std::shared_ptr(new RemoteLogResponseAction(config)); +} + +std::shared_ptr getDnstapLogAction(const std::string& identity, std::shared_ptr logger, std::optional alterFunc) +{ + return std::shared_ptr(new DnstapLogAction(identity, logger, std::move(alterFunc))); +} + +std::shared_ptr getDnstapLogResponseAction(const std::string& identity, std::shared_ptr logger, std::optional alterFunc) +{ + return std::shared_ptr(new DnstapLogResponseAction(identity, logger, std::move(alterFunc))); +} +#endif /* DISABLE_PROTOBUF */ + +#if defined(HAVE_LMDB) || defined(HAVE_CDB) +std::shared_ptr getKeyValueStoreLookupAction(std::shared_ptr& kvs, std::shared_ptr& lookupKey, const std::string& destinationTag) +{ + return std::shared_ptr(new KeyValueStoreLookupAction(kvs, lookupKey, destinationTag)); +} + +std::shared_ptr getKeyValueStoreRangeLookupAction(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) */ + +#ifdef HAVE_DNS_OVER_HTTPS +std::shared_ptr getHTTPStatusAction(uint16_t status, PacketBuffer&& body, const std::string& contentType, const dnsdist::ResponseConfig& responseConfig) +{ + return std::shared_ptr(new HTTPStatusAction(status, std::move(body), contentType, responseConfig)); +} + +#endif + +std::shared_ptr getLimitTTLResponseAction(uint32_t min, uint32_t max, std::unordered_set types) +{ + return std::shared_ptr(new LimitTTLResponseAction(min, max, std::move(types))); +} + +std::shared_ptr getMinTTLResponseAction(uint32_t min) +{ + return std::shared_ptr(new LimitTTLResponseAction(min)); +} + +std::shared_ptr getClearRecordTypesResponseAction(std::unordered_set types) +{ + return std::shared_ptr(new ClearRecordTypesResponseAction(std::move(types))); +} + +std::shared_ptr getContinueAction(std::shared_ptr action) +{ + return std::shared_ptr(new ContinueAction(action)); +} + +std::shared_ptr getNegativeAndSOAAction(bool nxd, const DNSName& zone, uint32_t ttl, const DNSName& mname, const DNSName& rname, const SOAParams& params, bool soaInAuthority, dnsdist::ResponseConfig responseConfig) +{ + return std::shared_ptr(new NegativeAndSOAAction(nxd, zone, ttl, mname, rname, params, soaInAuthority, responseConfig)); +} + +std::shared_ptr getRCodeAction(uint8_t rcode, const dnsdist::ResponseConfig& responseConfig) +{ + return std::shared_ptr(new RCodeAction(rcode, responseConfig)); +} + +std::shared_ptr getERCodeAction(uint8_t rcode, const dnsdist::ResponseConfig& responseConfig) +{ + return std::shared_ptr(new ERCodeAction(rcode, responseConfig)); +} + +std::shared_ptr getSetECSAction(const std::string& ipv4) +{ + return std::shared_ptr(new SetECSAction(Netmask(ipv4))); +} + +std::shared_ptr getSetECSAction(const std::string& ipv4, const std::string& ipv6) +{ + return std::shared_ptr(new SetECSAction(Netmask(ipv4), Netmask(ipv6))); +} + +std::shared_ptr getSpoofAction(const std::vector& addresses, const dnsdist::ResponseConfig& config) +{ + return std::shared_ptr(new SpoofAction(addresses, config)); +} + +std::shared_ptr getSpoofAction(const std::vector& rawRDatas, std::optional qtypeForAny, const dnsdist::ResponseConfig& config) +{ + return std::shared_ptr(new SpoofAction(rawRDatas, qtypeForAny, config)); +} + +std::shared_ptr getSpoofAction(const DNSName& cname, const dnsdist::ResponseConfig& config) +{ + return std::shared_ptr(new SpoofAction(cname, config)); +} + +std::shared_ptr getSpoofAction(const PacketBuffer& packet) +{ + return std::shared_ptr(new SpoofAction(packet)); +} + +std::shared_ptr getSpoofSVCAction(const std::vector& parameters, const dnsdist::ResponseConfig& responseConfig) +{ + return std::shared_ptr(new SpoofSVCAction(parameters, responseConfig)); +} + +std::shared_ptr getSetMaxReturnedTTLAction(uint32_t max) +{ + return std::shared_ptr(new SetMaxReturnedTTLAction(max)); +} + +std::shared_ptr getSetMaxReturnedTTLResponseAction(uint32_t max) +{ + return std::shared_ptr(new SetMaxReturnedTTLResponseAction(max)); +} + +std::shared_ptr getSetMaxTTLResponseAction(uint32_t max) +{ + return std::shared_ptr(new LimitTTLResponseAction(0, max)); +} + +std::shared_ptr getSetProxyProtocolValuesAction(const std::vector>& values) +{ + return std::shared_ptr(new SetProxyProtocolValuesAction(values)); +} + +std::shared_ptr getTeeAction(const ComboAddress& rca, std::optional lca, bool addECS, bool addProxyProtocol) +{ + return std::shared_ptr(new TeeAction(rca, lca, addECS, addProxyProtocol)); +} + +// NOLINTNEXTLINE(bugprone-suspicious-include) +#include "dnsdist-actions-factory-generated.cc" +// NOLINTNEXTLINE(bugprone-suspicious-include) +#include "dnsdist-response-actions-factory-generated.cc" +} diff --git a/pdns/dnsdistdist/dnsdist-actions-factory.hh b/pdns/dnsdistdist/dnsdist-actions-factory.hh new file mode 100644 index 000000000000..8aa98eee1338 --- /dev/null +++ b/pdns/dnsdistdist/dnsdist-actions-factory.hh @@ -0,0 +1,126 @@ +/* + * 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 + +struct DNSQuestion; +struct DNSResponse; + +#include "dnsdist-actions.hh" +#include "dnsdist-protobuf.hh" +#include "dnsdist-svc.hh" +#include "dnstap.hh" +#include "iputils.hh" +#include "noinitvector.hh" + +struct dnsdist_ffi_dnsquestion_t; +struct dnsdist_ffi_dnsresponse_t; +class RemoteLoggerInterface; +class KeyValueStore; +class KeyValueLookupKey; + +namespace dnsdist::actions +{ +using LuaActionFunction = std::function>(DNSQuestion* dnsquestion)>; +using LuaResponseActionFunction = std::function>(DNSResponse* response)>; +using LuaActionFFIFunction = std::function; +using LuaResponseActionFFIFunction = std::function; + +struct SOAParams +{ + uint32_t serial; + uint32_t refresh; + uint32_t retry; + uint32_t expire; + uint32_t minimum; +}; + +#include "dnsdist-actions-factory-generated.hh" +#include "dnsdist-response-actions-factory-generated.hh" + +std::shared_ptr getLuaAction(dnsdist::actions::LuaActionFunction function); +std::shared_ptr getLuaFFIAction(dnsdist::actions::LuaActionFFIFunction function); +std::shared_ptr getLuaResponseAction(dnsdist::actions::LuaResponseActionFunction function); +std::shared_ptr getLuaFFIResponseAction(dnsdist::actions::LuaResponseActionFFIFunction function); + +std::shared_ptr getContinueAction(std::shared_ptr action); +#ifdef HAVE_DNS_OVER_HTTPS +std::shared_ptr getHTTPStatusAction(uint16_t status, PacketBuffer&& body, const std::string& contentType, const dnsdist::ResponseConfig& responseConfig); +#endif +std::shared_ptr getNegativeAndSOAAction(bool nxd, const DNSName& zone, uint32_t ttl, const DNSName& mname, const DNSName& rname, const SOAParams& params, bool soaInAuthority, dnsdist::ResponseConfig responseConfig); +std::shared_ptr getSetProxyProtocolValuesAction(const std::vector>& values); +std::shared_ptr getRCodeAction(uint8_t rcode, const dnsdist::ResponseConfig& responseConfig); +std::shared_ptr getERCodeAction(uint8_t rcode, const dnsdist::ResponseConfig& responseConfig); + +#if defined(HAVE_LMDB) || defined(HAVE_CDB) +std::shared_ptr getKeyValueStoreLookupAction(std::shared_ptr& kvs, std::shared_ptr& lookupKey, const std::string& destinationTag); +std::shared_ptr getKeyValueStoreRangeLookupAction(std::shared_ptr& kvs, std::shared_ptr& lookupKey, const std::string& destinationTag); +#endif /* defined(HAVE_LMDB) || defined(HAVE_CDB) */ + +std::shared_ptr getSetECSAction(const std::string& ipv4); +std::shared_ptr getSetECSAction(const std::string& ipv4, const std::string& ipv6); +std::shared_ptr getSpoofAction(const std::vector& addresses, const dnsdist::ResponseConfig& config); +std::shared_ptr getSpoofAction(const std::vector& rawRDatas, std::optional qtypeForAny, const dnsdist::ResponseConfig& config); +std::shared_ptr getSpoofAction(const DNSName& cname, const dnsdist::ResponseConfig& config); +std::shared_ptr getSpoofAction(const PacketBuffer& packet); + +std::shared_ptr getSpoofSVCAction(const std::vector& parameters, const dnsdist::ResponseConfig& responseConfig); + +std::shared_ptr getSetMaxReturnedTTLAction(uint32_t max); +std::shared_ptr getLimitTTLResponseAction(uint32_t min, uint32_t max = std::numeric_limits::max(), std::unordered_set types = {}); +std::shared_ptr getMinTTLResponseAction(uint32_t min); +std::shared_ptr getSetMaxReturnedTTLResponseAction(uint32_t max); +std::shared_ptr getSetMaxTTLResponseAction(uint32_t max); + +std::shared_ptr getClearRecordTypesResponseAction(std::unordered_set types); + +std::shared_ptr getTeeAction(const ComboAddress& rca, std::optional lca, bool addECS, bool addProxyProtocol); + +#ifndef DISABLE_PROTOBUF +using ProtobufAlterFunction = std::function; +using ProtobufAlterResponseFunction = std::function; +using DnstapAlterFunction = std::function; +using DnstapAlterResponseFunction = std::function; + +struct RemoteLogActionConfiguration +{ + std::vector> metas; + std::optional> tagsToExport{std::nullopt}; + std::optional alterQueryFunc; + std::optional alterResponseFunc; + std::shared_ptr logger; + std::string serverID; + std::string ipEncryptKey; + std::optional exportExtendedErrorsToMeta{std::nullopt}; + bool includeCNAME{false}; +}; +std::shared_ptr getRemoteLogAction(RemoteLogActionConfiguration& config); +std::shared_ptr getRemoteLogResponseAction(RemoteLogActionConfiguration& config); +std::shared_ptr getDnstapLogAction(const std::string& identity, std::shared_ptr logger, std::optional alterFunc); +std::shared_ptr getDnstapLogResponseAction(const std::string& identity, std::shared_ptr logger, std::optional alterFunc); +#endif /* DISABLE_PROTOBUF */ +} diff --git a/pdns/dnsdistdist/dnsdist-actions.cc b/pdns/dnsdistdist/dnsdist-actions.cc new file mode 100644 index 000000000000..ad19901f2a6c --- /dev/null +++ b/pdns/dnsdistdist/dnsdist-actions.cc @@ -0,0 +1,93 @@ +/* + * 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-actions.hh" + +#include + +DNSAction::Action DNSAction::typeFromString(const std::string& str) +{ + static const std::unordered_map s_mappings{ + {"allow", Action::Allow}, + {"delay", Action::Delay}, + {"drop", Action::Drop}, + {"headermodify", Action::HeaderModify}, + {"none", Action::None}, + {"noop", Action::NoOp}, + {"norecurse", Action::NoRecurse}, + {"nxdomain", Action::Nxdomain}, + {"pool", Action::Pool}, + {"refused", Action::Refused}, + {"servfail", Action::ServFail}, + {"settag", Action::SetTag}, + {"spoof", Action::Spoof}, + {"spoofpacket", Action::SpoofPacket}, + {"spoofraw", Action::SpoofRaw}, + {"truncate", Action::Truncate}, + }; + + auto lower = boost::to_lower_copy(str); + boost::replace_all(lower, "-", ""); + auto mappingIt = s_mappings.find(lower); + if (mappingIt != s_mappings.end()) { + return mappingIt->second; + } + throw std::runtime_error("Unable to convert '" + str + "' into a DNS Action"); +} + +std::string DNSAction::typeToString(DNSAction::Action action) +{ + switch (action) { + case Action::Drop: + return "Drop"; + case Action::Nxdomain: + return "Send NXDomain"; + case Action::Refused: + return "Send Refused"; + case Action::Spoof: + return "Spoof an answer"; + case Action::SpoofPacket: + return "Spoof a raw answer from bytes"; + case Action::SpoofRaw: + return "Spoof an answer from raw bytes"; + case Action::Allow: + return "Allow"; + case Action::HeaderModify: + return "Modify the header"; + case Action::Pool: + return "Route to a pool"; + case Action::Delay: + return "Delay"; + case Action::Truncate: + return "Truncate over UDP"; + case Action::ServFail: + return "Send ServFail"; + case Action::SetTag: + return "Set Tag"; + case Action::None: + case Action::NoOp: + return "Do nothing"; + case Action::NoRecurse: + return "Set rd=0"; + } + + return "Unknown"; +} diff --git a/pdns/dnsdistdist/dnsdist-actions.hh b/pdns/dnsdistdist/dnsdist-actions.hh index 5bb08a0c6eac..30f7ec187a5a 100644 --- a/pdns/dnsdistdist/dnsdist-actions.hh +++ b/pdns/dnsdistdist/dnsdist-actions.hh @@ -21,6 +21,10 @@ */ #pragma once +#include +#include +#include + /* so what could you do: drop, fake up nxdomain, @@ -55,44 +59,8 @@ public: SpoofPacket, SetTag, }; - static std::string typeToString(const Action& action) - { - switch (action) { - case Action::Drop: - return "Drop"; - case Action::Nxdomain: - return "Send NXDomain"; - case Action::Refused: - return "Send Refused"; - case Action::Spoof: - return "Spoof an answer"; - case Action::SpoofPacket: - return "Spoof a raw answer from bytes"; - case Action::SpoofRaw: - return "Spoof an answer from raw bytes"; - case Action::Allow: - return "Allow"; - case Action::HeaderModify: - return "Modify the header"; - case Action::Pool: - return "Route to a pool"; - case Action::Delay: - return "Delay"; - case Action::Truncate: - return "Truncate over UDP"; - case Action::ServFail: - return "Send ServFail"; - case Action::SetTag: - return "Set Tag"; - case Action::None: - case Action::NoOp: - return "Do nothing"; - case Action::NoRecurse: - return "Set rd=0"; - } - - return "Unknown"; - } + static Action typeFromString(const std::string& str); + static std::string typeToString(Action action); virtual Action operator()(DNSQuestion*, std::string* ruleresult) const = 0; virtual ~DNSAction() = default; diff --git a/pdns/dnsdistdist/dnsdist-backend.cc b/pdns/dnsdistdist/dnsdist-backend.cc index d493d3f9aa1f..1907f797330f 100644 --- a/pdns/dnsdistdist/dnsdist-backend.cc +++ b/pdns/dnsdistdist/dnsdist-backend.cc @@ -19,6 +19,11 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ + +// for OpenBSD, sys/socket.h needs to come before net/if.h +#include +#include + #include #include "config.h" @@ -867,7 +872,7 @@ void DownstreamState::submitHealthCheckResult(bool initial, bool newResult) } setUpStatus(newState); - if (g_snmpAgent != nullptr && dnsdist::configuration::getCurrentRuntimeConfiguration().d_snmpTrapsEnabled) { + if (g_snmpAgent != nullptr && dnsdist::configuration::getImmutableConfiguration().d_snmpTrapsEnabled) { g_snmpAgent->sendBackendStatusChangeTrap(*this); } } @@ -916,6 +921,71 @@ void DownstreamState::registerXsk(std::vector>& xsks) } #endif /* HAVE_XSK */ +bool DownstreamState::parseSourceParameter(const std::string& source, DownstreamState::Config& config) +{ + /* 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") + */ + 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); + return true; + } + catch (...) { + } + } + + /* 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 + if (!dnsdist::configuration::isImmutableConfigurationDone()) { + /* we need to retain CAP_NET_RAW to be able to set SO_BINDTODEVICE in the health checks */ + dnsdist::configuration::updateImmutableConfiguration([](dnsdist::configuration::ImmutableConfiguration& currentConfig) { + currentConfig.d_capabilitiesToRetain.insert("CAP_NET_RAW"); + }); + } +#endif + return true; + } + + warnlog("Dismissing source %s because '%s' is not a valid interface name", source, config.sourceItfName); + return false; +} + +std::optional DownstreamState::getAvailabilityFromStr(const std::string& mode) +{ + if (pdns_iequals(mode, "auto")) { + return DownstreamState::Availability::Auto; + } + if (pdns_iequals(mode, "lazy")) { + return DownstreamState::Availability::Lazy; + } + if (pdns_iequals(mode, "up")) { + return DownstreamState::Availability::Up; + } + if (pdns_iequals(mode, "down")) { + return DownstreamState::Availability::Down; + } + return std::nullopt; +} + size_t ServerPool::countServers(bool upOnly) { std::shared_ptr servers = nullptr; diff --git a/pdns/dnsdistdist/dnsdist-configuration-yaml-internal.hh b/pdns/dnsdistdist/dnsdist-configuration-yaml-internal.hh new file mode 100644 index 000000000000..bbae2cd7a677 --- /dev/null +++ b/pdns/dnsdistdist/dnsdist-configuration-yaml-internal.hh @@ -0,0 +1,32 @@ +/* + * 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-configuration.hh" + +namespace dnsdist::configuration::yaml +{ +#if defined(HAVE_YAML_CONFIGURATION) +void convertImmutableFlatSettingsFromRust(const dnsdist::rust::settings::GlobalConfiguration& yamlConfig, dnsdist::configuration::ImmutableConfiguration& config); +void convertRuntimeFlatSettingsFromRust(const dnsdist::rust::settings::GlobalConfiguration& yamlConfig, dnsdist::configuration::RuntimeConfiguration& config); +#endif /* HAVE_YAML_CONFIGURATION */ +} diff --git a/pdns/dnsdistdist/dnsdist-configuration-yaml.cc b/pdns/dnsdistdist/dnsdist-configuration-yaml.cc new file mode 100644 index 000000000000..3719c2dfa932 --- /dev/null +++ b/pdns/dnsdistdist/dnsdist-configuration-yaml.cc @@ -0,0 +1,1664 @@ +/* + * 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-configuration-yaml.hh" + +#if defined(HAVE_YAML_CONFIGURATION) +#include "base64.hh" +#include "dolog.hh" +#include "dnscrypt.hh" +#include "dnsdist-actions-factory.hh" +#include "dnsdist-backend.hh" +#include "dnsdist-cache.hh" +#include "dnsdist-discovery.hh" +#include "dnsdist-dnsparser.hh" +#include "dnsdist-dynblocks.hh" +#include "dnsdist-rules.hh" +#include "dnsdist-rules-factory.hh" +#include "dnsdist-kvs.hh" +#include "dnsdist-web.hh" +#include "dnsdist-xsk.hh" +#include "doh.hh" +#include "fstrm_logger.hh" +#include "iputils.hh" +#include "remote_logger.hh" +#include "xsk.hh" + +#include "rust/cxx.h" +#include "rust/lib.rs.h" +#include "dnsdist-configuration-yaml-internal.hh" + +#include +#endif /* HAVE_YAML_CONFIGURATION */ + +namespace dnsdist::configuration::yaml +{ +#if defined(HAVE_YAML_CONFIGURATION) + +using XSKMap = std::vector>; + +using RegisteredTypes = std::variant, std::shared_ptr, std::shared_ptr, std::shared_ptr, std::shared_ptr, std::shared_ptr, std::shared_ptr, std::shared_ptr, std::shared_ptr, std::shared_ptr>; +static LockGuarded> s_registeredTypesMap; +static std::atomic s_inConfigCheckMode; +static std::atomic s_inClientMode; + +template +static void registerType(const std::shared_ptr& entry, const ::rust::string& rustName) +{ + std::string name(rustName); + if (name.empty()) { + auto uuid = getUniqueID(); + name = boost::uuids::to_string(uuid); + } + + auto [it, inserted] = s_registeredTypesMap.lock()->try_emplace(name, entry); + if (!inserted) { + throw std::runtime_error("Trying to register a type named '" + name + "' while one already exists"); + } +} + +template +static std::shared_ptr getRegisteredTypeByName(const std::string& name) +{ + auto map = s_registeredTypesMap.lock(); + auto item = map->find(name); + if (item == map->end()) { + return nullptr; + } + if (auto* ptr = std::get_if>(&item->second)) { + return *ptr; + } + return nullptr; +} + +template +static std::shared_ptr getRegisteredTypeByName(const ::rust::String& name) +{ + auto nameStr = std::string(name); + return getRegisteredTypeByName(nameStr); +} + +template +static T checkedConversionFromStr(const std::string& context, const std::string& parameterName, const std::string& str) +{ + try { + return pdns::checked_stoi(std::string(str)); + } + catch (const std::exception& exp) { + throw std::runtime_error("Error converting value '" + str + "' for parameter '" + parameterName + "' in YAML directive '" + context + "': " + exp.what()); + } +} + +template +static T checkedConversionFromStr(const std::string& context, const std::string& parameterName, const ::rust::string& str) +{ + return checkedConversionFromStr(context, parameterName, std::string(str)); +} + +template +static bool getOptionalLuaFunction(T& destination, const ::rust::string& functionName) +{ + auto lua = g_lua.lock(); + auto function = lua->readVariable>(std::string(functionName)); + if (!function) { + return false; + } + destination = *function; + return true; +} + +static std::optional loadContentFromConfigurationFile(const std::string& fileName) +{ + /* no check on the file size, don't do this with just any file! */ + auto file = std::ifstream(fileName); + if (!file.is_open()) { + return std::nullopt; + } + return std::string(std::istreambuf_iterator(file), {}); +} + +template +static bool getLuaFunctionFromConfiguration(FuncType& destination, const ::rust::string& functionName, const ::rust::string& functionCode, const ::rust::string& functionFile, const std::string& context) +{ + if (!functionName.empty()) { + return getOptionalLuaFunction(destination, functionName); + } + if (!functionCode.empty()) { + auto function = dnsdist::lua::getFunctionFromLuaCode(std::string(functionCode), context); + if (function) { + destination = *function; + return true; + } + throw std::runtime_error("Unable to load a Lua function from the content of lua directive in " + context + " context"); + } + if (!functionFile.empty()) { + auto content = loadContentFromConfigurationFile(std::string(functionFile)); + if (!content) { + throw std::runtime_error("Unable to load content of lua-file's '" + std::string(functionFile) + "' in " + context + " context"); + } + auto function = dnsdist::lua::getFunctionFromLuaCode(*content, context); + if (function) { + destination = *function; + return true; + } + throw std::runtime_error("Unable to load a Lua function from the content of lua-file's '" + std::string(functionFile) + "' in " + context + " context"); + } + return false; +} + +static std::set getCPUPiningFromStr(const std::string& context, const std::string& cpuStr) +{ + std::set cpus; + std::vector tokens; + stringtok(tokens, cpuStr); + for (const auto& token : tokens) { + cpus.insert(checkedConversionFromStr(context, "cpus", token)); + } + return cpus; +} + +static TLSConfig getTLSConfigFromRustIncomingTLS(const dnsdist::rust::settings::IncomingTlsConfiguration& incomingTLSConfig) +{ + TLSConfig out; + for (const auto& certConfig : incomingTLSConfig.certificates) { + TLSCertKeyPair pair(std::string(certConfig.certificate)); + if (!certConfig.key.empty()) { + pair.d_key = std::string(certConfig.key); + } + if (!certConfig.password.empty()) { + pair.d_password = std::string(certConfig.password); + } + out.d_certKeyPairs.push_back(std::move(pair)); + } + for (const auto& ocspFile : incomingTLSConfig.ocsp_response_files) { + out.d_ocspFiles.emplace_back(ocspFile); + } + out.d_ciphers = std::string(incomingTLSConfig.ciphers); + out.d_ciphers13 = std::string(incomingTLSConfig.ciphers_tls_13); + out.d_minTLSVersion = libssl_tls_version_from_string(std::string(incomingTLSConfig.minimum_version)); + out.d_ticketKeyFile = std::string(incomingTLSConfig.ticket_key_file); + out.d_keyLogFile = std::string(incomingTLSConfig.key_log_file); + out.d_maxStoredSessions = incomingTLSConfig.number_of_stored_sessions; + out.d_sessionTimeout = incomingTLSConfig.session_timeout; + out.d_ticketsKeyRotationDelay = incomingTLSConfig.tickets_keys_rotation_delay; + out.d_numberOfTicketsKeys = incomingTLSConfig.number_of_tickets_keys; + out.d_preferServerCiphers = incomingTLSConfig.prefer_server_ciphers; + out.d_enableTickets = incomingTLSConfig.session_tickets; + out.d_releaseBuffers = incomingTLSConfig.release_buffers; + out.d_enableRenegotiation = incomingTLSConfig.enable_renegotiation; + out.d_asyncMode = incomingTLSConfig.async_mode; + out.d_ktls = incomingTLSConfig.ktls; + out.d_readAhead = incomingTLSConfig.read_ahead; + return out; +} + +static bool validateTLSConfiguration(const dnsdist::rust::settings::BindConfiguration& bind, const TLSConfig& tlsConfig) +{ + if (!bind.tls.ignore_configuration_errors) { + return true; + } + + // 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(tlsConfig, ocspResponses); + } + catch (const std::runtime_error& e) { + errlog("Ignoring %s frontend: '%s'", bind.protocol, e.what()); + return false; + } + + return true; +} + +static bool handleTLSConfiguration(const dnsdist::rust::settings::BindConfiguration& bind, ClientState& state) +{ + auto tlsConfig = getTLSConfigFromRustIncomingTLS(bind.tls); + if (!validateTLSConfiguration(bind, tlsConfig)) { + return false; + } + + auto protocol = boost::to_lower_copy(std::string(bind.protocol)); + if (protocol == "dot") { + auto frontend = std::make_shared(TLSFrontend::ALPN::DoT); + frontend->d_provider = std::string(bind.tls.provider); + boost::algorithm::to_lower(frontend->d_provider); + frontend->d_proxyProtocolOutsideTLS = bind.tls.proxy_protocol_outside_tls; + frontend->d_tlsConfig = std::move(tlsConfig); + state.tlsFrontend = std::move(frontend); + } + else if (protocol == "doq") { + auto frontend = std::make_shared(); + frontend->d_local = ComboAddress(std::string(bind.listen_address), 853); + frontend->d_quicheParams.d_tlsConfig = std::move(tlsConfig); + frontend->d_quicheParams.d_maxInFlight = bind.doq.max_concurrent_queries_per_connection; + frontend->d_quicheParams.d_idleTimeout = bind.quic.idle_timeout; + frontend->d_quicheParams.d_keyLogFile = std::string(bind.tls.key_log_file); + if (dnsdist::doq::s_available_cc_algorithms.count(std::string(bind.quic.congestion_control_algorithm)) > 0) { + frontend->d_quicheParams.d_ccAlgo = std::string(bind.quic.congestion_control_algorithm); + } + frontend->d_internalPipeBufferSize = bind.quic.internal_pipe_buffer_size; + state.doqFrontend = std::move(frontend); + } + else if (protocol == "doh3") { + auto frontend = std::make_shared(); + frontend->d_local = ComboAddress(std::string(bind.listen_address), 443); + frontend->d_quicheParams.d_tlsConfig = std::move(tlsConfig); + frontend->d_quicheParams.d_idleTimeout = bind.quic.idle_timeout; + frontend->d_quicheParams.d_keyLogFile = std::string(bind.tls.key_log_file); + if (dnsdist::doq::s_available_cc_algorithms.count(std::string(bind.quic.congestion_control_algorithm)) > 0) { + frontend->d_quicheParams.d_ccAlgo = std::string(bind.quic.congestion_control_algorithm); + } + frontend->d_internalPipeBufferSize = bind.quic.internal_pipe_buffer_size; + state.doh3Frontend = std::move(frontend); + } + else if (protocol == "doh") { + auto frontend = std::make_shared(); + frontend->d_tlsContext.d_provider = std::string(bind.tls.provider); + boost::algorithm::to_lower(frontend->d_tlsContext.d_provider); + frontend->d_library = std::string(bind.doh.provider); + 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", bind.listen_address); + return false; +#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", bind.listen_address); + return false; +#endif /* HAVE_NGHTTP2 */ + } + else { + errlog("DOH bind %s is configured to use an unknown library ('%s')", bind.listen_address, frontend->d_library); + return false; + } + + for (const auto& path : bind.doh.paths) { + frontend->d_urls.emplace(path); + } + frontend->d_idleTimeout = bind.doh.idle_timeout; + frontend->d_serverTokens = std::string(bind.doh.server_tokens); + frontend->d_sendCacheControlHeaders = bind.doh.send_cache_control_headers; + frontend->d_keepIncomingHeaders = bind.doh.keep_incoming_headers; + frontend->d_trustForwardedForHeader = bind.doh.trust_forwarded_for_header; + frontend->d_earlyACLDrop = bind.doh.early_acl_drop; + frontend->d_internalPipeBufferSize = bind.doh.internal_pipe_buffer_size; + frontend->d_exactPathMatching = bind.doh.exact_path_matching; + for (const auto& customHeader : bind.doh.custom_response_headers) { + auto headerResponse = std::pair(boost::to_lower_copy(std::string(customHeader.key)), std::string(customHeader.value)); + frontend->d_customResponseHeaders.insert(std::move(headerResponse)); + } + + if (!bind.doh.responses_map.empty()) { + auto newMap = std::make_shared>>(); + for (const auto& responsesMap : bind.doh.responses_map) { + boost::optional> headers; + if (!responsesMap.headers.empty()) { + headers = std::unordered_map(); + for (const auto& header : responsesMap.headers) { + headers->emplace(boost::to_lower_copy(std::string(header.key)), std::string(header.value)); + } + } + auto entry = std::make_shared(std::string(responsesMap.expression), responsesMap.status, PacketBuffer(responsesMap.content.begin(), responsesMap.content.end()), headers); + newMap->emplace_back(std::move(entry)); + } + frontend->d_responsesMap = std::move(newMap); + } + + if (!tlsConfig.d_certKeyPairs.empty()) { + frontend->d_tlsContext.d_addr = ComboAddress(std::string(bind.listen_address), 443); + infolog("DNS over HTTPS configured"); + } + else { + frontend->d_tlsContext.d_addr = ComboAddress(std::string(bind.listen_address), 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()); + } + + frontend->d_tlsContext.d_proxyProtocolOutsideTLS = bind.tls.proxy_protocol_outside_tls; + frontend->d_tlsContext.d_tlsConfig = std::move(tlsConfig); + state.dohFrontend = std::move(frontend); + } + else if (protocol != "do53") { + errlog("Bind %s is configured to use an unknown protocol ('%s')", bind.listen_address, protocol); + return false; + } + + return true; +} + +static std::shared_ptr createBackendFromConfiguration(const dnsdist::rust::settings::BackendConfiguration& config, bool configCheck) +{ + DownstreamState::Config backendConfig; + std::shared_ptr tlsCtx; + + backendConfig.d_numberOfSockets = config.sockets; + backendConfig.d_qpsLimit = config.queries_per_second; + backendConfig.order = config.order; + backendConfig.d_weight = config.weight; + backendConfig.d_maxInFlightQueriesPerConn = config.max_in_flight; + backendConfig.d_tcpConcurrentConnectionsLimit = config.max_concurrent_tcp_connections; + backendConfig.name = std::string(config.name); + if (!config.id.empty()) { + backendConfig.id = boost::uuids::string_generator()(std::string(config.id)); + } + backendConfig.useECS = config.use_client_subnet; + backendConfig.useProxyProtocol = config.use_proxy_protocol; + backendConfig.d_proxyProtocolAdvertiseTLS = config.proxy_protocol_advertise_tls; + backendConfig.disableZeroScope = config.disable_zero_scope; + backendConfig.ipBindAddrNoPort = config.ip_bind_addr_no_port; + backendConfig.reconnectOnUp = config.reconnect_on_up; + backendConfig.d_cpus = getCPUPiningFromStr("backend", std::string(config.cpus)); + backendConfig.d_tcpOnly = config.tcp_only; + + backendConfig.d_retries = config.tcp.retries; + backendConfig.tcpConnectTimeout = config.tcp.connect_timeout; + backendConfig.tcpSendTimeout = config.tcp.send_timeout; + backendConfig.tcpRecvTimeout = config.tcp.receive_timeout; + backendConfig.tcpFastOpen = config.tcp.fast_open; + + const auto& hcConf = config.health_checks; + backendConfig.checkInterval = hcConf.interval; + if (!hcConf.qname.empty()) { + backendConfig.checkName = DNSName(std::string(hcConf.qname)); + } + backendConfig.checkType = std::string(hcConf.qtype); + if (!hcConf.qclass.empty()) { + backendConfig.checkClass = QClass(std::string(hcConf.qclass)); + } + backendConfig.checkTimeout = hcConf.timeout; + backendConfig.d_tcpCheck = hcConf.use_tcp; + backendConfig.setCD = hcConf.set_cd; + backendConfig.mustResolve = hcConf.must_resolve; + backendConfig.maxCheckFailures = hcConf.max_failures; + backendConfig.minRiseSuccesses = hcConf.rise; + + getLuaFunctionFromConfiguration(backendConfig.checkFunction, hcConf.function, hcConf.lua, hcConf.lua_file, "backend health-check"); + + auto availability = DownstreamState::getAvailabilityFromStr(std::string(hcConf.mode)); + if (availability) { + backendConfig.availability = *availability; + } + + backendConfig.d_lazyHealthCheckSampleSize = hcConf.lazy.sample_size; + backendConfig.d_lazyHealthCheckMinSampleCount = hcConf.lazy.min_sample_count; + backendConfig.d_lazyHealthCheckThreshold = hcConf.lazy.threshold; + backendConfig.d_lazyHealthCheckFailedInterval = hcConf.lazy.interval; + backendConfig.d_lazyHealthCheckUseExponentialBackOff = hcConf.lazy.use_exponential_back_off; + backendConfig.d_lazyHealthCheckMaxBackOff = hcConf.lazy.max_back_off; + if (hcConf.lazy.mode == "TimeoutOnly") { + backendConfig.d_lazyHealthCheckMode = DownstreamState::LazyHealthCheckMode::TimeoutOnly; + } + else if (hcConf.lazy.mode == "TimeoutOrServFail") { + backendConfig.d_lazyHealthCheckMode = DownstreamState::LazyHealthCheckMode::TimeoutOrServFail; + } + else if (!hcConf.lazy.mode.empty()) { + warnlog("Ignoring unknown value '%s' for 'lazy.mode' on backend %s", hcConf.lazy.mode, std::string(config.address)); + } + + backendConfig.d_upgradeToLazyHealthChecks = config.auto_upgrade.use_lazy_health_check; + + uint16_t serverPort = 53; + const auto& tlsConf = config.tls; + auto protocol = boost::to_lower_copy(std::string(config.protocol)); + if (protocol == "dot" || protocol == "doh") { + backendConfig.d_tlsParams.d_provider = std::string(tlsConf.provider); + backendConfig.d_tlsParams.d_ciphers = std::string(tlsConf.ciphers); + backendConfig.d_tlsParams.d_ciphers13 = std::string(tlsConf.ciphers_tls_13); + backendConfig.d_tlsParams.d_caStore = std::string(tlsConf.ca_store); + backendConfig.d_tlsParams.d_keyLogFile = std::string(tlsConf.key_log_file); + backendConfig.d_tlsParams.d_validateCertificates = tlsConf.validate_certificate; + backendConfig.d_tlsParams.d_releaseBuffers = tlsConf.release_buffers; + backendConfig.d_tlsParams.d_enableRenegotiation = tlsConf.enable_renegotiation; + backendConfig.d_tlsParams.d_ktls = tlsConf.ktls; + backendConfig.d_tlsSubjectName = std::string(tlsConf.subject_name); + if (!tlsConf.subject_address.empty()) { + try { + ComboAddress addr{std::string(tlsConf.subject_address)}; + backendConfig.d_tlsSubjectName = addr.toString(); + backendConfig.d_tlsSubjectIsAddr = true; + } + catch (const std::exception&) { + errlog("Error creating new server: downstream subject_address value must be a valid IP address"); + } + } + } + + if (protocol == "dot") { + serverPort = 853; + backendConfig.d_tlsParams.d_alpn = TLSFrontend::ALPN::DoT; + } + else if (protocol == "doh") { + serverPort = 443; + backendConfig.d_tlsParams.d_alpn = TLSFrontend::ALPN::DoH; + backendConfig.d_dohPath = std::string(config.doh.path); + backendConfig.d_addXForwardedHeaders = config.doh.add_x_forwarded_headers; + } + + for (const auto& pool : config.pools) { + backendConfig.pools.emplace(pool); + } + + backendConfig.remote = ComboAddress(std::string(config.address), serverPort); + + if (protocol == "dot" || protocol == "doh") { + tlsCtx = getTLSContext(backendConfig.d_tlsParams); + } + + auto downstream = std::make_shared(std::move(backendConfig), std::move(tlsCtx), !configCheck); + +#if defined(HAVE_XSK) + if (!config.xsk.empty()) { + auto xskMap = getRegisteredTypeByName(config.xsk); + if (!xskMap) { + throw std::runtime_error("XSK map " + std::string(config.xsk) + " attached to backend " + std::string(config.address) + " not found"); + } + downstream->registerXsk(*xskMap); + if (!configCheck) { + infolog("Added downstream server %s via XSK in %s mode", std::string(config.address), xskMap->at(0)->getXDPMode()); + } + } +#endif /* defined(HAVE_XSK) */ + + const auto& autoUpgradeConf = config.auto_upgrade; + if (autoUpgradeConf.enabled && downstream->getProtocol() != dnsdist::Protocol::DoT && downstream->getProtocol() != dnsdist::Protocol::DoH) { + dnsdist::ServiceDiscovery::addUpgradeableServer(downstream, autoUpgradeConf.interval, std::string(autoUpgradeConf.pool), autoUpgradeConf.doh_key, autoUpgradeConf.keep); + } + + return downstream; +} + +static void loadRulesConfiguration(const dnsdist::rust::settings::GlobalConfiguration& globalConfig) +{ + dnsdist::configuration::updateRuntimeConfiguration([&globalConfig](dnsdist::configuration::RuntimeConfiguration& config) { + for (const auto& rule : globalConfig.query_rules) { + boost::uuids::uuid ruleUniqueID = rule.uuid.empty() ? getUniqueID() : getUniqueID(std::string(rule.uuid)); + dnsdist::rules::add(config.d_ruleChains, dnsdist::rules::RuleChain::Rules, std::move(rule.selector.selector->d_rule), rule.action.action->d_action, std::string(rule.name), ruleUniqueID, 0); + } + + for (const auto& rule : globalConfig.cache_miss_rules) { + boost::uuids::uuid ruleUniqueID = rule.uuid.empty() ? getUniqueID() : getUniqueID(std::string(rule.uuid)); + dnsdist::rules::add(config.d_ruleChains, dnsdist::rules::RuleChain::CacheMissRules, std::move(rule.selector.selector->d_rule), rule.action.action->d_action, std::string(rule.name), ruleUniqueID, 0); + } + + for (const auto& rule : globalConfig.response_rules) { + boost::uuids::uuid ruleUniqueID = rule.uuid.empty() ? getUniqueID() : getUniqueID(std::string(rule.uuid)); + dnsdist::rules::add(config.d_ruleChains, dnsdist::rules::ResponseRuleChain::ResponseRules, std::move(rule.selector.selector->d_rule), rule.action.action->d_action, std::string(rule.name), ruleUniqueID, 0); + } + + for (const auto& rule : globalConfig.cache_hit_response_rules) { + boost::uuids::uuid ruleUniqueID = rule.uuid.empty() ? getUniqueID() : getUniqueID(std::string(rule.uuid)); + dnsdist::rules::add(config.d_ruleChains, dnsdist::rules::ResponseRuleChain::CacheHitResponseRules, std::move(rule.selector.selector->d_rule), rule.action.action->d_action, std::string(rule.name), ruleUniqueID, 0); + } + + for (const auto& rule : globalConfig.cache_inserted_response_rules) { + boost::uuids::uuid ruleUniqueID = rule.uuid.empty() ? getUniqueID() : getUniqueID(std::string(rule.uuid)); + dnsdist::rules::add(config.d_ruleChains, dnsdist::rules::ResponseRuleChain::CacheInsertedResponseRules, std::move(rule.selector.selector->d_rule), rule.action.action->d_action, std::string(rule.name), ruleUniqueID, 0); + } + + for (const auto& rule : globalConfig.self_answered_response_rules) { + boost::uuids::uuid ruleUniqueID = rule.uuid.empty() ? getUniqueID() : getUniqueID(std::string(rule.uuid)); + dnsdist::rules::add(config.d_ruleChains, dnsdist::rules::ResponseRuleChain::SelfAnsweredResponseRules, std::move(rule.selector.selector->d_rule), rule.action.action->d_action, std::string(rule.name), ruleUniqueID, 0); + } + + for (const auto& rule : globalConfig.xfr_response_rules) { + boost::uuids::uuid ruleUniqueID = rule.uuid.empty() ? getUniqueID() : getUniqueID(std::string(rule.uuid)); + dnsdist::rules::add(config.d_ruleChains, dnsdist::rules::ResponseRuleChain::XFRResponseRules, std::move(rule.selector.selector->d_rule), rule.action.action->d_action, std::string(rule.name), ruleUniqueID, 0); + } + }); +} + +static void loadDynamicBlockConfiguration(const dnsdist::rust::settings::DynamicRulesSettingsConfiguration& settings, const ::rust::Vec& dynamicRules) +{ + if (!settings.default_action.empty()) { + dnsdist::configuration::updateRuntimeConfiguration([default_action = settings.default_action](dnsdist::configuration::RuntimeConfiguration& config) { + config.d_dynBlockAction = DNSAction::typeFromString(std::string(default_action)); + }); + } + + for (const auto& dbrg : dynamicRules) { + auto dbrgObj = std::make_shared(); + dbrgObj->setMasks(dbrg.mask_ipv4, dbrg.mask_ipv6, dbrg.mask_port); + for (const auto& range : dbrg.exclude_ranges) { + dbrgObj->excludeRange(Netmask(std::string(range))); + } + for (const auto& range : dbrg.include_ranges) { + dbrgObj->includeRange(Netmask(std::string(range))); + } + for (const auto& domain : dbrg.exclude_domains) { + dbrgObj->excludeDomain(DNSName(std::string(domain))); + } + for (const auto& rule : dbrg.rules) { + if (rule.rule_type == "query-rate") { + DynBlockRulesGroup::DynBlockRule ruleParams(std::string(rule.comment), rule.action_duration, rule.rate, rule.warning_rate, rule.seconds, rule.action.empty() ? DNSAction::Action::None : DNSAction::typeFromString(std::string(rule.action))); + if (ruleParams.d_action == DNSAction::Action::SetTag && !rule.tag_name.empty()) { + ruleParams.d_tagSettings = std::make_shared(); + ruleParams.d_tagSettings->d_name = std::string(rule.tag_name); + ruleParams.d_tagSettings->d_value = std::string(rule.tag_value); + } + dbrgObj->setQueryRate(std::move(ruleParams)); + } + else if (rule.rule_type == "rcode-rate") { + DynBlockRulesGroup::DynBlockRule ruleParams(std::string(rule.comment), rule.action_duration, rule.rate, rule.warning_rate, rule.seconds, rule.action.empty() ? DNSAction::Action::None : DNSAction::typeFromString(std::string(rule.action))); + if (ruleParams.d_action == DNSAction::Action::SetTag && !rule.tag_name.empty()) { + ruleParams.d_tagSettings = std::make_shared(); + ruleParams.d_tagSettings->d_name = std::string(rule.tag_name); + ruleParams.d_tagSettings->d_value = std::string(rule.tag_value); + } + dbrgObj->setRCodeRate(checkedConversionFromStr("dynamic-rules.rules.rcode_rate", "rcode", rule.rcode), std::move(ruleParams)); + } + else if (rule.rule_type == "rcode-ratio") { + DynBlockRulesGroup::DynBlockRatioRule ruleParams(std::string(rule.comment), rule.action_duration, rule.ratio, rule.warning_ratio, rule.seconds, rule.action.empty() ? DNSAction::Action::None : DNSAction::typeFromString(std::string(rule.action)), rule.minimum_number_of_responses); + if (ruleParams.d_action == DNSAction::Action::SetTag && !rule.tag_name.empty()) { + ruleParams.d_tagSettings = std::make_shared(); + ruleParams.d_tagSettings->d_name = std::string(rule.tag_name); + ruleParams.d_tagSettings->d_value = std::string(rule.tag_value); + } + dbrgObj->setRCodeRatio(checkedConversionFromStr("dynamic-rules.rules.rcode_ratio", "rcode", rule.rcode), std::move(ruleParams)); + } + else if (rule.rule_type == "qtype-rate") { + DynBlockRulesGroup::DynBlockRule ruleParams(std::string(rule.comment), rule.action_duration, rule.rate, rule.warning_rate, rule.seconds, rule.action.empty() ? DNSAction::Action::None : DNSAction::typeFromString(std::string(rule.action))); + if (ruleParams.d_action == DNSAction::Action::SetTag && !rule.tag_name.empty()) { + ruleParams.d_tagSettings = std::make_shared(); + ruleParams.d_tagSettings->d_name = std::string(rule.tag_name); + ruleParams.d_tagSettings->d_value = std::string(rule.tag_value); + } + dbrgObj->setRCodeRate(checkedConversionFromStr("dynamic-rules.rules.qtype_rate", "qtype", rule.qtype), std::move(ruleParams)); + } + else if (rule.rule_type == "cache-miss-ratio") { + DynBlockRulesGroup::DynBlockCacheMissRatioRule ruleParams(std::string(rule.comment), rule.action_duration, rule.ratio, rule.warning_ratio, rule.seconds, rule.action.empty() ? DNSAction::Action::None : DNSAction::typeFromString(std::string(rule.action)), rule.minimum_number_of_responses, rule.minimum_global_cache_hit_ratio); + if (ruleParams.d_action == DNSAction::Action::SetTag && !rule.tag_name.empty()) { + ruleParams.d_tagSettings = std::make_shared(); + ruleParams.d_tagSettings->d_name = std::string(rule.tag_name); + ruleParams.d_tagSettings->d_value = std::string(rule.tag_value); + } + dbrgObj->setCacheMissRatio(std::move(ruleParams)); + } + else if (rule.rule_type == "response-byte-rate") { + DynBlockRulesGroup::DynBlockRule ruleParams(std::string(rule.comment), rule.action_duration, rule.rate, rule.warning_rate, rule.seconds, rule.action.empty() ? DNSAction::Action::None : DNSAction::typeFromString(std::string(rule.action))); + if (ruleParams.d_action == DNSAction::Action::SetTag && !rule.tag_name.empty()) { + ruleParams.d_tagSettings = std::make_shared(); + ruleParams.d_tagSettings->d_name = std::string(rule.tag_name); + ruleParams.d_tagSettings->d_value = std::string(rule.tag_value); + } + dbrgObj->setResponseByteRate(std::move(ruleParams)); + } + else if (rule.rule_type == "suffix-match") { + DynBlockRulesGroup::DynBlockRule ruleParams(std::string(rule.comment), rule.action_duration, 0, 0, rule.seconds, rule.action.empty() ? DNSAction::Action::None : DNSAction::typeFromString(std::string(rule.action))); + if (ruleParams.d_action == DNSAction::Action::SetTag && !rule.tag_name.empty()) { + ruleParams.d_tagSettings = std::make_shared(); + ruleParams.d_tagSettings->d_name = std::string(rule.tag_name); + ruleParams.d_tagSettings->d_value = std::string(rule.tag_value); + } + DynBlockRulesGroup::smtVisitor_t visitor; + getLuaFunctionFromConfiguration(visitor, rule.visitor_function_name, rule.visitor_function_code, rule.visitor_function_file, "dynamic block suffix match visitor function"); + dbrgObj->setSuffixMatchRule(std::move(ruleParams), std::move(visitor)); + } + else if (rule.rule_type == "suffix-match-ffi") { + DynBlockRulesGroup::DynBlockRule ruleParams(std::string(rule.comment), rule.action_duration, 0, 0, rule.seconds, rule.action.empty() ? DNSAction::Action::None : DNSAction::typeFromString(std::string(rule.action))); + if (ruleParams.d_action == DNSAction::Action::SetTag && !rule.tag_name.empty()) { + ruleParams.d_tagSettings = std::make_shared(); + ruleParams.d_tagSettings->d_name = std::string(rule.tag_name); + ruleParams.d_tagSettings->d_value = std::string(rule.tag_value); + } + dnsdist_ffi_stat_node_visitor_t visitor; + getLuaFunctionFromConfiguration(visitor, rule.visitor_function_name, rule.visitor_function_code, rule.visitor_function_file, "dynamic block suffix match FFI visitor function"); + dbrgObj->setSuffixMatchRuleFFI(std::move(ruleParams), std::move(visitor)); + } + } + dnsdist::DynamicBlocks::registerGroup(dbrgObj); + } +} + +static void loadBinds(const ::rust::Vec& binds) +{ + for (const auto& bind : binds) { + updateImmutableConfiguration([&bind](ImmutableConfiguration& config) { + auto protocol = boost::to_lower_copy(std::string(bind.protocol)); + uint16_t defaultPort = 53; + if (protocol == "dot" || protocol == "doq") { + defaultPort = 853; + } + else if (protocol == "doh" || protocol == "dnscrypt" || protocol == "doh3") { + defaultPort = 443; + } + ComboAddress listeningAddress(std::string(bind.listen_address), defaultPort); + auto cpus = getCPUPiningFromStr("binds", std::string(bind.cpus)); + std::shared_ptr xskMap; + if (!bind.xsk.empty()) { + xskMap = getRegisteredTypeByName(bind.xsk); + if (!xskMap) { + throw std::runtime_error("XSK map " + std::string(bind.xsk) + " attached to bind " + std::string(bind.listen_address) + " not found"); + } + if (xskMap->size() != bind.threads) { + throw std::runtime_error("XSK map " + std::string(bind.xsk) + " attached to bind " + std::string(bind.listen_address) + " has less queues than the number of threads of the bind"); + } + } + + for (size_t idx = 0; idx < bind.threads; idx++) { +#if defined(HAVE_DNSCRYPT) + std::shared_ptr dnsCryptContext; +#endif /* defined(HAVE_DNSCRYPT) */ + + auto state = std::make_shared(listeningAddress, protocol != "doq" && protocol != "doh3", bind.reuseport, bind.tcp.fast_open_queue_size, std::string(bind.interface), cpus, false); + + if (bind.tcp.listen_queue_size > 0) { + state->tcpListenQueueSize = bind.tcp.listen_queue_size; + } + if (bind.tcp.max_in_flight_queries > 0) { + state->d_maxInFlightQueriesPerConn = bind.tcp.max_in_flight_queries; + } + if (bind.tcp.max_concurrent_connections > 0) { + state->d_tcpConcurrentConnectionsLimit = bind.tcp.max_concurrent_connections; + } + + for (const auto& addr : bind.additional_addresses) { + try { + ComboAddress address{std::string(addr)}; + state->d_additionalAddresses.emplace_back(address, -1); + } + catch (const PDNSException& e) { + errlog("Unable to parse additional address %s for %s bind: %s", std::string(addr), protocol, e.reason); + } + } + + if (protocol == "dnscrypt") { +#if defined(HAVE_DNSCRYPT) + std::vector certKeys; + for (const auto& pair : bind.dnscrypt.certificates) { + certKeys.push_back({std::string(pair.certificate), std::string(pair.key)}); + } + dnsCryptContext = std::make_shared(std::string(bind.dnscrypt.provider_name), certKeys); + state->dnscryptCtx = dnsCryptContext; +#endif /* defined(HAVE_DNSCRYPT) */ + } + else if (protocol != "do53") { + if (!handleTLSConfiguration(bind, *state)) { + continue; + } + } + + config.d_frontends.emplace_back(std::move(state)); + if (protocol == "do53" || protocol == "dnscrypt") { + /* also create the UDP listener */ + state = std::make_shared(ComboAddress(std::string(bind.listen_address), defaultPort), false, bind.reuseport, bind.tcp.fast_open_queue_size, std::string(bind.interface), cpus, false); +#if defined(HAVE_DNSCRYPT) + state->dnscryptCtx = dnsCryptContext; +#endif /* defined(HAVE_DNSCRYPT) */ +#if defined(HAVE_XSK) + if (xskMap) { + auto xsk = xskMap->at(idx); + state->xskInfo = XskWorker::create(XskWorker::Type::Bidirectional, xsk->sharedEmptyFrameOffset); + xsk->addWorker(state->xskInfo); + xsk->addWorkerRoute(state->xskInfo, listeningAddress); + state->xskInfoResponder = XskWorker::create(XskWorker::Type::OutgoingOnly, xsk->sharedEmptyFrameOffset); + xsk->addWorker(state->xskInfoResponder); + vinfolog("Enabling XSK in %s mode for incoming UDP packets to %s", xsk->getXDPMode(), listeningAddress.toStringWithPort()); + } +#endif /* defined(HAVE_XSK) */ + config.d_frontends.emplace_back(std::move(state)); + } + } + }); + } +} + +static void loadWebServer(const dnsdist::rust::settings::WebserverConfiguration& webConfig) +{ + ComboAddress local; + try { + local = ComboAddress{std::string(webConfig.listen_address)}; + } + catch (const PDNSException& e) { + throw std::runtime_error(std::string("Error parsing the bind address for the webserver: ") + e.reason); + } + dnsdist::configuration::updateRuntimeConfiguration([local, webConfig](dnsdist::configuration::RuntimeConfiguration& config) { + config.d_webServerAddress = local; + if (!webConfig.password.empty()) { + auto holder = std::make_shared(std::string(webConfig.password), webConfig.hash_plaintext_credentials); + if (!holder->wasHashed() && holder->isHashingAvailable()) { + infolog("Passing a plain-text password via the 'webserver.password' parameter to is not advised, please consider generating a hashed one using 'hashPassword()' instead."); + } + config.d_webPassword = std::move(holder); + } + if (!webConfig.api_key.empty()) { + auto holder = std::make_shared(std::string(webConfig.api_key), webConfig.hash_plaintext_credentials); + if (!holder->wasHashed() && holder->isHashingAvailable()) { + infolog("Passing a plain-text API key via the 'webserver.api_key' parameter to is not advised, please consider generating a hashed one using 'hashPassword()' instead."); + } + config.d_webAPIKey = std::move(holder); + } + if (!webConfig.acl.empty()) { + config.d_webServerACL.clear(); + for (const auto& acl : webConfig.acl) { + config.d_webServerACL.toMasks(std::string(acl)); + } + } + if (!webConfig.custom_headers.empty()) { + if (!config.d_webCustomHeaders) { + config.d_webCustomHeaders = std::unordered_map(); + for (const auto& customHeader : webConfig.custom_headers) { + auto headerResponse = std::pair(boost::to_lower_copy(std::string(customHeader.key)), std::string(customHeader.value)); + config.d_webCustomHeaders->insert(std::move(headerResponse)); + } + } + } + + config.d_apiRequiresAuthentication = webConfig.api_requires_authentication; + config.d_dashboardRequiresAuthentication = webConfig.dashboard_requires_authentication; + config.d_statsRequireAuthentication = webConfig.stats_require_authentication; + dnsdist::webserver::setMaxConcurrentConnections(webConfig.max_concurrent_connections); + config.d_apiConfigDirectory = std::string(webConfig.api_configuration_directory); + config.d_apiReadWrite = webConfig.api_read_write; + }); +} + +static void loadCustomPolicies(const ::rust::Vec& customPolicies) +{ + for (const auto& policy : customPolicies) { + if (policy.ffi) { + if (policy.per_thread) { + auto policyObj = std::make_shared(std::string(policy.name), std::string(policy.function_code)); + registerType(policyObj, policy.name); + } + else { + ServerPolicy::ffipolicyfunc_t function; + + if (!getLuaFunctionFromConfiguration(function, policy.function_name, policy.function_code, policy.function_file, "FFI load-balancing policy")) { + throw std::runtime_error("Custom FFI load-balancing policy '" + std::string(policy.name) + "' could not be created: no valid function name, Lua code or Lua file"); + } + auto policyObj = std::make_shared(std::string(policy.name), std::move(function)); + registerType(policyObj, policy.name); + } + } + else { + ServerPolicy::policyfunc_t function; + if (!getLuaFunctionFromConfiguration(function, policy.function_name, policy.function_code, policy.function_file, "load-balancing policy")) { + throw std::runtime_error("Custom load-balancing policy '" + std::string(policy.name) + "' could not be created: no valid function name, Lua code or Lua file"); + } + auto policyObj = std::make_shared(std::string(policy.name), std::move(function), true); + registerType(policyObj, policy.name); + } + } +} + +static void handleOpenSSLSettings(const dnsdist::rust::settings::TlsTuningConfiguration& tlsSettings) +{ + for (const auto& engine : tlsSettings.engines) { +#if defined(HAVE_LIBSSL) && !defined(HAVE_TLS_PROVIDERS) + auto [success, error] = libssl_load_engine(std::string(engine.name), !engine.default_string.empty() ? std::optional(engine.default_string) : std::nullopt); + if (!success) { + warnlog("Error while trying to load TLS engine '%s': %s", std::string(engine.name), error); + } +#else + warnlog("Ignoring TLS engine '%s' because OpenSSL engine support is not compiled in", std::string(engine.name)); +#endif /* HAVE_LIBSSL && !HAVE_TLS_PROVIDERS */ + } + + for (const auto& provider : tlsSettings.providers) { +#if defined(HAVE_LIBSSL) && OPENSSL_VERSION_MAJOR >= 3 && defined(HAVE_TLS_PROVIDERS) + auto [success, error] = libssl_load_provider(std::string(provider)); + if (!success) { + warnlog("Error while trying to load TLS provider '%s': %s", std::string(provider), error); + } +#else + warnlog("Ignoring TLS provider '%s' because OpenSSL provider support is not compiled in", std::string(provider)); +#endif /* HAVE_LIBSSL && OPENSSL_VERSION_MAJOR >= 3 && HAVE_TLS_PROVIDERS */ + } +} + +static void handleLoggingConfiguration(const dnsdist::rust::settings::LoggingConfiguration& settings) +{ + if (!settings.verbose_log_destination.empty()) { + auto dest = std::string(settings.verbose_log_destination); + 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()); + } + } + + if (!settings.syslog_facility.empty()) { + auto facilityLevel = logFacilityFromString(std::string(settings.syslog_facility)); + if (!facilityLevel) { + warnlog("Unknown facility '%s' passed to logging.syslog_facility", std::string(settings.syslog_facility)); + } + else { + setSyslogFacility(*facilityLevel); + } + } + + if (settings.structured.enabled) { + auto levelPrefix = std::string(settings.structured.level_prefix); + auto timeFormat = std::string(settings.structured.time_format); + if (!timeFormat.empty()) { + 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 logging.structured.time_format parameter", timeFormat); + } + } + + dnsdist::logging::LoggingConfiguration::setStructuredLogging(true, levelPrefix); + } +} + +#endif /* defined(HAVE_YAML_CONFIGURATION) */ + +bool loadConfigurationFromFile(const std::string& fileName, bool isClient, bool configCheck) +{ +#if defined(HAVE_YAML_CONFIGURATION) + // this is not very elegant but passing a context to the functions called by the + // Rust code would be quite cumbersome so for now let's settle for this + s_inConfigCheckMode.store(configCheck); + s_inClientMode.store(isClient); + + auto data = loadContentFromConfigurationFile(fileName); + if (!data) { + errlog("Unable to open YAML file %s: %s", fileName, stringerror(errno)); + return false; + } + + /* register built-in policies */ + for (const auto& policy : dnsdist::lbpolicies::getBuiltInPolicies()) { + registerType(policy, ::rust::string(policy->d_name)); + } + + try { + auto globalConfig = dnsdist::rust::settings::from_yaml_string(*data); + + handleLoggingConfiguration(globalConfig.logging); + + if (!globalConfig.console.listen_address.empty()) { + const auto& consoleConf = globalConfig.console; + dnsdist::configuration::updateRuntimeConfiguration([consoleConf](dnsdist::configuration::RuntimeConfiguration& config) { + config.d_consoleServerAddress = ComboAddress(std::string(consoleConf.listen_address), 5199); + config.d_consoleEnabled = true; + config.d_consoleACL.clear(); + for (const auto& aclEntry : consoleConf.acl) { + config.d_consoleACL.addMask(std::string(aclEntry)); + } + B64Decode(std::string(consoleConf.key), config.d_consoleKey); + }); + } + + if (isClient) { + return true; + } + + if (!globalConfig.acl.empty()) { + dnsdist::configuration::updateRuntimeConfiguration([&acl = globalConfig.acl](dnsdist::configuration::RuntimeConfiguration& config) { + config.d_ACL.clear(); + for (const auto& aclEntry : acl) { + config.d_ACL.addMask(std::string(aclEntry)); + } + }); + } + + handleOpenSSLSettings(globalConfig.tuning.tls); + +#if defined(HAVE_EBPF) + if (!configCheck && globalConfig.ebpf.ipv4.max_entries > 0 && globalConfig.ebpf.ipv6.max_entries > 0 && globalConfig.ebpf.qnames.max_entries > 0) { + BPFFilter::MapFormat format = globalConfig.ebpf.external ? BPFFilter::MapFormat::WithActions : BPFFilter::MapFormat::Legacy; + std::unordered_map mapsConfig; + + const auto convertParamsToConfig = [&mapsConfig](const std::string& name, BPFFilter::MapType type, const dnsdist::rust::settings::EbpfMapConfiguration& mapConfig) { + if (mapConfig.max_entries == 0) { + return; + } + BPFFilter::MapConfiguration config; + config.d_type = type; + config.d_maxItems = mapConfig.max_entries; + config.d_pinnedPath = std::string(mapConfig.pinned_path); + mapsConfig[name] = std::move(config); + }; + + convertParamsToConfig("ipv4", BPFFilter::MapType::IPv4, globalConfig.ebpf.ipv4); + convertParamsToConfig("ipv6", BPFFilter::MapType::IPv6, globalConfig.ebpf.ipv6); + convertParamsToConfig("qnames", BPFFilter::MapType::QNames, globalConfig.ebpf.qnames); + convertParamsToConfig("cidr4", BPFFilter::MapType::CIDR4, globalConfig.ebpf.cidr_ipv4); + convertParamsToConfig("cidr6", BPFFilter::MapType::CIDR6, globalConfig.ebpf.cidr_ipv6); + auto filter = std::make_shared(mapsConfig, format, globalConfig.ebpf.external); + g_defaultBPFFilter = std::move(filter); + } +#endif /* defined(HAVE_EBPF) */ + +#if defined(HAVE_XSK) + for (const auto& xskEntry : globalConfig.xsk) { + auto map = std::shared_ptr(); + for (size_t counter = 0; counter < xskEntry.queues; ++counter) { + auto socket = std::make_shared(xskEntry.frames, std::string(xskEntry.interface), counter, std::string(xskEntry.map_path)); + dnsdist::xsk::g_xsk.push_back(socket); + map->push_back(std::move(socket)); + } + registerType(map, xskEntry.name); + } +#endif /* defined(HAVE_XSK) */ + + loadBinds(globalConfig.binds); + + for (const auto& backend : globalConfig.backends) { + auto downstream = createBackendFromConfiguration(backend, configCheck); + + if (!downstream->d_config.pools.empty()) { + for (const auto& poolName : downstream->d_config.pools) { + addServerToPool(poolName, downstream); + } + } + else { + addServerToPool("", downstream); + } + + dnsdist::backend::registerNewBackend(downstream); + } + + if (!globalConfig.proxy_protocol.acl.empty()) { + dnsdist::configuration::updateRuntimeConfiguration([&globalConfig](dnsdist::configuration::RuntimeConfiguration& config) { + config.d_proxyProtocolACL.clear(); + for (const auto& aclEntry : globalConfig.proxy_protocol.acl) { + config.d_proxyProtocolACL.addMask(std::string(aclEntry)); + } + }); + } + +#ifndef DISABLE_CARBON + if (!globalConfig.metrics.carbon.empty()) { + dnsdist::configuration::updateRuntimeConfiguration([&globalConfig](dnsdist::configuration::RuntimeConfiguration& config) { + for (const auto& carbonConfig : globalConfig.metrics.carbon) { + auto newEndpoint = dnsdist::Carbon::newEndpoint(std::string(carbonConfig.address), + std::string(carbonConfig.name), + carbonConfig.interval, + carbonConfig.name_space.empty() ? "dnsdist" : std::string(carbonConfig.name_space), + carbonConfig.instance.empty() ? "main" : std::string(carbonConfig.instance)); + config.d_carbonEndpoints.push_back(std::move(newEndpoint)); + } + }); + } +#endif /* DISABLE_CARBON */ + + if (!globalConfig.webserver.listen_address.empty()) { + const auto& webConfig = globalConfig.webserver; + loadWebServer(webConfig); + } + + if (globalConfig.query_count.enabled) { + dnsdist::configuration::updateRuntimeConfiguration([&globalConfig](dnsdist::configuration::RuntimeConfiguration& config) { + config.d_queryCountConfig.d_enabled = true; + getLuaFunctionFromConfiguration(config.d_queryCountConfig.d_filter, globalConfig.query_count.filter_function_name, globalConfig.query_count.filter_function_code, globalConfig.query_count.filter_function_file, "query count filter function"); + }); + } + + loadDynamicBlockConfiguration(globalConfig.dynamic_rules_settings, globalConfig.dynamic_rules); + + if (!globalConfig.tuning.tcp.fast_open_key.empty()) { + std::vector key(4); + auto ret = sscanf(globalConfig.tuning.tcp.fast_open_key.c_str(), "%" SCNx32 "-%" SCNx32 "-%" SCNx32 "-%" SCNx32, &key.at(0), &key.at(1), &key.at(2), &key.at(3)); + if (ret < 0 || static_cast(ret) != key.size()) { + throw std::runtime_error("Invalid value passed to tuning.tcp.fast_open_key!\n"); + } + dnsdist::configuration::updateImmutableConfiguration([&key](dnsdist::configuration::ImmutableConfiguration& config) { + config.d_tcpFastOpenKey = std::move(key); + }); + } + + if (!globalConfig.general.capabilities_to_retain.empty()) { + dnsdist::configuration::updateImmutableConfiguration([capabilities = globalConfig.general.capabilities_to_retain](dnsdist::configuration::ImmutableConfiguration& config) { + for (const auto& capability : capabilities) { + config.d_capabilitiesToRetain.emplace(std::string(capability)); + } + }); + } + + for (const auto& cache : globalConfig.packet_caches) { + auto packetCacheObj = std::make_shared(cache.size, cache.max_ttl, cache.min_ttl, cache.temporary_failure_ttl, cache.max_negative_ttl, cache.stale_ttl, cache.dont_age, cache.shards, cache.deferrable_insert_lock, cache.parse_ecs); + + packetCacheObj->setKeepStaleData(cache.keep_stale_data); + std::unordered_set optionsToSkip{EDNSOptionCode::COOKIE}; + + for (const auto& option : cache.options_to_skip) { + optionsToSkip.insert(pdns::checked_stoi(std::string(option))); + } + + if (cache.cookie_hashing) { + optionsToSkip.erase(EDNSOptionCode::COOKIE); + } + + packetCacheObj->setSkippedOptions(optionsToSkip); + if (cache.maximum_entry_size >= sizeof(dnsheader)) { + packetCacheObj->setMaximumEntrySize(cache.maximum_entry_size); + } + + registerType(packetCacheObj, cache.name); + } + + loadCustomPolicies(globalConfig.load_balancing_policies.custom_policies); + + if (!globalConfig.load_balancing_policies.default_policy.empty()) { + auto policy = getRegisteredTypeByName(globalConfig.load_balancing_policies.default_policy); + dnsdist::configuration::updateRuntimeConfiguration([&policy](dnsdist::configuration::RuntimeConfiguration& config) { + config.d_lbPolicy = std::move(policy); + }); + } + + for (const auto& pool : globalConfig.pools) { + std::shared_ptr poolObj = createPoolIfNotExists(std::string(pool.name)); + if (!pool.packet_cache.empty()) { + poolObj->packetCache = getRegisteredTypeByName(pool.packet_cache); + } + if (!pool.policy.empty()) { + poolObj->policy = getRegisteredTypeByName(pool.policy); + } + } + + dnsdist::configuration::updateImmutableConfiguration([&globalConfig](dnsdist::configuration::ImmutableConfiguration& config) { + convertImmutableFlatSettingsFromRust(globalConfig, config); + }); + + dnsdist::configuration::updateRuntimeConfiguration([&globalConfig](dnsdist::configuration::RuntimeConfiguration& config) { + convertRuntimeFlatSettingsFromRust(globalConfig, config); + }); + + loadRulesConfiguration(globalConfig); + + s_registeredTypesMap.lock()->clear(); + return true; + } + catch (const ::rust::Error& exp) { + errlog("Rust error while opening YAML file %s: %s", fileName, exp.what()); + } + catch (const std::exception& exp) { + errlog("C++ error while opening YAML file %s: %s", fileName, exp.what()); + } + s_registeredTypesMap.lock()->clear(); + return false; +#else + (void)fileName; + throw std::runtime_error("Unsupported YAML configuration"); +#endif /* HAVE_YAML_CONFIGURATION */ +} +} + +#if defined(HAVE_YAML_CONFIGURATION) +namespace dnsdist::rust::settings +{ + +static std::shared_ptr newDNSSelector(std::shared_ptr&& rule, const ::rust::String& name) +{ + auto selector = std::make_shared(); + selector->d_name = std::string(name); + selector->d_rule = std::move(rule); + dnsdist::configuration::yaml::registerType(selector, name); + return selector; +} + +static std::shared_ptr newDNSActionWrapper(std::shared_ptr&& action, const ::rust::String& name) +{ + auto wrapper = std::make_shared(); + wrapper->d_name = std::string(name); + wrapper->d_action = std::move(action); + dnsdist::configuration::yaml::registerType(wrapper, name); + return wrapper; +} + +static std::shared_ptr newDNSResponseActionWrapper(std::shared_ptr&& action, const ::rust::String& name) +{ + auto wrapper = std::make_shared(); + wrapper->d_name = std::string(name); + wrapper->d_action = std::move(action); + dnsdist::configuration::yaml::registerType(wrapper, name); + return wrapper; +} + +static dnsdist::ResponseConfig convertResponseConfig(const dnsdist::rust::settings::ResponseConfig& rustConfig) +{ + dnsdist::ResponseConfig cppConfig{}; + cppConfig.setAA = rustConfig.set_aa; + cppConfig.setAD = rustConfig.set_ad; + cppConfig.setRA = rustConfig.set_ra; + cppConfig.ttl = rustConfig.ttl; + return cppConfig; +} + +static dnsdist::actions::SOAParams convertSOAParams(const dnsdist::rust::settings::SOAParams& soa) +{ + dnsdist::actions::SOAParams cppSOA{}; + cppSOA.serial = soa.serial; + cppSOA.refresh = soa.refresh; + cppSOA.retry = soa.retry; + cppSOA.expire = soa.expire; + cppSOA.minimum = soa.minimum; + return cppSOA; +} + +static std::vector<::SVCRecordParameters> convertSVCRecordParameters(const ::rust::Vec& rustParameters) +{ + std::vector<::SVCRecordParameters> cppParameters; + for (const auto& rustConfig : rustParameters) { + ::SVCRecordParameters cppConfig{}; + for (auto param : rustConfig.mandatory_params) { + cppConfig.mandatoryParams.insert(param); + } + for (const auto& alpn : rustConfig.alpns) { + cppConfig.alpns.emplace_back(alpn); + } + for (const auto& hint : rustConfig.ipv4_hints) { + cppConfig.ipv4hints.emplace_back(std::string(hint)); + } + for (const auto& hint : rustConfig.ipv6_hints) { + cppConfig.ipv6hints.emplace_back(std::string(hint)); + } + for (const auto& param : rustConfig.additional_params) { + cppConfig.additionalParams.emplace_back(param.key, std::string(param.value)); + } + cppConfig.target = DNSName(std::string(rustConfig.target)); + if (rustConfig.port != 0) { + cppConfig.port = rustConfig.port; + } + cppConfig.priority = rustConfig.priority; + cppConfig.noDefaultAlpn = rustConfig.no_default_alpn; + + cppParameters.emplace_back(std::move(cppConfig)); + } + return cppParameters; +} + +std::shared_ptr getLuaAction(const LuaActionConfiguration& config) +{ + dnsdist::actions::LuaActionFunction function; + if (!dnsdist::configuration::yaml::getLuaFunctionFromConfiguration(function, config.function_name, config.function_code, config.function_file, "Lua action")) { + throw std::runtime_error("Lua action '" + std::string(config.name) + "' could not be created: no valid function name, Lua code or Lua file"); + } + auto action = dnsdist::actions::getLuaAction(std::move(function)); + return newDNSActionWrapper(std::move(action), config.name); +} + +std::shared_ptr getLuaFFIAction(const LuaFFIActionConfiguration& config) +{ + dnsdist::actions::LuaActionFFIFunction function; + if (!dnsdist::configuration::yaml::getLuaFunctionFromConfiguration(function, config.function_name, config.function_code, config.function_file, "Lua action")) { + throw std::runtime_error("Lua FFI action '" + std::string(config.name) + "' could not be created: no valid function name, Lua code or Lua file"); + } + auto action = dnsdist::actions::getLuaFFIAction(std::move(function)); + return newDNSActionWrapper(std::move(action), config.name); +} + +std::shared_ptr getContinueAction(const ContinueActionConfiguration& config) +{ + auto action = dnsdist::actions::getContinueAction(config.action.action->d_action); + return newDNSActionWrapper(std::move(action), config.name); +} + +std::shared_ptr getSetProxyProtocolValuesAction(const SetProxyProtocolValuesActionConfiguration& config) +{ + std::vector> values; + for (const auto& value : config.values) { + values.emplace_back(value.key, std::string(value.value)); + } + auto action = dnsdist::actions::getSetProxyProtocolValuesAction(values); + return newDNSActionWrapper(std::move(action), config.name); +} + +std::shared_ptr getSpoofPacketAction(const SpoofPacketActionConfiguration& config) +{ + if (config.response.size() < sizeof(dnsheader)) { + throw std::runtime_error(std::string("SpoofPacketAction: given packet len is too small")); + } + auto action = dnsdist::actions::getSpoofAction(PacketBuffer(config.response.data(), config.response.data() + config.response.size())); + return newDNSActionWrapper(std::move(action), config.name); +} + +std::shared_ptr getSpoofAction(const SpoofActionConfiguration& config) +{ + std::vector addresses; + for (const auto& addr : config.ips) { + addresses.emplace_back(std::string(addr)); + } + auto action = dnsdist::actions::getSpoofAction(addresses, convertResponseConfig(config.vars)); + return newDNSActionWrapper(std::move(action), config.name); +} + +std::shared_ptr getSpoofCNAMEAction(const SpoofCNAMEActionConfiguration& config) +{ + auto cname = DNSName(std::string(config.cname)); + auto action = dnsdist::actions::getSpoofAction(cname, convertResponseConfig(config.vars)); + return newDNSActionWrapper(std::move(action), config.name); +} + +std::shared_ptr getSpoofRawAction(const SpoofRawActionConfiguration& config) +{ + std::vector raws; + for (const auto& answer : config.answers) { + raws.emplace_back(answer); + } + std::optional qtypeForAny; + if (!config.qtype_for_any.empty()) { + QType qtype; + qtype = std::string(config.qtype_for_any); + qtypeForAny = qtype.getCode(); + } + auto action = dnsdist::actions::getSpoofAction(raws, qtypeForAny, convertResponseConfig(config.vars)); + return newDNSActionWrapper(std::move(action), config.name); +} + +std::shared_ptr getLuaResponseAction(const LuaResponseActionConfiguration& config) +{ + dnsdist::actions::LuaResponseActionFunction function; + if (!dnsdist::configuration::yaml::getLuaFunctionFromConfiguration(function, config.function_name, config.function_code, config.function_file, "Lua action")) { + throw std::runtime_error("Lua response action '" + std::string(config.name) + "' could not be created: no valid function name, Lua code or Lua file"); + } + auto action = dnsdist::actions::getLuaResponseAction(std::move(function)); + return newDNSResponseActionWrapper(std::move(action), config.name); +} + +std::shared_ptr getLuaFFIResponseAction(const LuaFFIResponseActionConfiguration& config) +{ + dnsdist::actions::LuaResponseActionFFIFunction function; + if (!dnsdist::configuration::yaml::getLuaFunctionFromConfiguration(function, config.function_name, config.function_code, config.function_file, "Lua action")) { + throw std::runtime_error("Lua FFI response action '" + std::string(config.name) + "' could not be created: no valid function name, Lua code or Lua file"); + } + auto action = dnsdist::actions::getLuaFFIResponseAction(std::move(function)); + return newDNSResponseActionWrapper(std::move(action), config.name); +} + +std::shared_ptr getClearRecordTypesResponseAction(const ClearRecordTypesResponseActionConfiguration& config) +{ + std::unordered_set qtypes{}; + for (const auto& type : config.types) { + qtypes.insert(type); + } + auto action = dnsdist::actions::getClearRecordTypesResponseAction(std::move(qtypes)); + return newDNSResponseActionWrapper(std::move(action), config.name); +} + +std::shared_ptr getLimitTTLResponseAction(const LimitTTLResponseActionConfiguration& config) +{ + std::unordered_set capTypes; + for (const auto& type : config.types) { + capTypes.insert(QType(type)); + } + + auto action = dnsdist::actions::getLimitTTLResponseAction(config.min, config.max, capTypes); + return newDNSResponseActionWrapper(std::move(action), config.name); +} + +std::shared_ptr getSetMinTTLResponseAction(const SetMinTTLResponseActionConfiguration& config) +{ + auto action = dnsdist::actions::getLimitTTLResponseAction(config.min); + return newDNSResponseActionWrapper(std::move(action), config.name); +} + +std::shared_ptr getSetMaxTTLResponseAction(const SetMaxTTLResponseActionConfiguration& config) +{ + auto action = dnsdist::actions::getLimitTTLResponseAction(0, config.max); + return newDNSResponseActionWrapper(std::move(action), config.name); +} + +std::shared_ptr getQNameSuffixSelector(const QNameSuffixSelectorConfiguration& config) +{ + SuffixMatchNode suffixes; + for (const auto& suffix : config.suffixes) { + suffixes.add(std::string(suffix)); + } + return newDNSSelector(dnsdist::selectors::getQNameSuffixSelector(suffixes, config.quiet), config.name); +} + +std::shared_ptr getQNameSetSelector(const QNameSetSelectorConfiguration& config) +{ + DNSNameSet qnames; + for (const auto& name : config.qnames) { + qnames.emplace(std::string(name)); + } + return newDNSSelector(dnsdist::selectors::getQNameSetSelector(qnames), config.name); +} + +std::shared_ptr getQNameSelector(const QNameSelectorConfiguration& config) +{ + return newDNSSelector(dnsdist::selectors::getQNameSelector(DNSName(std::string(config.qname))), config.name); +} + +std::shared_ptr getNetmaskGroupSelector(const NetmaskGroupSelectorConfiguration& config) +{ + std::shared_ptr nmg; + if (!config.netmask_group_name.empty()) { + nmg = dnsdist::configuration::yaml::getRegisteredTypeByName(std::string(config.netmask_group_name)); + } + if (!nmg) { + nmg = std::make_shared(); + } + for (const auto& netmask : config.netmasks) { + nmg->addMask(std::string(netmask)); + } + auto selector = dnsdist::selectors::getNetmaskGroupSelector(*nmg, config.source, config.quiet); + return newDNSSelector(std::move(selector), config.name); +} + +std::shared_ptr getKeyValueStoreLookupAction(const KeyValueStoreLookupActionConfiguration& config) +{ + auto kvs = dnsdist::configuration::yaml::getRegisteredTypeByName(std::string(config.kvs_name)); + if (!kvs && !(dnsdist::configuration::yaml::s_inClientMode || dnsdist::configuration::yaml::s_inConfigCheckMode)) { + throw std::runtime_error("Unable to find the key-value store named '" + std::string(config.kvs_name) + "'"); + } + auto lookupKey = dnsdist::configuration::yaml::getRegisteredTypeByName(std::string(config.lookup_key_name)); + if (!lookupKey && !(dnsdist::configuration::yaml::s_inClientMode || dnsdist::configuration::yaml::s_inConfigCheckMode)) { + throw std::runtime_error("Unable to find the key-value lookup key named '" + std::string(config.lookup_key_name) + "'"); + } + auto action = dnsdist::actions::getKeyValueStoreLookupAction(kvs, lookupKey, std::string(config.destination_tag)); + return newDNSActionWrapper(std::move(action), config.name); +} + +std::shared_ptr getKeyValueStoreRangeLookupAction(const KeyValueStoreRangeLookupActionConfiguration& config) +{ + auto kvs = dnsdist::configuration::yaml::getRegisteredTypeByName(std::string(config.kvs_name)); + if (!kvs && !(dnsdist::configuration::yaml::s_inClientMode || dnsdist::configuration::yaml::s_inConfigCheckMode)) { + throw std::runtime_error("Unable to find the key-value store named '" + std::string(config.kvs_name) + "'"); + } + auto lookupKey = dnsdist::configuration::yaml::getRegisteredTypeByName(std::string(config.lookup_key_name)); + if (!lookupKey && !(dnsdist::configuration::yaml::s_inClientMode || dnsdist::configuration::yaml::s_inConfigCheckMode)) { + throw std::runtime_error("Unable to find the key-value lookup key named '" + std::string(config.lookup_key_name) + "'"); + } + auto action = dnsdist::actions::getKeyValueStoreRangeLookupAction(kvs, lookupKey, std::string(config.destination_tag)); + return newDNSActionWrapper(std::move(action), config.name); +} + +std::shared_ptr getKeyValueStoreLookupSelector(const KeyValueStoreLookupSelectorConfiguration& config) +{ + auto kvs = dnsdist::configuration::yaml::getRegisteredTypeByName(std::string(config.kvs_name)); + if (!kvs && !(dnsdist::configuration::yaml::s_inClientMode || dnsdist::configuration::yaml::s_inConfigCheckMode)) { + throw std::runtime_error("Unable to find the key-value store named '" + std::string(config.kvs_name) + "'"); + } + auto lookupKey = dnsdist::configuration::yaml::getRegisteredTypeByName(std::string(config.lookup_key_name)); + if (!lookupKey && !(dnsdist::configuration::yaml::s_inClientMode || dnsdist::configuration::yaml::s_inConfigCheckMode)) { + throw std::runtime_error("Unable to find the key-value lookup key named '" + std::string(config.lookup_key_name) + "'"); + } + auto selector = dnsdist::selectors::getKeyValueStoreLookupSelector(kvs, lookupKey); + return newDNSSelector(std::move(selector), config.name); +} + +std::shared_ptr getKeyValueStoreRangeLookupSelector(const KeyValueStoreRangeLookupSelectorConfiguration& config) +{ + auto kvs = dnsdist::configuration::yaml::getRegisteredTypeByName(std::string(config.kvs_name)); + if (!kvs && !(dnsdist::configuration::yaml::s_inClientMode || dnsdist::configuration::yaml::s_inConfigCheckMode)) { + throw std::runtime_error("Unable to find the key-value store named '" + std::string(config.kvs_name) + "'"); + } + auto lookupKey = dnsdist::configuration::yaml::getRegisteredTypeByName(std::string(config.lookup_key_name)); + if (!lookupKey && !(dnsdist::configuration::yaml::s_inClientMode || dnsdist::configuration::yaml::s_inConfigCheckMode)) { + throw std::runtime_error("Unable to find the key-value lookup key named '" + std::string(config.lookup_key_name) + "'"); + } + auto selector = dnsdist::selectors::getKeyValueStoreRangeLookupSelector(kvs, lookupKey); + return newDNSSelector(std::move(selector), config.name); +} + +std::shared_ptr getDnstapLogAction(const DnstapLogActionConfiguration& config) +{ +#if defined(DISABLE_PROTOBUF) || !defined(HAVE_FSTRM) + throw std::runtime_error("Unable to create dnstap log action: dnstap support is not enabled"); +#else + auto logger = dnsdist::configuration::yaml::getRegisteredTypeByName(std::string(config.logger_name)); + if (!logger && !(dnsdist::configuration::yaml::s_inClientMode || dnsdist::configuration::yaml::s_inConfigCheckMode)) { + throw std::runtime_error("Unable to find the dnstap logger named '" + std::string(config.logger_name) + "'"); + } + dnsdist::actions::DnstapAlterFunction alterFunc; + dnsdist::configuration::yaml::getLuaFunctionFromConfiguration(alterFunc, config.alter_function_name, config.alter_function_code, config.alter_function_file, "dnstap log action"); + auto action = dnsdist::actions::getDnstapLogAction(std::string(config.identity), logger, alterFunc); + return newDNSActionWrapper(std::move(action), config.name); +#endif +} + +std::shared_ptr getDnstapLogResponseAction(const DnstapLogResponseActionConfiguration& config) +{ +#if defined(DISABLE_PROTOBUF) || !defined(HAVE_FSTRM) + throw std::runtime_error("Unable to create dnstap log action: dnstap support is not enabled"); +#else + auto logger = dnsdist::configuration::yaml::getRegisteredTypeByName(std::string(config.logger_name)); + if (!logger && !(dnsdist::configuration::yaml::s_inClientMode || dnsdist::configuration::yaml::s_inConfigCheckMode)) { + throw std::runtime_error("Unable to find the dnstap logger named '" + std::string(config.logger_name) + "'"); + } + dnsdist::actions::DnstapAlterResponseFunction alterFunc; + dnsdist::configuration::yaml::getLuaFunctionFromConfiguration(alterFunc, config.alter_function_name, config.alter_function_code, config.alter_function_file, "dnstap log response action"); + auto action = dnsdist::actions::getDnstapLogResponseAction(std::string(config.identity), logger, alterFunc); + return newDNSResponseActionWrapper(std::move(action), config.name); +#endif +} + +std::shared_ptr getRemoteLogAction(const RemoteLogActionConfiguration& config) +{ +#if defined(DISABLE_PROTOBUF) + throw std::runtime_error("Unable to create remote log action: protobuf support is disabled"); +#else + auto logger = dnsdist::configuration::yaml::getRegisteredTypeByName(std::string(config.logger_name)); + if (!logger && !(dnsdist::configuration::yaml::s_inClientMode || dnsdist::configuration::yaml::s_inConfigCheckMode)) { + throw std::runtime_error("Unable to find the protobuf logger named '" + std::string(config.logger_name) + "'"); + } + dnsdist::actions::RemoteLogActionConfiguration actionConfig{}; + actionConfig.logger = std::move(logger); + actionConfig.serverID = std::string(config.server_id); + actionConfig.ipEncryptKey = std::string(config.ip_encrypt_key); + for (const auto& meta : config.metas) { + actionConfig.metas.emplace_back(std::string(meta.key), ProtoBufMetaKey(std::string(meta.value))); + } + if (!config.export_tags.empty()) { + actionConfig.tagsToExport = std::unordered_set(); + for (const auto& tag : config.export_tags) { + actionConfig.tagsToExport->emplace(std::string(tag)); + } + } + dnsdist::actions::ProtobufAlterFunction alterFunc; + if (dnsdist::configuration::yaml::getLuaFunctionFromConfiguration(alterFunc, config.alter_function_name, config.alter_function_code, config.alter_function_file, "remote log action")) { + actionConfig.alterQueryFunc = std::move(alterFunc); + } + auto action = dnsdist::actions::getRemoteLogAction(actionConfig); + return newDNSActionWrapper(std::move(action), config.name); +#endif +} + +std::shared_ptr getRemoteLogResponseAction(const RemoteLogResponseActionConfiguration& config) +{ +#if defined(DISABLE_PROTOBUF) + throw std::runtime_error("Unable to create remote log action: protobuf support is disabled"); +#else + auto logger = dnsdist::configuration::yaml::getRegisteredTypeByName(std::string(config.logger_name)); + if (!logger && !(dnsdist::configuration::yaml::s_inClientMode || dnsdist::configuration::yaml::s_inConfigCheckMode)) { + throw std::runtime_error("Unable to find the protobuf logger named '" + std::string(config.logger_name) + "'"); + } + dnsdist::actions::RemoteLogActionConfiguration actionConfig{}; + actionConfig.logger = std::move(logger); + actionConfig.serverID = std::string(config.server_id); + actionConfig.ipEncryptKey = std::string(config.ip_encrypt_key); + actionConfig.includeCNAME = config.include_cname; + for (const auto& meta : config.metas) { + actionConfig.metas.emplace_back(std::string(meta.key), ProtoBufMetaKey(std::string(meta.value))); + } + if (!config.export_tags.empty()) { + actionConfig.tagsToExport = std::unordered_set(); + for (const auto& tag : config.export_tags) { + actionConfig.tagsToExport->emplace(std::string(tag)); + } + } + if (!config.export_extended_errors_to_meta.empty()) { + actionConfig.exportExtendedErrorsToMeta = std::string(config.export_extended_errors_to_meta); + } + dnsdist::actions::ProtobufAlterResponseFunction alterFunc; + if (dnsdist::configuration::yaml::getLuaFunctionFromConfiguration(alterFunc, config.alter_function_name, config.alter_function_code, config.alter_function_file, "remote log response action")) { + actionConfig.alterResponseFunc = std::move(alterFunc); + } + auto action = dnsdist::actions::getRemoteLogResponseAction(actionConfig); + return newDNSResponseActionWrapper(std::move(action), config.name); +#endif +} + +void registerProtobufLogger(const ProtobufLoggerConfiguration& config) +{ +#if defined(DISABLE_PROTOBUF) + throw std::runtime_error("Unable to create protobuf logger: protobuf support is disabled"); +#else + if (dnsdist::configuration::yaml::s_inClientMode || dnsdist::configuration::yaml::s_inConfigCheckMode) { + auto object = std::shared_ptr(nullptr); + dnsdist::configuration::yaml::registerType(object, config.name); + return; + } + auto object = std::shared_ptr(std::make_shared(ComboAddress(std::string(config.address)), config.timeout, config.max_queued_entries * 100, config.reconnect_wait_time, false)); + dnsdist::configuration::yaml::registerType(object, config.name); +#endif +} + +void registerDnstapLogger(const DnstapLoggerConfiguration& config) +{ +#if defined(DISABLE_PROTOBUF) || !defined(HAVE_FSTRM) + throw std::runtime_error("Unable to create dnstap logger: dnstap support is disabled"); +#else + auto transport = boost::to_lower_copy(std::string(config.transport)); + int family{0}; + if (transport == "unix") { + family = AF_UNIX; + } + else if (transport == "tcp") { + family = AF_INET; + } + else { + throw std::runtime_error("Unsupport dnstap transport type '" + transport + "'"); + } + + if (dnsdist::configuration::yaml::s_inClientMode || dnsdist::configuration::yaml::s_inConfigCheckMode) { + auto object = std::shared_ptr(nullptr); + dnsdist::configuration::yaml::registerType(object, config.name); + return; + } + + std::unordered_map options; + options["bufferHint"] = config.buffer_hint; + options["flushTimeout"] = config.flush_timeout; + options["inputQueueSize"] = config.input_queue_size; + options["outputQueueSize"] = config.output_queue_size; + options["queueNotifyThreshold"] = config.queue_notify_threshold; + options["reopenInterval"] = config.reopen_interval; + + auto object = std::shared_ptr(std::make_shared(family, std::string(config.address), false, options)); + dnsdist::configuration::yaml::registerType(object, config.name); +#endif +} + +void registerKVSObjects(const KeyValueStoresConfiguration& config) +{ + bool createObjects = !dnsdist::configuration::yaml::s_inClientMode && !dnsdist::configuration::yaml::s_inConfigCheckMode; +#if defined(HAVE_LMDB) + for (const auto& lmdb : config.lmdb) { + auto store = createObjects ? std::shared_ptr(std::make_shared(std::string(lmdb.file_name), std::string(lmdb.database_name), lmdb.no_lock)) : std::shared_ptr(); + dnsdist::configuration::yaml::registerType(store, lmdb.name); + } +#endif /* defined(HAVE_LMDB) */ +#if defined(HAVE_CDB) + for (const auto& cdb : config.cdb) { + auto store = createObjects ? std::shared_ptr(std::make_shared(std::string(cdb.file_name), cdb.refresh_delay)) : std::shared_ptr(); + dnsdist::configuration::yaml::registerType(store, cdb.name); + } +#endif /* defined(HAVE_CDB) */ +#if defined(HAVE_LMDB) || defined(HAVE_CDB) + for (const auto& key : config.lookup_keys.source_ip_keys) { + auto lookup = createObjects ? std::shared_ptr(std::make_shared(key.v4_mask, key.v6_mask, key.include_port)) : std::shared_ptr(); + dnsdist::configuration::yaml::registerType(lookup, key.name); + } + for (const auto& key : config.lookup_keys.qname_keys) { + auto lookup = createObjects ? std::shared_ptr(std::make_shared(key.wire_format)) : std::shared_ptr(); + dnsdist::configuration::yaml::registerType(lookup, key.name); + } + for (const auto& key : config.lookup_keys.suffix_keys) { + auto lookup = createObjects ? std::shared_ptr(std::make_shared(key.minimum_labels, key.wire_format)) : std::shared_ptr(); + dnsdist::configuration::yaml::registerType(lookup, key.name); + } + for (const auto& key : config.lookup_keys.tag_keys) { + auto lookup = createObjects ? std::shared_ptr(std::make_shared(std::string(key.tag))) : std::shared_ptr(); + dnsdist::configuration::yaml::registerType(lookup, key.name); + } +#endif /* defined(HAVE_LMDB) || defined(HAVE_CDB) */ +} + +std::shared_ptr getLuaSelector(const LuaSelectorConfiguration& config) +{ + dnsdist::selectors::LuaSelectorFunction function; + if (!dnsdist::configuration::yaml::getLuaFunctionFromConfiguration(function, config.function_name, config.function_code, config.function_file, "Lua selector")) { + throw std::runtime_error("Unable to create a Lua selector: no valid function name, Lua code or Lua file"); + } + auto selector = dnsdist::selectors::getLuaSelector(function); + return newDNSSelector(std::move(selector), config.name); +} + +std::shared_ptr getLuaFFISelector(const LuaFFISelectorConfiguration& config) +{ + dnsdist::selectors::LuaSelectorFFIFunction function; + if (!dnsdist::configuration::yaml::getLuaFunctionFromConfiguration(function, config.function_name, config.function_code, config.function_file, "Lua FFI selector")) { + throw std::runtime_error("Unable to create a Lua FFI selector: no valid function name, Lua code or Lua file"); + } + auto selector = dnsdist::selectors::getLuaFFISelector(function); + return newDNSSelector(std::move(selector), config.name); +} + +std::shared_ptr getAndSelector(const AndSelectorConfiguration& config) +{ + std::vector> selectors; + selectors.reserve(config.selectors.size()); + for (const auto& subSelector : config.selectors) { + selectors.emplace_back(subSelector.selector->d_rule); + } + auto selector = dnsdist::selectors::getAndSelector(selectors); + return newDNSSelector(std::move(selector), config.name); +} + +std::shared_ptr getOrSelector(const OrSelectorConfiguration& config) +{ + std::vector> selectors; + selectors.reserve(config.selectors.size()); + for (const auto& subSelector : config.selectors) { + selectors.emplace_back(subSelector.selector->d_rule); + } + auto selector = dnsdist::selectors::getOrSelector(selectors); + return newDNSSelector(std::move(selector), config.name); +} + +std::shared_ptr getNotSelector(const NotSelectorConfiguration& config) +{ + auto selector = dnsdist::selectors::getNotSelector(config.selector.selector->d_rule); + return newDNSSelector(std::move(selector), config.name); +} + +std::shared_ptr getByNameSelector(const ByNameSelectorConfiguration& config) +{ + return dnsdist::configuration::yaml::getRegisteredTypeByName(config.selector_name); +} + +// NOLINTNEXTLINE(bugprone-suspicious-include) +#include "dnsdist-rust-bridge-actions-generated.cc" +// NOLINTNEXTLINE(bugprone-suspicious-include) +#include "dnsdist-rust-bridge-selectors-generated.cc" +} +#endif /* defined(HAVE_YAML_CONFIGURATION) */ diff --git a/pdns/dnsdistdist/dnsdist-configuration-yaml.hh b/pdns/dnsdistdist/dnsdist-configuration-yaml.hh new file mode 100644 index 000000000000..467b9df99f4d --- /dev/null +++ b/pdns/dnsdistdist/dnsdist-configuration-yaml.hh @@ -0,0 +1,31 @@ +/* + * 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 + +namespace dnsdist::configuration::yaml +{ +bool loadConfigurationFromFile(const std::string& fileName, bool isClient, bool configCheck); +} diff --git a/pdns/dnsdistdist/dnsdist-configuration.hh b/pdns/dnsdistdist/dnsdist-configuration.hh index bfde4514724a..435cc0964db9 100644 --- a/pdns/dnsdistdist/dnsdist-configuration.hh +++ b/pdns/dnsdistdist/dnsdist-configuration.hh @@ -59,6 +59,7 @@ struct ImmutableConfiguration std::set d_capabilitiesToRetain; std::vector d_tcpFastOpenKey; std::vector> d_frontends; + std::string d_snmpDaemonSocketPath; #ifdef __linux__ // On Linux this gives us 128k pending queries (default is 8192 queries), // which should be enough to deal with huge spikes @@ -78,7 +79,7 @@ struct ImmutableConfiguration uint64_t d_outgoingTCPCleanupInterval{60}; uint64_t d_outgoingDoHMaxIdlePerBackend{10}; uint64_t d_outgoingTCPMaxIdlePerBackend{10}; - uint64_t d_maxTCPClientThreads{0}; + uint64_t d_maxTCPClientThreads{10}; size_t d_maxTCPConnectionsPerClient{0}; size_t d_udpVectorSize{1}; size_t d_ringsCapacity{10000}; @@ -93,6 +94,8 @@ struct ImmutableConfiguration bool d_randomizeIDsToBackend{false}; bool d_ringsRecordQueries{true}; bool d_ringsRecordResponses{true}; + bool d_snmpEnabled{false}; + bool d_snmpTrapsEnabled{false}; }; /* this part of the configuration can be updated at runtime via @@ -152,8 +155,6 @@ struct RuntimeConfiguration bool d_servFailOnNoPolicy{false}; bool d_allowEmptyResponse{false}; bool d_dropEmptyQueries{false}; - bool d_snmpEnabled{false}; - bool d_snmpTrapsEnabled{false}; bool d_consoleEnabled{false}; bool d_logConsoleConnections{true}; bool d_addEDNSToSelfGeneratedResponses{true}; diff --git a/pdns/dnsdistdist/dnsdist-dnsparser.cc b/pdns/dnsdistdist/dnsdist-dnsparser.cc index bfe0be3e07ed..81db3d91515d 100644 --- a/pdns/dnsdistdist/dnsdist-dnsparser.cc +++ b/pdns/dnsdistdist/dnsdist-dnsparser.cc @@ -213,6 +213,28 @@ namespace PacketMangling memcpy(packet, &header, sizeof(header)); return true; } + + void restrictDNSPacketTTLs(PacketBuffer& packet, uint32_t minimumValue, uint32_t maximumValue, const std::unordered_set& types) + { + auto visitor = [minimumValue, maximumValue, types](uint8_t section, uint16_t qclass, uint16_t qtype, uint32_t ttl) { + if (!types.empty() && qclass == QClass::IN && types.count(qtype) == 0) { + return ttl; + } + + if (minimumValue > 0) { + if (ttl < minimumValue) { + ttl = minimumValue; + } + } + if (ttl > maximumValue) { + ttl = maximumValue; + } + return ttl; + }; + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) + editDNSPacketTTL(reinterpret_cast(packet.data()), packet.size(), visitor); + } + } void setResponseHeadersFromConfig(dnsheader& dnsheader, const ResponseConfig& config) diff --git a/pdns/dnsdistdist/dnsdist-dnsparser.hh b/pdns/dnsdistdist/dnsdist-dnsparser.hh index 67d74a344205..cbb0ff79ea80 100644 --- a/pdns/dnsdistdist/dnsdist-dnsparser.hh +++ b/pdns/dnsdistdist/dnsdist-dnsparser.hh @@ -59,6 +59,7 @@ namespace PacketMangling { bool editDNSHeaderFromPacket(PacketBuffer& packet, const std::function& editFunction); bool editDNSHeaderFromRawPacket(void* packet, const std::function& editFunction); + void restrictDNSPacketTTLs(PacketBuffer& packet, uint32_t minimumValue, uint32_t maximumValue = std::numeric_limits::max(), const std::unordered_set& types = {}); } struct ResponseConfig diff --git a/pdns/dnsdistdist/dnsdist-doh-common.cc b/pdns/dnsdistdist/dnsdist-doh-common.cc index df3c01d8a215..c533cc7e8cd6 100644 --- a/pdns/dnsdistdist/dnsdist-doh-common.cc +++ b/pdns/dnsdistdist/dnsdist-doh-common.cc @@ -21,87 +21,9 @@ */ #include "base64.hh" #include "dnsdist-doh-common.hh" -#include "dnsdist-rules.hh" +#include "dnsdist.hh" #ifdef HAVE_DNS_OVER_HTTPS - -HTTPHeaderRule::HTTPHeaderRule(const std::string& header, const std::string& regex) : - d_header(toLower(header)), d_regex(regex), d_visual("http[" + header + "] ~ " + regex) -{ -} - -bool HTTPHeaderRule::matches(const DNSQuestion* dq) const -{ - if (dq->ids.du) { - const auto& headers = dq->ids.du->getHTTPHeaders(); - for (const auto& header : headers) { - if (header.first == d_header) { - return d_regex.match(header.second); - } - } - return false; - } - if (dq->ids.doh3u) { - const auto& headers = dq->ids.doh3u->getHTTPHeaders(); - for (const auto& header : headers) { - if (header.first == d_header) { - return d_regex.match(header.second); - } - } - return false; - } - return false; -} - -string HTTPHeaderRule::toString() const -{ - return d_visual; -} - -HTTPPathRule::HTTPPathRule(std::string path) : - d_path(std::move(path)) -{ -} - -bool HTTPPathRule::matches(const DNSQuestion* dq) const -{ - if (dq->ids.du) { - const auto path = dq->ids.du->getHTTPPath(); - return d_path == path; - } - if (dq->ids.doh3u) { - return dq->ids.doh3u->getHTTPPath() == d_path; - } - return false; -} - -string HTTPPathRule::toString() const -{ - return "url path == " + d_path; -} - -HTTPPathRegexRule::HTTPPathRegexRule(const std::string& regex) : - d_regex(regex), d_visual("http path ~ " + regex) -{ -} - -bool HTTPPathRegexRule::matches(const DNSQuestion* dq) const -{ - if (dq->ids.du) { - const auto path = dq->ids.du->getHTTPPath(); - return d_regex.match(path); - } - if (dq->ids.doh3u) { - return d_regex.match(dq->ids.doh3u->getHTTPPath()); - } - return false; -} - -string HTTPPathRegexRule::toString() const -{ - return d_visual; -} - void DOHFrontend::rotateTicketsKey(time_t now) { return d_tlsContext.rotateTicketsKey(now); diff --git a/pdns/dnsdistdist/dnsdist-dynblocks.cc b/pdns/dnsdistdist/dnsdist-dynblocks.cc index 7912689986f9..9fd8125d3c35 100644 --- a/pdns/dnsdistdist/dnsdist-dynblocks.cc +++ b/pdns/dnsdistdist/dnsdist-dynblocks.cc @@ -1039,6 +1039,24 @@ void clearSuffixDynamicRules() setSuffixDynamicRules(std::move(emptySMT)); } +LockGuarded>> s_registeredDynamicBlockGroups; + +void registerGroup(std::shared_ptr& group) +{ + s_registeredDynamicBlockGroups.lock()->push_back(group); +} + +void runRegisteredGroups(LuaContext& luaCtx) +{ + // only used to make sure we hold the Lua context lock + (void)luaCtx; + timespec now{}; + gettime(&now); + for (auto& group : *s_registeredDynamicBlockGroups.lock()) { + group->apply(now); + } +} + } #endif /* DISABLE_DYNBLOCKS */ diff --git a/pdns/dnsdistdist/dnsdist-dynblocks.hh b/pdns/dnsdistdist/dnsdist-dynblocks.hh index 7c240f5fd26c..7f93216d3f5a 100644 --- a/pdns/dnsdistdist/dnsdist-dynblocks.hh +++ b/pdns/dnsdistdist/dnsdist-dynblocks.hh @@ -26,6 +26,7 @@ #include "dolog.hh" #include "dnsdist-rings.hh" +#include "gettime.hh" #include "statnode.hh" extern "C" @@ -465,5 +466,8 @@ void setClientAddressDynamicRules(ClientAddressDynamicRules&& rules); void setSuffixDynamicRules(SuffixDynamicRules&& rules); void clearClientAddressDynamicRules(); void clearSuffixDynamicRules(); + +void registerGroup(std::shared_ptr& group); +void runRegisteredGroups(LuaContext& luaCtx); } #endif /* DISABLE_DYNBLOCKS */ diff --git a/pdns/dnsdistdist/dnsdist-lbpolicies.cc b/pdns/dnsdistdist/dnsdist-lbpolicies.cc index 298565cdd6ab..d5659dae5d7a 100644 --- a/pdns/dnsdistdist/dnsdist-lbpolicies.cc +++ b/pdns/dnsdistdist/dnsdist-lbpolicies.cc @@ -401,3 +401,18 @@ std::shared_ptr ServerPolicy::getSelectedBackend(const ServerPo return selectedBackend; } + +namespace dnsdist::lbpolicies +{ +const std::vector>& getBuiltInPolicies() +{ + static const std::vector> s_policies{ + std::make_shared("firstAvailable", firstAvailable, false), + std::make_shared("roundrobin", roundrobin, false), + std::make_shared("wrandom", wrandom, false), + std::make_shared("whashed", whashed, false), + std::make_shared("chashed", chashed, false), + std::make_shared("leastOutstanding", leastOutstanding, false)}; + return s_policies; +} +} diff --git a/pdns/dnsdistdist/dnsdist-lbpolicies.hh b/pdns/dnsdistdist/dnsdist-lbpolicies.hh index 3ddc4ec592d5..d975988742ee 100644 --- a/pdns/dnsdistdist/dnsdist-lbpolicies.hh +++ b/pdns/dnsdistdist/dnsdist-lbpolicies.hh @@ -106,3 +106,10 @@ std::shared_ptr whashedFromHash(const ServerPolicy::NumberedSer 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); + +#include + +namespace dnsdist::lbpolicies +{ +const std::vector>& getBuiltInPolicies(); +} diff --git a/pdns/dnsdistdist/dnsdist-lua-actions-generated.cc b/pdns/dnsdistdist/dnsdist-lua-actions-generated.cc new file mode 100644 index 000000000000..885a11c664e8 --- /dev/null +++ b/pdns/dnsdistdist/dnsdist-lua-actions-generated.cc @@ -0,0 +1,70 @@ +// !! This file has been generated by dnsdist-rules-generator.py, do not edit by hand!! +luaCtx.writeFunction("AllowAction", []() { + return dnsdist::actions::getAllowAction(); +}); +luaCtx.writeFunction("DelayAction", [](uint32_t msec) { + return dnsdist::actions::getDelayAction(msec); +}); +luaCtx.writeFunction("DropAction", []() { + return dnsdist::actions::getDropAction(); +}); +luaCtx.writeFunction("SetEDNSOptionAction", [](uint32_t code, std::string data) { + return dnsdist::actions::getSetEDNSOptionAction(code, data); +}); +luaCtx.writeFunction("LogAction", [](boost::optional file_name, boost::optional binary, boost::optional append, boost::optional buffered, boost::optional verbose_only, boost::optional include_timestamp) { + return dnsdist::actions::getLogAction(file_name ? *file_name : "", binary ? *binary : true, append ? *append : false, buffered ? *buffered : false, verbose_only ? *verbose_only : true, include_timestamp ? *include_timestamp : false); +}); +luaCtx.writeFunction("LuaFFIPerThreadAction", [](std::string code) { + return dnsdist::actions::getLuaFFIPerThreadAction(code); +}); +luaCtx.writeFunction("NoneAction", []() { + return dnsdist::actions::getNoneAction(); +}); +luaCtx.writeFunction("PoolAction", [](std::string pool_name, boost::optional stop_processing) { + return dnsdist::actions::getPoolAction(pool_name, stop_processing ? *stop_processing : true); +}); +luaCtx.writeFunction("QPSAction", [](uint32_t limit) { + return dnsdist::actions::getQPSAction(limit); +}); +luaCtx.writeFunction("QPSPoolAction", [](uint32_t limit, std::string pool_name, boost::optional stop_processing) { + return dnsdist::actions::getQPSPoolAction(limit, pool_name, stop_processing ? *stop_processing : true); +}); +luaCtx.writeFunction("SetAdditionalProxyProtocolValueAction", [](uint8_t proxy_type, std::string value) { + return dnsdist::actions::getSetAdditionalProxyProtocolValueAction(proxy_type, value); +}); +luaCtx.writeFunction("SetDisableECSAction", []() { + return dnsdist::actions::getSetDisableECSAction(); +}); +luaCtx.writeFunction("SetDisableValidationAction", []() { + return dnsdist::actions::getSetDisableValidationAction(); +}); +luaCtx.writeFunction("SetECSOverrideAction", [](bool override_existing) { + return dnsdist::actions::getSetECSOverrideAction(override_existing); +}); +luaCtx.writeFunction("SetECSPrefixLengthAction", [](uint16_t ipv4, uint16_t ipv6) { + return dnsdist::actions::getSetECSPrefixLengthAction(ipv4, ipv6); +}); +luaCtx.writeFunction("SetExtendedDNSErrorAction", [](uint16_t info_code, boost::optional extra_text) { + return dnsdist::actions::getSetExtendedDNSErrorAction(info_code, extra_text ? *extra_text : ""); +}); +luaCtx.writeFunction("SetMacAddrAction", [](uint32_t code) { + return dnsdist::actions::getSetMacAddrAction(code); +}); +luaCtx.writeFunction("SetNoRecurseAction", []() { + return dnsdist::actions::getSetNoRecurseAction(); +}); +luaCtx.writeFunction("SetSkipCacheAction", []() { + return dnsdist::actions::getSetSkipCacheAction(); +}); +luaCtx.writeFunction("SetTagAction", [](std::string tag, std::string value) { + return dnsdist::actions::getSetTagAction(tag, value); +}); +luaCtx.writeFunction("SetTempFailureCacheTTLAction", [](uint32_t ttl) { + return dnsdist::actions::getSetTempFailureCacheTTLAction(ttl); +}); +luaCtx.writeFunction("SNMPTrapAction", [](boost::optional reason) { + return dnsdist::actions::getSNMPTrapAction(reason ? *reason : ""); +}); +luaCtx.writeFunction("TCAction", []() { + return dnsdist::actions::getTCAction(); +}); diff --git a/pdns/dnsdistdist/dnsdist-lua-actions.cc b/pdns/dnsdistdist/dnsdist-lua-actions.cc index 6a89743d58d6..65cd1d085338 100644 --- a/pdns/dnsdistdist/dnsdist-lua-actions.cc +++ b/pdns/dnsdistdist/dnsdist-lua-actions.cc @@ -20,2371 +20,15 @@ * 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-actions-factory.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-rule-chains.hh" -#include "dnsdist-snmp.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]] dnsdist::ResponseConfig& getResponseConfig() - { - return d_responseConfig; - } - -private: - dnsdist::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]] dnsdist::ResponseConfig& getResponseConfig() - { - return d_responseConfig; - } - -private: - dnsdist::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_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 - { - if (!dnsdist::svc::generateSVCResponse(*dnsquestion, d_payloads, d_additionals4, d_additionals6, d_responseConfig)) { - return Action::None; - } - - return Action::HeaderModify; - } - - [[nodiscard]] std::string toString() const override - { - return "spoof SVC record "; - } - - [[nodiscard]] dnsdist::ResponseConfig& getResponseConfig() - { - return d_responseConfig; - } - -private: - dnsdist::ResponseConfig d_responseConfig; - std::vector> d_payloads{}; - std::set> d_additionals4{}; - std::set> d_additionals6{}; -}; - -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 (dnsdist::configuration::getCurrentRuntimeConfiguration().d_addEDNSToSelfGeneratedResponses && queryHasEDNS(*dnsquestion)) { - hadEDNS = true; - dnssecOK = ((dnsdist::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)); - - 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; - }); - } - } - 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) { - addEDNS(dnsquestion->getMutableData(), dnsquestion->getMaximumSize(), dnssecOK, dnsdist::configuration::getCurrentRuntimeConfiguration().d_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(), dnsdist::configuration::s_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 || dnsdist::configuration::getCurrentRuntimeConfiguration().d_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 || dnsdist::configuration::getCurrentRuntimeConfiguration().d_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); - } - -#ifdef 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 && dnsdist::configuration::getCurrentRuntimeConfiguration().d_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); - } - -#ifdef 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 != nullptr && dnsdist::configuration::getCurrentRuntimeConfiguration().d_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; -}; - -#if defined(HAVE_DNS_OVER_HTTPS) || defined(HAVE_DNS_OVER_HTTP3) -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 defined(HAVE_DNS_OVER_HTTPS) - if (dnsquestion->ids.du) { - 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; - } -#endif /* HAVE_DNS_OVER_HTTPS */ -#if defined(HAVE_DNS_OVER_HTTP3) - if (dnsquestion->ids.doh3u) { - dnsquestion->ids.doh3u->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; - } -#endif /* HAVE_DNS_OVER_HTTP3 */ - return Action::None; - } - - [[nodiscard]] std::string toString() const override - { - return "return an HTTP status of " + std::to_string(d_code); - } - - [[nodiscard]] dnsdist::ResponseConfig& getResponseConfig() - { - return d_responseConfig; - } - -private: - dnsdist::ResponseConfig d_responseConfig; - PacketBuffer d_body; - std::string d_contentType; - int d_code; -}; -#endif /* HAVE_DNS_OVER_HTTPS || HAVE_DNS_OVER_HTTP3 */ - -#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]] dnsdist::ResponseConfig& getResponseConfig() - { - return d_responseConfig; - } - -private: - dnsdist::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; -}; +#include "dnstap.hh" +#include "remote_logger.hh" template static void addAction(IdentifierT identifier, const luadnsrule_t& var, const std::shared_ptr& action, boost::optional& params) @@ -2405,12 +49,25 @@ static void addAction(IdentifierT identifier, const luadnsrule_t& var, const std using responseParams_t = std::unordered_map>; -static void parseResponseConfig(boost::optional& vars, dnsdist::ResponseConfig& config) +static dnsdist::ResponseConfig parseResponseConfig(boost::optional& vars) { + dnsdist::ResponseConfig config; getOptionalValue(vars, "ttl", config.ttl); getOptionalValue(vars, "aa", config.setAA); getOptionalValue(vars, "ad", config.setAD); getOptionalValue(vars, "ra", config.setRA); + return config; +} + +template +static std::vector convertLuaArrayToRegular(const LuaArray& luaArray) +{ + std::vector out; + out.reserve(luaArray.size()); + for (const auto& entry : luaArray) { + out.emplace_back(entry.second); + } + return out; } // NOLINTNEXTLINE(readability-function-cognitive-complexity): this function declares Lua bindings, even with a good refactoring it will likely blow up the threshold @@ -2480,43 +137,20 @@ void setupLuaActions(LuaContext& luaCtx) 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("LuaAction", [](dnsdist::actions::LuaActionFunction function) { + return dnsdist::actions::getLuaAction(std::move(function)); }); - luaCtx.writeFunction("PoolAction", [](const std::string& poolname, boost::optional stopProcessing) { - return std::shared_ptr(new PoolAction(poolname, stopProcessing ? *stopProcessing : true)); + luaCtx.writeFunction("LuaFFIAction", [](dnsdist::actions::LuaActionFFIFunction function) { + return dnsdist::actions::getLuaFFIAction(std::move(function)); }); - luaCtx.writeFunction("QPSAction", [](int limit) { - return std::shared_ptr(new QPSAction(limit)); + luaCtx.writeFunction("LuaResponseAction", [](dnsdist::actions::LuaResponseActionFunction function) { + return dnsdist::actions::getLuaResponseAction(std::move(function)); }); - 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("LuaFFIResponseAction", [](dnsdist::actions::LuaResponseActionFFIFunction function) { + return dnsdist::actions::getLuaFFIResponseAction(std::move(function)); }); luaCtx.writeFunction("SpoofAction", [](LuaTypeOrArrayOf inp, boost::optional vars) { @@ -2531,25 +165,24 @@ void setupLuaActions(LuaContext& luaCtx) } } - auto ret = std::shared_ptr(new SpoofAction(addrs)); - auto spoofaction = std::dynamic_pointer_cast(ret); - parseResponseConfig(vars, spoofaction->getResponseConfig()); + auto responseConfig = parseResponseConfig(vars); checkAllParametersConsumed("SpoofAction", vars); + auto ret = dnsdist::actions::getSpoofAction(addrs, responseConfig); 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()); + auto responseConfig = parseResponseConfig(vars); + checkAllParametersConsumed("SpoofAction", vars); + auto svcParams = convertLuaArrayToRegular(parameters); + auto ret = dnsdist::actions::getSpoofSVCAction(svcParams, responseConfig); 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()); + auto responseConfig = parseResponseConfig(vars); checkAllParametersConsumed("SpoofCNAMEAction", vars); + auto ret = dnsdist::actions::getSpoofAction(DNSName(cname), responseConfig); return ret; }); @@ -2573,10 +206,9 @@ void setupLuaActions(LuaContext& luaCtx) 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()); + auto responseConfig = parseResponseConfig(vars); checkAllParametersConsumed("SpoofRawAction", vars); + auto ret = dnsdist::actions::getSpoofAction(raws, qtypeForAnyParam, responseConfig); return ret; }); @@ -2584,46 +216,11 @@ void setupLuaActions(LuaContext& luaCtx) 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)); + // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic) + auto ret = dnsdist::actions::getSpoofAction(PacketBuffer(response.data(), response.data() + 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) { @@ -2632,30 +229,30 @@ void setupLuaActions(LuaContext& luaCtx) capTypes.insert(QType(type)); } } - return std::shared_ptr(new LimitTTLResponseAction(min, max, capTypes)); + return dnsdist::actions::getLimitTTLResponseAction(min, max, capTypes); }); luaCtx.writeFunction("SetMinTTLResponseAction", [](uint32_t min) { - return std::shared_ptr(new LimitTTLResponseAction(min)); + return dnsdist::actions::getLimitTTLResponseAction(min); }); luaCtx.writeFunction("SetMaxTTLResponseAction", [](uint32_t max) { - return std::shared_ptr(new LimitTTLResponseAction(0, max)); + return dnsdist::actions::getLimitTTLResponseAction(0, max); }); luaCtx.writeFunction("SetMaxReturnedTTLAction", [](uint32_t max) { - return std::shared_ptr(new MaxReturnedTTLAction(max)); + return dnsdist::actions::getSetMaxReturnedTTLAction(max); }); luaCtx.writeFunction("SetMaxReturnedTTLResponseAction", [](uint32_t max) { - return std::shared_ptr(new MaxReturnedTTLResponseAction(max)); + return dnsdist::actions::getSetMaxReturnedTTLResponseAction(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)); + return dnsdist::actions::getSetReducedTTLResponseAction(percentage); }); luaCtx.writeFunction("ClearRecordTypesResponseAction", [](LuaTypeOrArrayOf types) { @@ -2669,66 +266,25 @@ void setupLuaActions(LuaContext& luaCtx) qtypes.insert(tpair.second); } } - return std::shared_ptr(new ClearRecordTypesResponseAction(std::move(qtypes))); + return dnsdist::actions::getClearRecordTypesResponseAction(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()); + auto responseConfig = parseResponseConfig(vars); checkAllParametersConsumed("RCodeAction", vars); + auto ret = dnsdist::actions::getRCodeAction(rcode, responseConfig); 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()); + auto responseConfig = parseResponseConfig(vars); checkAllParametersConsumed("ERCodeAction", vars); + auto ret = dnsdist::actions::getERCodeAction(rcode, responseConfig); 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) { + 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; @@ -2739,9 +295,11 @@ void setupLuaActions(LuaContext& luaCtx) } std::string tags; - RemoteLogActionConfiguration config; + dnsdist::actions::RemoteLogActionConfiguration config; config.logger = std::move(logger); - config.alterQueryFunc = std::move(alterFunc); + if (alterFunc) { + config.alterQueryFunc = std::move(*alterFunc); + } getOptionalValue(vars, "serverID", config.serverID); getOptionalValue(vars, "ipEncryptKey", config.ipEncryptKey); getOptionalValue(vars, "exportTags", tags); @@ -2765,10 +323,10 @@ void setupLuaActions(LuaContext& luaCtx) checkAllParametersConsumed("RemoteLogAction", vars); - return std::shared_ptr(new RemoteLogAction(config)); + return dnsdist::actions::getRemoteLogAction(config); }); - luaCtx.writeFunction("RemoteLogResponseAction", [](std::shared_ptr logger, boost::optional> alterFunc, boost::optional includeCNAME, boost::optional> vars, boost::optional> metas) { + 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; @@ -2779,9 +337,11 @@ void setupLuaActions(LuaContext& luaCtx) } std::string tags; - RemoteLogActionConfiguration config; + dnsdist::actions::RemoteLogActionConfiguration config; config.logger = std::move(logger); - config.alterResponseFunc = std::move(alterFunc); + if (alterFunc) { + config.alterResponseFunc = std::move(*alterFunc); + } config.includeCNAME = includeCNAME ? *includeCNAME : false; getOptionalValue(vars, "serverID", config.serverID); getOptionalValue(vars, "ipEncryptKey", config.ipEncryptKey); @@ -2807,117 +367,78 @@ void setupLuaActions(LuaContext& luaCtx) checkAllParametersConsumed("RemoteLogResponseAction", vars); - return std::shared_ptr(new RemoteLogResponseAction(config)); + return dnsdist::actions::getRemoteLogResponseAction(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("DnstapLogAction", [](const std::string& identity, std::shared_ptr logger, boost::optional alterFunc) { + return dnsdist::actions::getDnstapLogAction(identity, std::move(logger), alterFunc ? std::move(*alterFunc) : std::optional()); }); - 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))); + luaCtx.writeFunction("DnstapLogResponseAction", [](const std::string& identity, std::shared_ptr logger, boost::optional alterFunc) { + return dnsdist::actions::getDnstapLogResponseAction(identity, std::move(logger), alterFunc ? std::move(*alterFunc) : std::optional()); }); #endif /* DISABLE_PROTOBUF */ luaCtx.writeFunction("TeeAction", [](const std::string& remote, boost::optional addECS, boost::optional local, boost::optional addProxyProtocol) { - boost::optional localAddr{boost::none}; + std::optional localAddr; 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()); + return dnsdist::actions::getTeeAction(ComboAddress(remote, 53), localAddr, addECS ? *addECS : false, addProxyProtocol ? *addProxyProtocol : false); }); luaCtx.writeFunction("SetECSAction", [](const std::string& v4Netmask, boost::optional v6Netmask) { if (v6Netmask) { - return std::shared_ptr(new SetECSAction(Netmask(v4Netmask), Netmask(*v6Netmask))); + return dnsdist::actions::getSetECSAction(v4Netmask, *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)); + return dnsdist::actions::getSetECSAction(v4Netmask); }); luaCtx.writeFunction("ContinueAction", [](std::shared_ptr action) { - return std::shared_ptr(new ContinueAction(action)); + return dnsdist::actions::getContinueAction(std::move(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()); + auto responseConfig = parseResponseConfig(vars); checkAllParametersConsumed("HTTPStatusAction", vars); + auto ret = dnsdist::actions::getHTTPStatusAction(status, PacketBuffer(body.begin(), body.end()), contentType ? *contentType : "", responseConfig); 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)); + return dnsdist::actions::getKeyValueStoreLookupAction(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)); + return dnsdist::actions::getKeyValueStoreRangeLookupAction(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{ + auto responseConfig = parseResponseConfig(vars); + checkAllParametersConsumed("NegativeAndSOAAction", vars); + dnsdist::actions::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); + auto ret = dnsdist::actions::getNegativeAndSOAAction(nxd, DNSName(zone), ttl, DNSName(mname), DNSName(rname), params, soaInAuthoritySection, responseConfig); 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 : "")); + return dnsdist::actions::getSetProxyProtocolValuesAction(values); }); - luaCtx.writeFunction("SetExtendedDNSErrorResponseAction", [](uint16_t infoCode, boost::optional extraText) { - return std::shared_ptr(new SetExtendedDNSErrorResponseAction(infoCode, extraText ? *extraText : "")); - }); +// NOLINTNEXTLINE(bugprone-suspicious-include) +#include "dnsdist-lua-actions-generated.cc" +// NOLINTNEXTLINE(bugprone-suspicious-include) +#include "dnsdist-lua-response-actions-generated.cc" } diff --git a/pdns/dnsdistdist/dnsdist-lua-bindings-dnsquestion.cc b/pdns/dnsdistdist/dnsdist-lua-bindings-dnsquestion.cc index 8be21fd3d8a1..8ae0d81704da 100644 --- a/pdns/dnsdistdist/dnsdist-lua-bindings-dnsquestion.cc +++ b/pdns/dnsdistdist/dnsdist-lua-bindings-dnsquestion.cc @@ -25,6 +25,7 @@ #include "dnsdist-ecs.hh" #include "dnsdist-internal-queries.hh" #include "dnsdist-lua.hh" +#include "dnsdist-self-answers.hh" #include "dnsdist-snmp.hh" #include "dnsparser.hh" @@ -174,7 +175,7 @@ void setupLuaBindingsDNSQuestion(LuaContext& luaCtx) luaCtx.registerFunction("sendTrap", [](const DNSQuestion& dnsQuestion, boost::optional reason) { #ifdef HAVE_NET_SNMP - if (g_snmpAgent != nullptr && dnsdist::configuration::getCurrentRuntimeConfiguration().d_snmpTrapsEnabled) { + if (g_snmpAgent != nullptr && dnsdist::configuration::getImmutableConfiguration().d_snmpTrapsEnabled) { g_snmpAgent->sendDNSTrap(dnsQuestion, reason ? *reason : ""); } #endif /* HAVE_NET_SNMP */ @@ -254,6 +255,7 @@ void setupLuaBindingsDNSQuestion(LuaContext& luaCtx) }); luaCtx.registerFunction, LuaArray>&, boost::optional)>("spoof", [](DNSQuestion& dnsQuestion, const boost::variant, LuaArray>& response, boost::optional typeForAny) { + dnsdist::ResponseConfig responseConfig; if (response.type() == typeid(LuaArray)) { std::vector data; auto responses = boost::get>(response); @@ -261,9 +263,7 @@ void setupLuaBindingsDNSQuestion(LuaContext& luaCtx) for (const auto& resp : responses) { data.push_back(resp.second); } - std::string result; - SpoofAction tempSpoofAction(data); - tempSpoofAction(&dnsQuestion, &result); + dnsdist::self_answers::generateAnswerFromIPAddresses(dnsQuestion, data, responseConfig); return; } if (response.type() == typeid(LuaArray)) { @@ -273,9 +273,7 @@ void setupLuaBindingsDNSQuestion(LuaContext& luaCtx) for (const auto& resp : responses) { data.push_back(resp.second); } - std::string result; - SpoofAction tempSpoofAction(data, typeForAny ? *typeForAny : std::optional()); - tempSpoofAction(&dnsQuestion, &result); + dnsdist::self_answers::generateAnswerFromRDataEntries(dnsQuestion, data, typeForAny ? *typeForAny : std::optional(), responseConfig); return; } }); @@ -505,7 +503,7 @@ void setupLuaBindingsDNSQuestion(LuaContext& luaCtx) luaCtx.registerFunction("sendTrap", [](const DNSResponse& dnsResponse, boost::optional reason) { #ifdef HAVE_NET_SNMP - if (g_snmpAgent != nullptr && dnsdist::configuration::getCurrentRuntimeConfiguration().d_snmpTrapsEnabled) { + if (g_snmpAgent != nullptr && dnsdist::configuration::getImmutableConfiguration().d_snmpTrapsEnabled) { g_snmpAgent->sendDNSTrap(dnsResponse, reason ? *reason : ""); } #endif /* HAVE_NET_SNMP */ diff --git a/pdns/dnsdistdist/dnsdist-lua-bindings.cc b/pdns/dnsdistdist/dnsdist-lua-bindings.cc index f08746d9f5b2..8f966b1a124f 100644 --- a/pdns/dnsdistdist/dnsdist-lua-bindings.cc +++ b/pdns/dnsdistdist/dnsdist-lua-bindings.cc @@ -87,14 +87,7 @@ void setupLuaBindings(LuaContext& luaCtx, bool client, bool configCheck) luaCtx.registerFunction("toString", &ServerPolicy::toString); luaCtx.registerFunction("__tostring", &ServerPolicy::toString); - const std::array, 6> policies = { - std::make_shared("firstAvailable", firstAvailable, false), - std::make_shared("roundrobin", roundrobin, false), - std::make_shared("wrandom", wrandom, false), - std::make_shared("whashed", whashed, false), - std::make_shared("chashed", chashed, false), - std::make_shared("leastOutstanding", leastOutstanding, false)}; - for (const auto& policy : policies) { + for (const auto& policy : dnsdist::lbpolicies::getBuiltInPolicies()) { luaCtx.writeVariable(policy->d_name, policy); } diff --git a/pdns/dnsdistdist/dnsdist-lua-configuration-items.cc b/pdns/dnsdistdist/dnsdist-lua-configuration-items.cc new file mode 100644 index 000000000000..6c07c07a10e8 --- /dev/null +++ b/pdns/dnsdistdist/dnsdist-lua-configuration-items.cc @@ -0,0 +1,239 @@ +/* + * 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 "config.h" +#include "dnsdist-configuration.hh" +#include "dnsdist-lua.hh" +#include "dolog.hh" + +namespace dnsdist::lua +{ +struct BooleanConfigurationItems +{ + const std::function mutator; +}; + +struct UnsignedIntegerConfigurationItems +{ + const std::function mutator; + const size_t maximumValue{std::numeric_limits::max()}; +}; + +struct StringConfigurationItems +{ + const std::function mutator; +}; + +struct BooleanImmutableConfigurationItems +{ + const std::function mutator; +}; +struct UnsignedIntegerImmutableConfigurationItems +{ + const std::function mutator; + const size_t maximumValue{std::numeric_limits::max()}; +}; + +struct DoubleImmutableConfigurationItems +{ + const std::function mutator; + const double minimumValue{1.0}; +}; + +// clang-format off +static const std::map s_booleanConfigItems{ + {"truncateTC", {[](dnsdist::configuration::RuntimeConfiguration& config, bool newValue) { config.d_truncateTC = newValue; }}}, + {"fixupCase", {[](dnsdist::configuration::RuntimeConfiguration& config, bool newValue) { config.d_fixupCase = newValue; }}}, + {"setECSOverride", {[](dnsdist::configuration::RuntimeConfiguration& config, bool newValue) { config.d_ecsOverride = newValue; }}}, + {"setQueryCount", {[](dnsdist::configuration::RuntimeConfiguration& config, bool newValue) { config.d_queryCountConfig.d_enabled = newValue; }}}, + {"setVerbose", {[](dnsdist::configuration::RuntimeConfiguration& config, bool newValue) { config.d_verbose = newValue; }}}, + {"setVerboseHealthChecks", {[](dnsdist::configuration::RuntimeConfiguration& config, bool newValue) { config.d_verboseHealthChecks = newValue; }}}, + {"setServFailWhenNoServer", {[](dnsdist::configuration::RuntimeConfiguration& config, bool newValue) { config.d_servFailOnNoPolicy = newValue; }}}, + {"setRoundRobinFailOnNoServer", {[](dnsdist::configuration::RuntimeConfiguration& config, bool newValue) { config.d_roundrobinFailOnNoServer = newValue; }}}, + {"setDropEmptyQueries", {[](dnsdist::configuration::RuntimeConfiguration& config, bool newValue) { config.d_dropEmptyQueries = newValue; }}}, + {"setAllowEmptyResponse", {[](dnsdist::configuration::RuntimeConfiguration& config, bool newValue) { config.d_allowEmptyResponse = newValue; }}}, + {"setConsoleConnectionsLogging", {[](dnsdist::configuration::RuntimeConfiguration& config, bool newValue) { config.d_logConsoleConnections = newValue; }}}, + {"setProxyProtocolApplyACLToProxiedClients", {[](dnsdist::configuration::RuntimeConfiguration& config, bool newValue) { config.d_applyACLToProxiedClients = newValue; }}}, + {"setAddEDNSToSelfGeneratedResponses", {[](dnsdist::configuration::RuntimeConfiguration& config, bool newValue) { config.d_addEDNSToSelfGeneratedResponses = newValue; }}}, +}; + +static const std::map s_unsignedIntegerConfigItems{ + {"setCacheCleaningDelay", {[](dnsdist::configuration::RuntimeConfiguration& config, uint64_t newValue) { config.d_cacheCleaningDelay = newValue; }, std::numeric_limits::max()}}, + {"setCacheCleaningPercentage", {[](dnsdist::configuration::RuntimeConfiguration& config, uint64_t newValue) { config.d_cacheCleaningPercentage = newValue; }, 100U}}, + {"setOutgoingTLSSessionsCacheMaxTicketsPerBackend", {[](dnsdist::configuration::RuntimeConfiguration& config, uint64_t newValue) { config.d_tlsSessionCacheMaxSessionsPerBackend = newValue; }, std::numeric_limits::max()}}, + {"setOutgoingTLSSessionsCacheCleanupDelay", {[](dnsdist::configuration::RuntimeConfiguration& config, uint64_t newValue) { config.d_tlsSessionCacheCleanupDelay = newValue; }, std::numeric_limits::max()}}, + {"setOutgoingTLSSessionsCacheMaxTicketValidity", {[](dnsdist::configuration::RuntimeConfiguration& config, uint64_t newValue) { config.d_tlsSessionCacheSessionValidity = newValue; }, std::numeric_limits::max()}}, + {"setECSSourcePrefixV4", {[](dnsdist::configuration::RuntimeConfiguration& config, uint64_t newValue) { config.d_ECSSourcePrefixV4 = newValue; }, std::numeric_limits::max()}}, + {"setECSSourcePrefixV6", {[](dnsdist::configuration::RuntimeConfiguration& config, uint64_t newValue) { config.d_ECSSourcePrefixV6 = newValue; }, std::numeric_limits::max()}}, + {"setTCPRecvTimeout", {[](dnsdist::configuration::RuntimeConfiguration& config, uint64_t newValue) { config.d_tcpRecvTimeout = newValue; }, std::numeric_limits::max()}}, + {"setTCPSendTimeout", {[](dnsdist::configuration::RuntimeConfiguration& config, uint64_t newValue) { config.d_tcpSendTimeout = newValue; }, std::numeric_limits::max()}}, + {"setMaxTCPQueriesPerConnection", {[](dnsdist::configuration::RuntimeConfiguration& config, uint64_t newValue) { config.d_maxTCPQueriesPerConn = newValue; }, std::numeric_limits::max()}}, + {"setMaxTCPConnectionDuration", {[](dnsdist::configuration::RuntimeConfiguration& config, uint64_t newValue) { config.d_maxTCPConnectionDuration = newValue; }, std::numeric_limits::max()}}, + {"setStaleCacheEntriesTTL", {[](dnsdist::configuration::RuntimeConfiguration& config, uint64_t newValue) { config.d_staleCacheEntriesTTL = newValue; }, std::numeric_limits::max()}}, + {"setConsoleOutputMaxMsgSize", {[](dnsdist::configuration::RuntimeConfiguration& config, uint64_t newValue) { config.d_consoleOutputMsgMaxSize = newValue; }, std::numeric_limits::max()}}, +#ifndef DISABLE_SECPOLL + {"setSecurityPollInterval", {[](dnsdist::configuration::RuntimeConfiguration& config, uint64_t newValue) { config.d_secPollInterval = newValue; }, std::numeric_limits::max()}}, +#endif /* DISABLE_SECPOLL */ + {"setProxyProtocolMaximumPayloadSize", {[](dnsdist::configuration::RuntimeConfiguration& config, uint64_t newValue) { config.d_proxyProtocolMaximumSize = std::max(static_cast(16), newValue); }, std::numeric_limits::max()}}, + {"setPayloadSizeOnSelfGeneratedAnswers", {[](dnsdist::configuration::RuntimeConfiguration& config, uint64_t newValue) { + if (newValue < 512) { + warnlog("setPayloadSizeOnSelfGeneratedAnswers() is set too low, using 512 instead!"); + g_outputBuffer = "setPayloadSizeOnSelfGeneratedAnswers() is set too low, using 512 instead!"; + newValue = 512; + } + if (newValue > dnsdist::configuration::s_udpIncomingBufferSize) { + warnlog("setPayloadSizeOnSelfGeneratedAnswers() is set too high, capping to %d instead!", dnsdist::configuration::s_udpIncomingBufferSize); + g_outputBuffer = "setPayloadSizeOnSelfGeneratedAnswers() is set too high, capping to " + std::to_string(dnsdist::configuration::s_udpIncomingBufferSize) + " instead"; + newValue = dnsdist::configuration::s_udpIncomingBufferSize; + } + config.d_payloadSizeSelfGenAnswers = newValue; + }, + std::numeric_limits::max()}}, +#ifndef DISABLE_DYNBLOCKS + {"setDynBlocksPurgeInterval", {[](dnsdist::configuration::RuntimeConfiguration& config, uint64_t newValue) { config.d_dynBlocksPurgeInterval = newValue; }, std::numeric_limits::max()}}, +#endif /* DISABLE_DYNBLOCKS */ +}; + +static const std::map s_stringConfigItems{ +#ifndef DISABLE_SECPOLL + {"setSecurityPollSuffix", {[](dnsdist::configuration::RuntimeConfiguration& config, const std::string& newValue) { config.d_secPollSuffix = newValue; }}}, +#endif /* DISABLE_SECPOLL */ +}; + +static const std::map s_booleanImmutableConfigItems{ + {"setRandomizedOutgoingSockets", {[](dnsdist::configuration::ImmutableConfiguration& config, bool newValue) { config.d_randomizeUDPSocketsToBackend = newValue; }}}, + {"setRandomizedIdsOverUDP", {[](dnsdist::configuration::ImmutableConfiguration& config, bool newValue) { config.d_randomizeIDsToBackend = newValue; }}}, +}; + +static const std::map s_unsignedIntegerImmutableConfigItems{ + {"setMaxTCPQueuedConnections", {[](dnsdist::configuration::ImmutableConfiguration& config, uint64_t newValue) { config.d_maxTCPQueuedConnections = newValue; }, std::numeric_limits::max()}}, + {"setMaxTCPClientThreads", {[](dnsdist::configuration::ImmutableConfiguration& config, uint64_t newValue) { config.d_maxTCPClientThreads = newValue; }, std::numeric_limits::max()}}, + {"setMaxTCPConnectionsPerClient", {[](dnsdist::configuration::ImmutableConfiguration& config, uint64_t newValue) { config.d_maxTCPConnectionsPerClient = newValue; }, std::numeric_limits::max()}}, + {"setTCPInternalPipeBufferSize", {[](dnsdist::configuration::ImmutableConfiguration& config, uint64_t newValue) { config.d_tcpInternalPipeBufferSize = newValue; }, std::numeric_limits::max()}}, + {"setMaxCachedTCPConnectionsPerDownstream", {[](dnsdist::configuration::ImmutableConfiguration& config, uint64_t newValue) { config.d_outgoingTCPMaxIdlePerBackend = newValue; }, std::numeric_limits::max()}}, + {"setTCPDownstreamCleanupInterval", {[](dnsdist::configuration::ImmutableConfiguration& config, uint64_t newValue) { config.d_outgoingTCPCleanupInterval = newValue; }, std::numeric_limits::max()}}, + {"setTCPDownstreamMaxIdleTime", {[](dnsdist::configuration::ImmutableConfiguration& config, uint64_t newValue) { config.d_outgoingTCPMaxIdleTime = newValue; }, std::numeric_limits::max()}}, +#if defined(HAVE_DNS_OVER_HTTPS) && defined(HAVE_NGHTTP2) + {"setOutgoingDoHWorkerThreads", {[](dnsdist::configuration::ImmutableConfiguration& config, uint64_t newValue) { config.d_outgoingDoHWorkers = newValue; }, std::numeric_limits::max()}}, + {"setMaxIdleDoHConnectionsPerDownstream", {[](dnsdist::configuration::ImmutableConfiguration& config, uint64_t newValue) { config.d_outgoingDoHMaxIdlePerBackend = newValue; }, std::numeric_limits::max()}}, + {"setDoHDownstreamCleanupInterval", {[](dnsdist::configuration::ImmutableConfiguration& config, uint64_t newValue) { config.d_outgoingDoHCleanupInterval = newValue; }, std::numeric_limits::max()}}, + {"setDoHDownstreamMaxIdleTime", {[](dnsdist::configuration::ImmutableConfiguration& config, uint64_t newValue) { config.d_outgoingDoHMaxIdleTime = newValue; }, std::numeric_limits::max()}}, +#endif /* HAVE_DNS_OVER_HTTPS && HAVE_NGHTTP2 */ + {"setMaxUDPOutstanding", {[](dnsdist::configuration::ImmutableConfiguration& config, uint64_t newValue) { config.d_maxUDPOutstanding = newValue; }, std::numeric_limits::max()}}, + {"setWHashedPertubation", {[](dnsdist::configuration::ImmutableConfiguration& config, uint64_t newValue) { config.d_hashPerturbation = newValue; }, std::numeric_limits::max()}}, +#ifndef DISABLE_RECVMMSG + {"setUDPMultipleMessagesVectorSize", {[](dnsdist::configuration::ImmutableConfiguration& config, uint64_t newValue) { config.d_udpVectorSize = newValue; }, std::numeric_limits::max()}}, +#endif /* DISABLE_RECVMMSG */ + {"setUDPTimeout", {[](dnsdist::configuration::ImmutableConfiguration& config, uint64_t newValue) { config.d_udpTimeout = newValue; }, std::numeric_limits::max()}}, + {"setConsoleMaximumConcurrentConnections", {[](dnsdist::configuration::ImmutableConfiguration& config, uint64_t newValue) { config.d_consoleMaxConcurrentConnections = newValue; }, std::numeric_limits::max()}}, + {"setRingBuffersLockRetries", {[](dnsdist::configuration::ImmutableConfiguration& config, uint64_t newValue) { config.d_ringsNbLockTries = newValue; }, std::numeric_limits::max()}}, +}; + +static const std::map s_doubleImmutableConfigItems{ + {"setConsistentHashingBalancingFactor", {[](dnsdist::configuration::ImmutableConfiguration& config, double newValue) { config.d_consistentHashBalancingFactor = newValue; }, 1.0}}, + {"setWeightedBalancingFactor", {[](dnsdist::configuration::ImmutableConfiguration& config, double newValue) { config.d_weightedBalancingFactor = newValue; }, 1.0}}, +}; +// clang-format on + +void setupConfigurationItems(LuaContext& luaCtx) +{ + for (const auto& item : s_booleanConfigItems) { + luaCtx.writeFunction(item.first, [&item = item.second](bool value) { + setLuaSideEffect(); + dnsdist::configuration::updateRuntimeConfiguration([value, &item](dnsdist::configuration::RuntimeConfiguration& config) { + item.mutator(config, value); + }); + }); + } + + for (const auto& item : s_unsignedIntegerConfigItems) { + luaCtx.writeFunction(item.first, [&name = item.first, &item = item.second](uint64_t value) { + setLuaSideEffect(); + checkParameterBound(name, value, item.maximumValue); + dnsdist::configuration::updateRuntimeConfiguration([value, &item](dnsdist::configuration::RuntimeConfiguration& config) { + item.mutator(config, value); + }); + }); + } + + for (const auto& item : s_stringConfigItems) { + luaCtx.writeFunction(item.first, [&item = item.second](const std::string& value) { + setLuaSideEffect(); + dnsdist::configuration::updateRuntimeConfiguration([value, &item](dnsdist::configuration::RuntimeConfiguration& config) { + item.mutator(config, value); + }); + }); + } + + for (const auto& item : s_booleanImmutableConfigItems) { + luaCtx.writeFunction(item.first, [&name = item.first, &item = item.second](bool value) { + try { + dnsdist::configuration::updateImmutableConfiguration([value, &item](dnsdist::configuration::ImmutableConfiguration& config) { + item.mutator(config, value); + }); + } + catch (const std::exception& exp) { + g_outputBuffer = name + " cannot be used at runtime!\n"; + errlog("%s cannot be used at runtime!", name); + } + }); + } + + for (const auto& item : s_unsignedIntegerImmutableConfigItems) { + luaCtx.writeFunction(item.first, [&name = item.first, &item = item.second](uint64_t value) { + checkParameterBound(name, value, item.maximumValue); + try { + dnsdist::configuration::updateImmutableConfiguration([value, &item](dnsdist::configuration::ImmutableConfiguration& config) { + item.mutator(config, value); + }); + } + catch (const std::exception& exp) { + g_outputBuffer = name + " cannot be used at runtime!\n"; + errlog("%s cannot be used at runtime!", name); + } + }); + } + for (const auto& item : s_doubleImmutableConfigItems) { + luaCtx.writeFunction(item.first, [&name = item.first, &item = item.second](double value) { + if (value != 0 && value < item.minimumValue) { + g_outputBuffer = "Invalid value passed to " + name + "()!\n"; + errlog("Invalid value passed to %s()!", name); + return; + } + + try { + dnsdist::configuration::updateImmutableConfiguration([value, &item](dnsdist::configuration::ImmutableConfiguration& config) { + item.mutator(config, value); + }); + } + catch (const std::exception& exp) { + g_outputBuffer = name + " cannot be used at runtime!\n"; + errlog("%s cannot be used at runtime!", name); + } + setLuaSideEffect(); + }); + } +} +} diff --git a/pdns/dnsdistdist/dnsdist-lua-ffi.cc b/pdns/dnsdistdist/dnsdist-lua-ffi.cc index 12f901acf536..54886c6b8d50 100644 --- a/pdns/dnsdistdist/dnsdist-lua-ffi.cc +++ b/pdns/dnsdistdist/dnsdist-lua-ffi.cc @@ -32,6 +32,7 @@ #include "dnsdist-lua.hh" #include "dnsdist-ecs.hh" #include "dnsdist-rings.hh" +#include "dnsdist-self-answers.hh" #include "dnsdist-svc.hh" #include "dnsdist-snmp.hh" #include "dolog.hh" @@ -637,16 +638,15 @@ bool dnsdist_ffi_dnsquestion_set_trailing_data(dnsdist_ffi_dnsquestion_t* dq, co void dnsdist_ffi_dnsquestion_send_trap(dnsdist_ffi_dnsquestion_t* dq, const char* reason, size_t reasonLen) { - if (g_snmpAgent != nullptr && dnsdist::configuration::getCurrentRuntimeConfiguration().d_snmpTrapsEnabled) { + if (g_snmpAgent != nullptr && dnsdist::configuration::getImmutableConfiguration().d_snmpTrapsEnabled) { g_snmpAgent->sendDNSTrap(*dq->dq, std::string(reason, reasonLen)); } } void dnsdist_ffi_dnsquestion_spoof_packet(dnsdist_ffi_dnsquestion_t* dq, const char* raw, size_t len) { - std::string result; - SpoofAction tempSpoofAction(raw, len); - tempSpoofAction(dq->dq, &result); + // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic) + dnsdist::self_answers::generateAnswerFromRawPacket(*dq->dq, PacketBuffer(raw, raw + len)); } void dnsdist_ffi_dnsquestion_spoof_raw(dnsdist_ffi_dnsquestion_t* dq, const dnsdist_ffi_raw_value_t* values, size_t valuesCount) @@ -658,9 +658,8 @@ void dnsdist_ffi_dnsquestion_spoof_raw(dnsdist_ffi_dnsquestion_t* dq, const dnsd data.emplace_back(values[idx].value, values[idx].size); } - std::string result; - SpoofAction tempSpoofAction(data, std::nullopt); - tempSpoofAction(dq->dq, &result); + dnsdist::ResponseConfig config{}; + dnsdist::self_answers::generateAnswerFromRDataEntries(*dq->dq, data, std::nullopt, config); } void dnsdist_ffi_dnsquestion_spoof_addrs(dnsdist_ffi_dnsquestion_t* dq, const dnsdist_ffi_raw_value_t* values, size_t valuesCount) @@ -687,9 +686,8 @@ void dnsdist_ffi_dnsquestion_spoof_addrs(dnsdist_ffi_dnsquestion_t* dq, const dn } } - std::string result; - SpoofAction tempSpoofAction(data); - tempSpoofAction(dq->dq, &result); + dnsdist::ResponseConfig config{}; + dnsdist::self_answers::generateAnswerFromIPAddresses(*dq->dq, data, config); } void dnsdist_ffi_dnsquestion_set_max_returned_ttl(dnsdist_ffi_dnsquestion_t* dq, uint32_t max) @@ -789,9 +787,7 @@ void dnsdist_ffi_dnsresponse_set_max_ttl(dnsdist_ffi_dnsresponse_t* dr, uint32_t void dnsdist_ffi_dnsresponse_limit_ttl(dnsdist_ffi_dnsresponse_t* dr, uint32_t min, uint32_t max) { if (dr != nullptr && dr->dr != nullptr) { - std::string result; - LimitTTLResponseAction ac(min, max); - ac(dr->dr, &result); + dnsdist::PacketMangling::restrictDNSPacketTTLs(dr->dr->getMutableData(), min, max); } } diff --git a/pdns/dnsdistdist/dnsdist-lua-response-actions-generated.cc b/pdns/dnsdistdist/dnsdist-lua-response-actions-generated.cc new file mode 100644 index 000000000000..bef903eea137 --- /dev/null +++ b/pdns/dnsdistdist/dnsdist-lua-response-actions-generated.cc @@ -0,0 +1,34 @@ +// !! This file has been generated by dnsdist-rules-generator.py, do not edit by hand!! +luaCtx.writeFunction("AllowResponseAction", []() { + return dnsdist::actions::getAllowResponseAction(); +}); +luaCtx.writeFunction("DelayResponseAction", [](uint32_t msec) { + return dnsdist::actions::getDelayResponseAction(msec); +}); +luaCtx.writeFunction("DropResponseAction", []() { + return dnsdist::actions::getDropResponseAction(); +}); +luaCtx.writeFunction("LogResponseAction", [](boost::optional file_name, boost::optional append, boost::optional buffered, boost::optional verbose_only, boost::optional include_timestamp) { + return dnsdist::actions::getLogResponseAction(file_name ? *file_name : "", append ? *append : false, buffered ? *buffered : false, verbose_only ? *verbose_only : true, include_timestamp ? *include_timestamp : false); +}); +luaCtx.writeFunction("LuaFFIPerThreadResponseAction", [](std::string code) { + return dnsdist::actions::getLuaFFIPerThreadResponseAction(code); +}); +luaCtx.writeFunction("SetExtendedDNSErrorResponseAction", [](uint16_t info_code, boost::optional extra_text) { + return dnsdist::actions::getSetExtendedDNSErrorResponseAction(info_code, extra_text ? *extra_text : ""); +}); +luaCtx.writeFunction("SetReducedTTLResponseAction", [](uint8_t percentage) { + return dnsdist::actions::getSetReducedTTLResponseAction(percentage); +}); +luaCtx.writeFunction("SetSkipCacheResponseAction", []() { + return dnsdist::actions::getSetSkipCacheResponseAction(); +}); +luaCtx.writeFunction("SetTagResponseAction", [](std::string tag, std::string value) { + return dnsdist::actions::getSetTagResponseAction(tag, value); +}); +luaCtx.writeFunction("SNMPTrapResponseAction", [](boost::optional reason) { + return dnsdist::actions::getSNMPTrapResponseAction(reason ? *reason : ""); +}); +luaCtx.writeFunction("TCResponseAction", []() { + return dnsdist::actions::getTCResponseAction(); +}); diff --git a/pdns/dnsdistdist/dnsdist-lua-rules.cc b/pdns/dnsdistdist/dnsdist-lua-rules.cc index 10937c03fcdf..44942e902f33 100644 --- a/pdns/dnsdistdist/dnsdist-lua-rules.cc +++ b/pdns/dnsdistdist/dnsdist-lua-rules.cc @@ -22,6 +22,7 @@ #include "dnsdist.hh" #include "dnsdist-lua.hh" #include "dnsdist-rules.hh" +#include "dnsdist-rules-factory.hh" #include "dnsdist-rule-chains.hh" #include "dns_random.hh" @@ -211,7 +212,7 @@ static void moveRuleToTop(IdentifierTypeT chainIdentifier) if (rules.empty()) { return; } - //coverity[auto_causes_copy] + // coverity[auto_causes_copy] auto subject = *rules.rbegin(); rules.erase(std::prev(rules.end())); rules.insert(rules.begin(), subject); @@ -228,7 +229,7 @@ static void mvRule(IdentifierTypeT chainIdentifier, unsigned int from, unsigned g_outputBuffer = "Error: attempt to move rules from/to invalid index\n"; return; } - //coverity[auto_causes_copy] + // coverity[auto_causes_copy] auto subject = rules[from]; rules.erase(rules.begin() + from); if (destination > rules.size()) { @@ -341,6 +342,12 @@ std::shared_ptr qnameSuffixRule(const boost::variant(&names); return std::shared_ptr(new SuffixMatchNodeRule(smn, quiet ? *quiet : false)); } + +template +std::optional boostToStandardOptional(const boost::optional& boostOpt) +{ + return boostOpt ? *boostOpt : std::optional(); +} } // NOLINTNEXTLINE(readability-function-cognitive-complexity): this function declares Lua bindings, even with a good refactoring it will likely blow up the threshold @@ -462,43 +469,6 @@ void setupLuaRules(LuaContext& luaCtx) }); } - 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)); - } - 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) { @@ -562,18 +532,6 @@ void setupLuaRules(LuaContext& luaCtx) 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) { @@ -596,63 +554,34 @@ void setupLuaRules(LuaContext& luaCtx) return std::shared_ptr(new QClassRule(cla)); }); - 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>& rules) { + luaCtx.writeFunction("AndRule", [](const LuaArray>& rulePairs) { + std::vector> rules; + rules.reserve(rulePairs.size()); + for (const auto& pair : rulePairs) { + rules.emplace_back(pair.second); + } return std::shared_ptr(new AndRule(rules)); }); - luaCtx.writeFunction("OrRule", [](const LuaArray>& rules) { + luaCtx.writeFunction("OrRule", [](const LuaArray>& rulePairs) { + std::vector> rules; + rules.reserve(rulePairs.size()); + for (const auto& pair : rulePairs) { + rules.emplace_back(pair.second); + } return std::shared_ptr(new OrRule(rules)); }); - 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("LuaRule", [](const dnsdist::selectors::LuaSelectorFunction& function) { + return std::shared_ptr(dnsdist::selectors::getLuaSelector(function)); }); - luaCtx.writeFunction("QNameWireLengthRule", [](uint64_t min, uint64_t max) { - return std::shared_ptr(new QNameWireLengthRule(min, max)); + luaCtx.writeFunction("LuaFFIRule", [](const dnsdist::selectors::LuaSelectorFFIFunction& function) { + return std::shared_ptr(dnsdist::selectors::getLuaFFISelector(function)); }); luaCtx.writeFunction("RCodeRule", [](uint64_t rcode) { @@ -665,36 +594,10 @@ void setupLuaRules(LuaContext& luaCtx) 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("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::make_shared(); }); - 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", [](const std::shared_ptr& tisr) { tisr->clear(); }); @@ -714,6 +617,10 @@ void setupLuaRules(LuaContext& luaCtx) tisr->toString(); }); + luaCtx.writeFunction("QNameRule", [](const std::string& qname) { + return std::shared_ptr(dnsdist::selectors::getQNameSelector(DNSName(qname))); + }); + luaCtx.writeFunction("QNameSetRule", [](const DNSNameSet& names) { return std::shared_ptr(new QNameSetRule(names)); }); @@ -728,23 +635,6 @@ void setupLuaRules(LuaContext& luaCtx) }); #endif /* defined(HAVE_LMDB) || defined(HAVE_CDB) */ - luaCtx.writeFunction("LuaRule", [](const LuaRule::func_t& func) { - return std::shared_ptr(new LuaRule(func)); - }); - - luaCtx.writeFunction("LuaFFIRule", [](const 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)); - }); +// NOLINTNEXTLINE(bugprone-suspicious-include) +#include "dnsdist-lua-selectors-generated.cc" } diff --git a/pdns/dnsdistdist/dnsdist-lua-selectors-generated.cc b/pdns/dnsdistdist/dnsdist-lua-selectors-generated.cc new file mode 100644 index 000000000000..ee31aa13fc0b --- /dev/null +++ b/pdns/dnsdistdist/dnsdist-lua-selectors-generated.cc @@ -0,0 +1,91 @@ +// !! This file has been generated by dnsdist-rules-generator.py, do not edit by hand!! +luaCtx.writeFunction("AllRule", []() { + return std::shared_ptr(dnsdist::selectors::getAllSelector()); +}); +luaCtx.writeFunction("DNSSECRule", []() { + return std::shared_ptr(dnsdist::selectors::getDNSSECSelector()); +}); +luaCtx.writeFunction("DSTPortRule", [](uint16_t port) { + return std::shared_ptr(dnsdist::selectors::getDSTPortSelector(port)); +}); +luaCtx.writeFunction("EDNSOptionRule", [](uint16_t option_code) { + return std::shared_ptr(dnsdist::selectors::getEDNSOptionSelector(option_code)); +}); +luaCtx.writeFunction("EDNSVersionRule", [](uint8_t version) { + return std::shared_ptr(dnsdist::selectors::getEDNSVersionSelector(version)); +}); +luaCtx.writeFunction("ERCodeRule", [](uint64_t rcode) { + return std::shared_ptr(dnsdist::selectors::getERCodeSelector(rcode)); +}); +luaCtx.writeFunction("HTTPHeaderRule", [](std::string header, std::string expression) { + return std::shared_ptr(dnsdist::selectors::getHTTPHeaderSelector(header, expression)); +}); +luaCtx.writeFunction("HTTPPathRule", [](std::string path) { + return std::shared_ptr(dnsdist::selectors::getHTTPPathSelector(path)); +}); +luaCtx.writeFunction("HTTPPathRegexRule", [](std::string expression) { + return std::shared_ptr(dnsdist::selectors::getHTTPPathRegexSelector(expression)); +}); +luaCtx.writeFunction("LuaFFIPerThreadRule", [](std::string code) { + return std::shared_ptr(dnsdist::selectors::getLuaFFIPerThreadSelector(code)); +}); +luaCtx.writeFunction("MaxQPSRule", [](uint32_t qps, boost::optional burst) { + return std::shared_ptr(dnsdist::selectors::getMaxQPSSelector(qps, boostToStandardOptional(burst))); +}); +luaCtx.writeFunction("MaxQPSIPRule", [](uint32_t qps, boost::optional ipv4_mask, boost::optional ipv6_mask, boost::optional burst, boost::optional expiration, boost::optional cleanup_delay, boost::optional scan_fraction, boost::optional shards) { + return std::shared_ptr(dnsdist::selectors::getMaxQPSIPSelector(qps, boostToStandardOptional(ipv4_mask), boostToStandardOptional(ipv6_mask), boostToStandardOptional(burst), boostToStandardOptional(expiration), boostToStandardOptional(cleanup_delay), boostToStandardOptional(scan_fraction), boostToStandardOptional(shards))); +}); +luaCtx.writeFunction("OpcodeRule", [](uint8_t code) { + return std::shared_ptr(dnsdist::selectors::getOpcodeSelector(code)); +}); +luaCtx.writeFunction("PayloadSizeRule", [](std::string comparison, uint16_t size) { + return std::shared_ptr(dnsdist::selectors::getPayloadSizeSelector(comparison, size)); +}); +luaCtx.writeFunction("PoolAvailableRule", [](std::string pool) { + return std::shared_ptr(dnsdist::selectors::getPoolAvailableSelector(pool)); +}); +luaCtx.writeFunction("PoolOutstandingRule", [](std::string pool, uint64_t max_outstanding) { + return std::shared_ptr(dnsdist::selectors::getPoolOutstandingSelector(pool, max_outstanding)); +}); +luaCtx.writeFunction("ProbaRule", [](double probability) { + return std::shared_ptr(dnsdist::selectors::getProbaSelector(probability)); +}); +luaCtx.writeFunction("ProxyProtocolValueRule", [](uint8_t option_type, boost::optional option_value) { + return std::shared_ptr(dnsdist::selectors::getProxyProtocolValueSelector(option_type, boostToStandardOptional(option_value))); +}); +luaCtx.writeFunction("QNameLabelsCountRule", [](uint16_t min_labels_count, uint16_t max_labels_count) { + return std::shared_ptr(dnsdist::selectors::getQNameLabelsCountSelector(min_labels_count, max_labels_count)); +}); +luaCtx.writeFunction("QNameWireLengthRule", [](uint16_t min, uint16_t max) { + return std::shared_ptr(dnsdist::selectors::getQNameWireLengthSelector(min, max)); +}); +luaCtx.writeFunction("RCodeRule", [](uint64_t rcode) { + return std::shared_ptr(dnsdist::selectors::getRCodeSelector(rcode)); +}); +luaCtx.writeFunction("RDRule", []() { + return std::shared_ptr(dnsdist::selectors::getRDSelector()); +}); +luaCtx.writeFunction("RE2Rule", [](std::string expression) { + return std::shared_ptr(dnsdist::selectors::getRE2Selector(expression)); +}); +luaCtx.writeFunction("RecordsCountRule", [](uint8_t section, uint16_t minimum, uint16_t maximum) { + return std::shared_ptr(dnsdist::selectors::getRecordsCountSelector(section, minimum, maximum)); +}); +luaCtx.writeFunction("RecordsTypeCountRule", [](uint8_t section, uint16_t record_type, uint16_t minimum, uint16_t maximum) { + return std::shared_ptr(dnsdist::selectors::getRecordsTypeCountSelector(section, record_type, minimum, maximum)); +}); +luaCtx.writeFunction("RegexRule", [](std::string expression) { + return std::shared_ptr(dnsdist::selectors::getRegexSelector(expression)); +}); +luaCtx.writeFunction("SNIRule", [](std::string server_name) { + return std::shared_ptr(dnsdist::selectors::getSNISelector(server_name)); +}); +luaCtx.writeFunction("TagRule", [](std::string tag, boost::optional value) { + return std::shared_ptr(dnsdist::selectors::getTagSelector(tag, boostToStandardOptional(value))); +}); +luaCtx.writeFunction("TCPRule", [](bool tcp) { + return std::shared_ptr(dnsdist::selectors::getTCPSelector(tcp)); +}); +luaCtx.writeFunction("TrailingDataRule", []() { + return std::shared_ptr(dnsdist::selectors::getTrailingDataSelector()); +}); diff --git a/pdns/dnsdistdist/dnsdist-lua.cc b/pdns/dnsdistdist/dnsdist-lua.cc index 4cd7ab494a89..b9119ea4969c 100644 --- a/pdns/dnsdistdist/dnsdist-lua.cc +++ b/pdns/dnsdistdist/dnsdist-lua.cc @@ -26,10 +26,6 @@ #include #include -// for OpenBSD, sys/socket.h needs to come before net/if.h -#include -#include - #include #include #include @@ -328,17 +324,9 @@ static void handleNewServerHealthCheckParameters(boost::optional& v 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; + auto availability = DownstreamState::getAvailabilityFromStr(mode); + if (availability) { + config.availability = *availability; } else { warnlog("Ignoring unknown value '%s' for 'healthCheckMode' on 'newServer'", mode); @@ -411,57 +399,18 @@ static void handleNewServerHealthCheckParameters(boost::optional& v static void handleNewServerSourceParameter(boost::optional& vars, DownstreamState::Config& config) { 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) { - /* 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 */ - dnsdist::configuration::updateImmutableConfiguration([](dnsdist::configuration::ImmutableConfiguration& currentConfig) { - currentConfig.d_capabilitiesToRetain.insert("CAP_NET_RAW"); - }); -#endif - } - else { - warnlog("Dismissing source %s because '%s' is not a valid interface name", source, config.sourceItfName); - } - } + if (getOptionalValue(vars, "source", source) <= 0) { + return; } + + DownstreamState::parseSourceParameter(source, config); } // NOLINTNEXTLINE(readability-function-cognitive-complexity,readability-function-size): 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) { + dnsdist::lua::setupConfigurationItems(luaCtx); + luaCtx.writeFunction("inClientStartup", [client]() { return client && !dnsdist::configuration::isImmutableConfigurationDone(); }); @@ -769,208 +718,6 @@ static void setupLuaConfig(LuaContext& luaCtx, bool client, bool configCheck) server->stop(); }); - struct BooleanConfigurationItems - { - const std::string name; - const std::function mutator; - }; - static const std::vector booleanConfigItems{ - {"truncateTC", [](dnsdist::configuration::RuntimeConfiguration& config, bool newValue) { config.d_truncateTC = newValue; }}, - {"fixupCase", [](dnsdist::configuration::RuntimeConfiguration& config, bool newValue) { config.d_fixupCase = newValue; }}, - {"setECSOverride", [](dnsdist::configuration::RuntimeConfiguration& config, bool newValue) { config.d_ecsOverride = newValue; }}, - {"setQueryCount", [](dnsdist::configuration::RuntimeConfiguration& config, bool newValue) { config.d_queryCountConfig.d_enabled = newValue; }}, - {"setVerbose", [](dnsdist::configuration::RuntimeConfiguration& config, bool newValue) { config.d_verbose = newValue; }}, - {"setVerboseHealthChecks", [](dnsdist::configuration::RuntimeConfiguration& config, bool newValue) { config.d_verboseHealthChecks = newValue; }}, - {"setServFailWhenNoServer", [](dnsdist::configuration::RuntimeConfiguration& config, bool newValue) { config.d_servFailOnNoPolicy = newValue; }}, - {"setRoundRobinFailOnNoServer", [](dnsdist::configuration::RuntimeConfiguration& config, bool newValue) { config.d_roundrobinFailOnNoServer = newValue; }}, - {"setDropEmptyQueries", [](dnsdist::configuration::RuntimeConfiguration& config, bool newValue) { config.d_dropEmptyQueries = newValue; }}, - {"setAllowEmptyResponse", [](dnsdist::configuration::RuntimeConfiguration& config, bool newValue) { config.d_allowEmptyResponse = newValue; }}, - {"setConsoleConnectionsLogging", [](dnsdist::configuration::RuntimeConfiguration& config, bool newValue) { config.d_logConsoleConnections = newValue; }}, - {"setProxyProtocolApplyACLToProxiedClients", [](dnsdist::configuration::RuntimeConfiguration& config, bool newValue) { config.d_applyACLToProxiedClients = newValue; }}, - {"setAddEDNSToSelfGeneratedResponses", [](dnsdist::configuration::RuntimeConfiguration& config, bool newValue) { config.d_addEDNSToSelfGeneratedResponses = newValue; }}, - }; - struct UnsignedIntegerConfigurationItems - { - const std::string name; - const std::function mutator; - const size_t maximumValue{std::numeric_limits::max()}; - }; - static const std::vector unsignedIntegerConfigItems{ - {"setCacheCleaningDelay", [](dnsdist::configuration::RuntimeConfiguration& config, uint64_t newValue) { config.d_cacheCleaningDelay = newValue; }, std::numeric_limits::max()}, - {"setCacheCleaningPercentage", [](dnsdist::configuration::RuntimeConfiguration& config, uint64_t newValue) { config.d_cacheCleaningPercentage = newValue; }, 100U}, - {"setOutgoingTLSSessionsCacheMaxTicketsPerBackend", [](dnsdist::configuration::RuntimeConfiguration& config, uint64_t newValue) { config.d_tlsSessionCacheMaxSessionsPerBackend = newValue; }, std::numeric_limits::max()}, - {"setOutgoingTLSSessionsCacheCleanupDelay", [](dnsdist::configuration::RuntimeConfiguration& config, uint64_t newValue) { config.d_tlsSessionCacheCleanupDelay = newValue; }, std::numeric_limits::max()}, - {"setOutgoingTLSSessionsCacheMaxTicketValidity", [](dnsdist::configuration::RuntimeConfiguration& config, uint64_t newValue) { config.d_tlsSessionCacheSessionValidity = newValue; }, std::numeric_limits::max()}, - {"setECSSourcePrefixV4", [](dnsdist::configuration::RuntimeConfiguration& config, uint64_t newValue) { config.d_ECSSourcePrefixV4 = newValue; }, std::numeric_limits::max()}, - {"setECSSourcePrefixV6", [](dnsdist::configuration::RuntimeConfiguration& config, uint64_t newValue) { config.d_ECSSourcePrefixV6 = newValue; }, std::numeric_limits::max()}, - {"setTCPRecvTimeout", [](dnsdist::configuration::RuntimeConfiguration& config, uint64_t newValue) { config.d_tcpRecvTimeout = newValue; }, std::numeric_limits::max()}, - {"setTCPSendTimeout", [](dnsdist::configuration::RuntimeConfiguration& config, uint64_t newValue) { config.d_tcpSendTimeout = newValue; }, std::numeric_limits::max()}, - {"setMaxTCPQueriesPerConnection", [](dnsdist::configuration::RuntimeConfiguration& config, uint64_t newValue) { config.d_maxTCPQueriesPerConn = newValue; }, std::numeric_limits::max()}, - {"setMaxTCPConnectionDuration", [](dnsdist::configuration::RuntimeConfiguration& config, uint64_t newValue) { config.d_maxTCPConnectionDuration = newValue; }, std::numeric_limits::max()}, - {"setStaleCacheEntriesTTL", [](dnsdist::configuration::RuntimeConfiguration& config, uint64_t newValue) { config.d_staleCacheEntriesTTL = newValue; }, std::numeric_limits::max()}, - {"setConsoleOutputMaxMsgSize", [](dnsdist::configuration::RuntimeConfiguration& config, uint64_t newValue) { config.d_consoleOutputMsgMaxSize = newValue; }, std::numeric_limits::max()}, -#ifndef DISABLE_SECPOLL - {"setSecurityPollInterval", [](dnsdist::configuration::RuntimeConfiguration& config, uint64_t newValue) { config.d_secPollInterval = newValue; }, std::numeric_limits::max()}, -#endif /* DISABLE_SECPOLL */ - {"setProxyProtocolMaximumPayloadSize", [](dnsdist::configuration::RuntimeConfiguration& config, uint64_t newValue) { config.d_proxyProtocolMaximumSize = std::max(static_cast(16), newValue); }, std::numeric_limits::max()}, - {"setPayloadSizeOnSelfGeneratedAnswers", [](dnsdist::configuration::RuntimeConfiguration& config, uint64_t newValue) { - if (newValue < 512) { - warnlog("setPayloadSizeOnSelfGeneratedAnswers() is set too low, using 512 instead!"); - g_outputBuffer = "setPayloadSizeOnSelfGeneratedAnswers() is set too low, using 512 instead!"; - newValue = 512; - } - if (newValue > dnsdist::configuration::s_udpIncomingBufferSize) { - warnlog("setPayloadSizeOnSelfGeneratedAnswers() is set too high, capping to %d instead!", dnsdist::configuration::s_udpIncomingBufferSize); - g_outputBuffer = "setPayloadSizeOnSelfGeneratedAnswers() is set too high, capping to " + std::to_string(dnsdist::configuration::s_udpIncomingBufferSize) + " instead"; - newValue = dnsdist::configuration::s_udpIncomingBufferSize; - } - config.d_payloadSizeSelfGenAnswers = newValue; - }, - std::numeric_limits::max()}, -#ifndef DISABLE_DYNBLOCKS - {"setDynBlocksPurgeInterval", [](dnsdist::configuration::RuntimeConfiguration& config, uint64_t newValue) { config.d_dynBlocksPurgeInterval = newValue; }, std::numeric_limits::max()}, -#endif /* DISABLE_DYNBLOCKS */ - }; - - struct StringConfigurationItems - { - const std::string name; - const std::function mutator; - }; - static const std::vector stringConfigItems{ -#ifndef DISABLE_SECPOLL - {"setSecurityPollSuffix", [](dnsdist::configuration::RuntimeConfiguration& config, const std::string& newValue) { config.d_secPollSuffix = newValue; }}, -#endif /* DISABLE_SECPOLL */ - }; - - for (const auto& item : booleanConfigItems) { - luaCtx.writeFunction(item.name, [&item](bool value) { - setLuaSideEffect(); - dnsdist::configuration::updateRuntimeConfiguration([value, &item](dnsdist::configuration::RuntimeConfiguration& config) { - item.mutator(config, value); - }); - }); - } - - for (const auto& item : unsignedIntegerConfigItems) { - luaCtx.writeFunction(item.name, [&item](uint64_t value) { - setLuaSideEffect(); - checkParameterBound(item.name, value, item.maximumValue); - dnsdist::configuration::updateRuntimeConfiguration([value, &item](dnsdist::configuration::RuntimeConfiguration& config) { - item.mutator(config, value); - }); - }); - } - - for (const auto& item : stringConfigItems) { - luaCtx.writeFunction(item.name, [&item](const std::string& value) { - setLuaSideEffect(); - dnsdist::configuration::updateRuntimeConfiguration([value, &item](dnsdist::configuration::RuntimeConfiguration& config) { - item.mutator(config, value); - }); - }); - } - - struct BooleanImmutableConfigurationItems - { - const std::string name; - const std::function mutator; - }; - static const std::vector booleanImmutableConfigItems{ - {"setRandomizedOutgoingSockets", [](dnsdist::configuration::ImmutableConfiguration& config, bool newValue) { config.d_randomizeUDPSocketsToBackend = newValue; }}, - {"setRandomizedIdsOverUDP", [](dnsdist::configuration::ImmutableConfiguration& config, bool newValue) { config.d_randomizeIDsToBackend = newValue; }}, - }; - struct UnsignedIntegerImmutableConfigurationItems - { - const std::string name; - const std::function mutator; - const size_t maximumValue{std::numeric_limits::max()}; - }; - static const std::vector unsignedIntegerImmutableConfigItems - { - {"setMaxTCPQueuedConnections", [](dnsdist::configuration::ImmutableConfiguration& config, uint64_t newValue) { config.d_maxTCPQueuedConnections = newValue; }, std::numeric_limits::max()}, - {"setMaxTCPClientThreads", [](dnsdist::configuration::ImmutableConfiguration& config, uint64_t newValue) { config.d_maxTCPClientThreads = newValue; }, std::numeric_limits::max()}, - {"setMaxTCPConnectionsPerClient", [](dnsdist::configuration::ImmutableConfiguration& config, uint64_t newValue) { config.d_maxTCPConnectionsPerClient = newValue; }, std::numeric_limits::max()}, - {"setTCPInternalPipeBufferSize", [](dnsdist::configuration::ImmutableConfiguration& config, uint64_t newValue) { config.d_tcpInternalPipeBufferSize = newValue; }, std::numeric_limits::max()}, - {"setMaxCachedTCPConnectionsPerDownstream", [](dnsdist::configuration::ImmutableConfiguration& config, uint64_t newValue) { config.d_outgoingTCPMaxIdlePerBackend = newValue; }, std::numeric_limits::max()}, - {"setTCPDownstreamCleanupInterval", [](dnsdist::configuration::ImmutableConfiguration& config, uint64_t newValue) { config.d_outgoingTCPCleanupInterval = newValue; }, std::numeric_limits::max()}, - {"setTCPDownstreamMaxIdleTime", [](dnsdist::configuration::ImmutableConfiguration& config, uint64_t newValue) { config.d_outgoingTCPMaxIdleTime = newValue; }, std::numeric_limits::max()}, -#if defined(HAVE_DNS_OVER_HTTPS) && defined(HAVE_NGHTTP2) - {"setOutgoingDoHWorkerThreads", [](dnsdist::configuration::ImmutableConfiguration& config, uint64_t newValue) { config.d_outgoingDoHWorkers = newValue; }, std::numeric_limits::max()}, - {"setMaxIdleDoHConnectionsPerDownstream", [](dnsdist::configuration::ImmutableConfiguration& config, uint64_t newValue) { config.d_outgoingDoHMaxIdlePerBackend = newValue; }, std::numeric_limits::max()}, - {"setDoHDownstreamCleanupInterval", [](dnsdist::configuration::ImmutableConfiguration& config, uint64_t newValue) { config.d_outgoingDoHCleanupInterval = newValue; }, std::numeric_limits::max()}, - {"setDoHDownstreamMaxIdleTime", [](dnsdist::configuration::ImmutableConfiguration& config, uint64_t newValue) { config.d_outgoingDoHMaxIdleTime = newValue; }, std::numeric_limits::max()}, -#endif /* HAVE_DNS_OVER_HTTPS && HAVE_NGHTTP2 */ - {"setMaxUDPOutstanding", [](dnsdist::configuration::ImmutableConfiguration& config, uint64_t newValue) { config.d_maxUDPOutstanding = newValue; }, std::numeric_limits::max()}, - {"setWHashedPertubation", [](dnsdist::configuration::ImmutableConfiguration& config, uint64_t newValue) { config.d_hashPerturbation = newValue; }, std::numeric_limits::max()}, -#ifndef DISABLE_RECVMMSG - {"setUDPMultipleMessagesVectorSize", [](dnsdist::configuration::ImmutableConfiguration& config, uint64_t newValue) { config.d_udpVectorSize = newValue; }, std::numeric_limits::max()}, -#endif /* DISABLE_RECVMMSG */ - {"setUDPTimeout", [](dnsdist::configuration::ImmutableConfiguration& config, uint64_t newValue) { config.d_udpTimeout = newValue; }, std::numeric_limits::max()}, - {"setConsoleMaximumConcurrentConnections", [](dnsdist::configuration::ImmutableConfiguration& config, uint64_t newValue) { config.d_consoleMaxConcurrentConnections = newValue; }, std::numeric_limits::max()}, - {"setRingBuffersLockRetries", [](dnsdist::configuration::ImmutableConfiguration& config, uint64_t newValue) { config.d_ringsNbLockTries = newValue; }, std::numeric_limits::max()}, - }; - - struct DoubleImmutableConfigurationItems - { - const std::string name; - const std::function mutator; - const double minimumValue{1.0}; - }; - static const std::vector doubleImmutableConfigItems{ - {"setConsistentHashingBalancingFactor", [](dnsdist::configuration::ImmutableConfiguration& config, double newValue) { config.d_consistentHashBalancingFactor = newValue; }, 1.0}, - {"setWeightedBalancingFactor", [](dnsdist::configuration::ImmutableConfiguration& config, double newValue) { config.d_weightedBalancingFactor = newValue; }, 1.0}, - }; - - for (const auto& item : booleanImmutableConfigItems) { - luaCtx.writeFunction(item.name, [&item](bool value) { - try { - dnsdist::configuration::updateImmutableConfiguration([value, &item](dnsdist::configuration::ImmutableConfiguration& config) { - item.mutator(config, value); - }); - } - catch (const std::exception& exp) { - g_outputBuffer = item.name + " cannot be used at runtime!\n"; - errlog("%s cannot be used at runtime!", item.name); - } - }); - } - - for (const auto& item : unsignedIntegerImmutableConfigItems) { - luaCtx.writeFunction(item.name, [&item](uint64_t value) { - checkParameterBound(item.name, value, item.maximumValue); - try { - dnsdist::configuration::updateImmutableConfiguration([value, &item](dnsdist::configuration::ImmutableConfiguration& config) { - item.mutator(config, value); - }); - } - catch (const std::exception& exp) { - g_outputBuffer = item.name + " cannot be used at runtime!\n"; - errlog("%s cannot be used at runtime!", item.name); - } - }); - } - for (const auto& item : doubleImmutableConfigItems) { - luaCtx.writeFunction(item.name, [&item](double value) { - if (value != 0 && value < item.minimumValue) { - g_outputBuffer = "Invalid value passed to " + item.name + "()!\n"; - errlog("Invalid value passed to %s()!", item.name); - return; - } - - try { - dnsdist::configuration::updateImmutableConfiguration([value, &item](dnsdist::configuration::ImmutableConfiguration& config) { - item.mutator(config, value); - }); - } - catch (const std::exception& exp) { - g_outputBuffer = item.name + " cannot be used at runtime!\n"; - errlog("%s cannot be used at runtime!", item.name); - } - setLuaSideEffect(); - }); - } - luaCtx.writeFunction("getVerbose", []() { return dnsdist::configuration::getCurrentRuntimeConfiguration().d_verbose; }); luaCtx.writeFunction("addACL", [](const std::string& mask) { @@ -2242,28 +1989,18 @@ static void setupLuaConfig(LuaContext& luaCtx, bool client, bool configCheck) if (client || configCheck) { return; } - if (!checkConfigurationTime("snmpAgent")) { - return; - } - - { - if (dnsdist::configuration::getCurrentRuntimeConfiguration().d_snmpEnabled) { - errlog("snmpAgent() cannot be used twice!"); - g_outputBuffer = "snmpAgent() cannot be used twice!\n"; - return; - } - } - dnsdist::configuration::updateRuntimeConfiguration([enableTraps](dnsdist::configuration::RuntimeConfiguration& config) { + dnsdist::configuration::updateImmutableConfiguration([enableTraps, &daemonSocket](dnsdist::configuration::ImmutableConfiguration& config) { config.d_snmpEnabled = true; config.d_snmpTrapsEnabled = enableTraps; + if (daemonSocket) { + config.d_snmpDaemonSocketPath = *daemonSocket; + } }); - - g_snmpAgent = std::make_unique("dnsdist", daemonSocket ? *daemonSocket : std::string()); }); luaCtx.writeFunction("sendCustomTrap", [](const std::string& str) { - if (g_snmpAgent != nullptr && dnsdist::configuration::getCurrentRuntimeConfiguration().d_snmpTrapsEnabled) { + if (g_snmpAgent != nullptr && dnsdist::configuration::getImmutableConfiguration().d_snmpTrapsEnabled) { g_snmpAgent->sendCustomTrap(str); } }); @@ -2367,57 +2104,13 @@ static void setupLuaConfig(LuaContext& luaCtx, bool client, bool configCheck) } 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 facilityIt = facilities.find(facilityStr); - if (facilityIt == facilities.end()) { + auto facilityLevel = logFacilityFromString(facilityStr); + if (!facilityLevel) { g_outputBuffer = "Unknown facility '" + facilityStr + "' passed to setSyslogFacility()!\n"; return; } - setSyslogFacility(facilityIt->second); + setSyslogFacility(*facilityLevel); } else { setSyslogFacility(boost::get(facility)); @@ -3474,7 +3167,9 @@ static void setupLuaConfig(LuaContext& luaCtx, bool client, bool configCheck) }); } -void setupLua(LuaContext& luaCtx, bool client, bool configCheck, const std::string& config) +namespace dnsdist::lua +{ +void setupLua(LuaContext& luaCtx, bool client, bool configCheck) { setupLuaActions(luaCtx); setupLuaConfig(luaCtx, client, configCheck); @@ -3497,7 +3192,13 @@ void setupLua(LuaContext& luaCtx, bool client, bool configCheck, const std::stri #ifdef LUAJIT_VERSION luaCtx.executeCode(getLuaFFIWrappers()); #endif +} +} +namespace dnsdist::configuration::lua +{ +void loadLuaConfigurationFile(LuaContext& luaCtx, const std::string& config, bool configCheck) +{ std::ifstream ifs(config); if (!ifs) { if (configCheck) { @@ -3511,3 +3212,4 @@ void setupLua(LuaContext& luaCtx, bool client, bool configCheck, const std::stri luaCtx.executeCode(ifs); } +} diff --git a/pdns/dnsdistdist/dnsdist-lua.hh b/pdns/dnsdistdist/dnsdist-lua.hh index e2be6dca1e80..09d18a643b00 100644 --- a/pdns/dnsdistdist/dnsdist-lua.hh +++ b/pdns/dnsdistdist/dnsdist-lua.hh @@ -21,145 +21,14 @@ */ #pragma once -#include - #include "dolog.hh" #include "dnsdist.hh" -#include "dnsdist-dnsparser.hh" -#include "dnsparser.hh" #include "ext/luawrapper/include/LuaContext.hpp" extern RecursiveLockGuarded g_lua; extern std::string g_outputBuffer; // locking for this is ok, as locked by g_luamutex -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]] dnsdist::ResponseConfig& getResponseConfig() - { - return d_responseConfig; - } - -private: - dnsdist::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 @@ -193,6 +62,34 @@ void setupLuaVars(LuaContext& luaCtx); void setupLuaWeb(LuaContext& luaCtx); void setupLuaLoadBalancingContext(LuaContext& luaCtx); +namespace dnsdist::lua +{ +void setupLua(LuaContext& luaCtx, bool client, bool configCheck); +void setupConfigurationItems(LuaContext& luaCtx); + +template +std::optional getFunctionFromLuaCode(const std::string& code, const std::string& context) +{ + try { + auto function = g_lua.lock()->executeCode(code); + if (!function) { + return std::nullopt; + } + return function; + } + catch (const std::exception& exp) { + warnlog("Parsing Lua code '%s' in context '%s' failed: %s", code, context, exp.what()); + } + + return std::nullopt; +} +} + +namespace dnsdist::configuration::lua +{ +void loadLuaConfigurationFile(LuaContext& luaCtx, const std::string& config, bool configCheck); +} + /** * getOptionalValue(vars, key, value) * diff --git a/pdns/dnsdistdist/dnsdist-protobuf.hh b/pdns/dnsdistdist/dnsdist-protobuf.hh index c2dee817daf3..13d035749e60 100644 --- a/pdns/dnsdistdist/dnsdist-protobuf.hh +++ b/pdns/dnsdistdist/dnsdist-protobuf.hh @@ -21,12 +21,18 @@ */ #pragma once -#include "dnsdist.hh" #include "dnsname.hh" #ifndef DISABLE_PROTOBUF +#include +#include +#include + #include "protozero.hh" +struct DNSQuestion; +struct DNSResponse; + class DNSDistProtoBufMessage { public: @@ -146,9 +152,9 @@ class ProtoBufMetaKey using TypeContainer = boost::multi_index_container< KeyTypeDescription, - indexed_by< - hashed_unique, member>, - hashed_unique, member>>>; + boost::multi_index::indexed_by< + boost::multi_index::hashed_unique, boost::multi_index::member>, + boost::multi_index::hashed_unique, boost::multi_index::member>>>; static const TypeContainer s_types; diff --git a/pdns/dnsdistdist/dnsdist-response-actions-definitions.yml b/pdns/dnsdistdist/dnsdist-response-actions-definitions.yml new file mode 100644 index 000000000000..1c9c54a281de --- /dev/null +++ b/pdns/dnsdistdist/dnsdist-response-actions-definitions.yml @@ -0,0 +1,235 @@ +--- +- name: "allow" + description: "Let these packets go through." +- name: "ClearRecordTypes" + description: "Removes given type(s) records from the response. Beware you can accidentally turn the answer into a NODATA response without a SOA record in the additional section in which case you may want to use NegativeAndSOAAction() to generate an answer, see example below. Subsequent rules are processed after this action." + skip-cpp: true + skip-rust: true + parameters: + - name: "types" + type: "Vec" + default: true + description: "List of types to remove" +- name: "delay" + description: "Delay the response by the specified amount of milliseconds (UDP-only). Note that the sending of the query to the backend, if needed, is not delayed. Only the sending of the response to the client will be delayed. Subsequent rules are processed after this action" + parameters: + - name: "msec" + type: "u32" + description: "The amount of milliseconds to delay the response" +- name: "DnstapLog" + description: "Send the current response to a remote logger as a dnstap message. ``alter-function`` is a callback, receiving a :class:`DNSResponse` and a :class:`DnstapMessage`, that can be used to modify the message. Subsequent rules are processed after this action" + skip-cpp: true + skip-rust: true + parameters: + - name: "identity" + type: "String" + description: "Server identity to store in the dnstap message" + - name: "logger_name" + type: "String" + description: "The name of dnstap logger" + - name: "alter_function_name" + type: "String" + default: "" + description: "The name of the Lua function that will alter the message" + - name: "alter_function_code" + type: "String" + default: "" + description: "The code of the Lua function that will alter the message" + - name: "alter_function_file" + type: "String" + default: "" + description: "The path to a file containing the code of the Lua function that will alter the message" +- name: "drop" + description: "Drop the packet" +- name: "LimitTTL" + description: "Cap the TTLs of the response to the given boundaries" + skip-cpp: true + skip-rust: true + parameters: + - name: "min" + type: "u32" + description: "The minimum allowed value" + - name: "max" + type: "u32" + description: "The maximum allowed value" + - name: "types" + type: "Vec" + default: true + description: "The record types to cap the TTL for, as integers. Default is empty which means all records will be capped" +- name: "log" + description: "Log a line for each response, to the specified file if any, to the console (require verbose) if the empty string is given as filename. +If an empty string is supplied in the file name, the logging is done to stdout, and only in verbose mode by default. This can be changed by setting ``verbose-only`` to ``false``. +The ``append`` parameter specifies whether we open the file for appending or truncate each time (default). The ``buffered`` parameter specifies whether writes to the file are buffered (default) or not. +Subsequent rules are processed after this action" + parameters: + - name: "file_name" + type: "String" + default: "" + cpp-optional: false + description: "File to log to. Set to an empty string to log to the normal stdout log, this only works when ``-v`` is set on the command line" + - name: "append" + type: "bool" + default: "false" + cpp-optional: false + description: "Whether to append to an existing file" + - name: "buffered" + type: "bool" + default: "false" + cpp-optional: false + description: "Whether to use buffered I/O" + - name: "verbose_only" + type: "bool" + default: "true" + cpp-optional: false + description: "Whether to log only in verbose mode when logging to stdout" + - name: "include_timestamp" + type: "bool" + default: "false" + cpp-optional: false + description: "Whether to include a timestamp for every entry" +- name: "lua" + description: "Invoke a Lua function that accepts a :class:`DNSResponse`. The function should return a :ref:`DNSResponseAction`. If the Lua code fails, ``ServFail`` is returned" + skip-cpp: true + skip-rust: true + parameters: + - name: "function_name" + type: "String" + default: "" + description: "The name of the Lua function" + - name: "function_code" + type: "String" + default: "" + description: "The code of the Lua function" + - name: "function_file" + type: "String" + default: "" + description: "The path to a file containing the code of the Lua function" +- name: "LuaFFI" + description: "Invoke a Lua function that accepts a pointer to a ``dnsdist_ffi_dnsquestion_t`` object, whose bindings are defined in ``dnsdist-lua-ffi-interface.h``. The function should return a :ref:`DNSResponseAction`. If the Lua code fails, ``ServFail`` is returned" + skip-cpp: true + skip-rust: true + parameters: + - name: "function_name" + type: "String" + default: "" + description: "The name of the Lua function" + - name: "function_code" + type: "String" + default: "" + description: "The code of the Lua function" + - name: "function_file" + type: "String" + default: "" + description: "The path to a file containing the code of the Lua function" +- name: "LuaFFIPerThread" + description: "Invoke a Lua function that accepts a pointer to a ``dnsdist_ffi_dnsquestion_t`` object, whose bindings are defined in ``dnsdist-lua-ffi-interface.h``. The function should return a :ref:`DNSResponseAction`. If the Lua code fails, ``ServFail`` is returned. +The function will be invoked in a per-thread Lua state, without access to the global Lua state. All constants (:ref:`DNSQType`, :ref:`DNSRCode`, ...) are available in that per-thread context, as well as all FFI functions. Objects and their bindings that are not usable in a FFI context (:class:`DNSQuestion`, :class:`DNSDistProtoBufMessage`, :class:`PacketCache`, ...) are not available." + parameters: + - name: "code" + type: "String" + description: "The code of the Lua function" +- name: "RemoteLog" + description: "Send the current response to a remote logger as a Protocol Buffer message. ``alter-function`` is a callback, receiving a :class:`DNSResponse` and a :class:`DNSDistProtoBufMessage`, that can be used to modify the message, for example for anonymization purposes. Subsequent rules are processed after this action" + skip-cpp: true + skip-rust: true + parameters: + - name: "logger_name" + type: "String" + description: "The name of the protocol buffer logger" + - name: "alter_function_name" + type: "String" + default: "" + description: "The name of the Lua function" + - name: "alter_function_code" + type: "String" + default: "" + description: "The code of the Lua function" + - name: "alter_function_file" + type: "String" + default: "" + description: "The path to a file containing the code of the Lua function" + - name: "server_id" + type: "String" + default: "" + description: "Set the Server Identity field" + - name: "ip_encrypt_key" + type: "String" + default: "" + description: "A key, that can be generated via the :func:`makeIPCipherKey` function, to encrypt the IP address of the requestor for anonymization purposes. The encryption is done using ipcrypt for IPv4 and a 128-bit AES ECB operation for IPv6" + - name: "include_cname" + type: "bool" + default: "false" + description: "Whether or not to parse and export CNAMEs" + - name: "export_tags" + type: "Vec" + default: "" + description: "The comma-separated list of keys of internal tags to export into the ``tags`` Protocol Buffer field, as ``key:value`` strings. Note that a tag with an empty value will be exported as ````, not ``:``. An empty string means that no internal tag will be exported. The special value ``*`` means that all tags will be exported" + - name: "export_extended_errors_to_meta" + type: "String" + default: "" + description: "Export Extended DNS Errors present in the DNS response, if any, into the ``meta`` Protocol Buffer field using the specified ``key``. The EDE info code will be exported as an integer value, and the EDE extra text, if present, as a string value" + - name: "metas" + type: "Vec" + default: true + description: "A list of ``name``=``key`` pairs, for meta-data to be added to Protocol Buffer message" +- name: "SetExtendedDNSError" + description: "Set an Extended DNS Error status that will be added to the response. Subsequent rules are processed after this action" + parameters: + - name: "info_code" + type: "u16" + description: "The EDNS Extended DNS Error code" + - name: "extra_text" + type: "String" + default: "" + cpp-optional: false + description: "The optional EDNS Extended DNS Error extra text" +- name: "SetMaxReturnedTTL" + description: "Cap the TTLs of the response to the given maximum, but only after inserting the response into the packet cache with the initial TTL values" + skip-cpp: true + parameters: + - name: "max" + type: "u32" + description: "The TTL cap" +- name: "SetMaxTTL" + description: "Cap the TTLs of the response to the given maximum" + skip-cpp: true + skip-rust: true + parameters: + - name: "max" + type: "u32" + description: "The TTL cap" +- name: "SetMinTTL" + description: "Cap the TTLs of the response to the given minimum" + skip-cpp: true + skip-rust: true + parameters: + - name: "min" + type: "u32" + description: "The TTL cap" +- name: "SetReducedTTL" + description: "Reduce the TTL of records in a response to a percentage of the original TTL. For example, passing 50 means that the original TTL will be cut in half. Subsequent rules are processed after this action" + parameters: + - name: "percentage" + type: "u8" + description: "The percentage to use" +- name: "SetSkipCache" + description: "Don’t store this answer in the cache. Subsequent rules are processed after this action." +- name: "SetTag" + description: "Associate a tag named ``tag`` with a value of ``value`` to this response. This function will overwrite any existing tag value. Subsequent rules are processed after this action" + parameters: + - name: "tag" + type: "String" + description: "The tag name" + - name: "value" + type: "String" + description: "The tag value" +- name: "SNMPTrap" + description: "Send an SNMP trap, adding the message string as the query description. Subsequent rules are processed after this action" + parameters: + - name: "reason" + type: "String" + default: "" + cpp-optional: false + description: "The SNMP trap reason" +- name: "TC" + description: "Truncate an existing answer, to force the client to TCP. Only applied to answers that will be sent to the client over TCP. In addition to the TC bit being set, all records are removed from the answer, authority and additional sections" diff --git a/pdns/dnsdistdist/dnsdist-response-actions-factory-generated.cc b/pdns/dnsdistdist/dnsdist-response-actions-factory-generated.cc new file mode 100644 index 000000000000..e5f5c8dbee82 --- /dev/null +++ b/pdns/dnsdistdist/dnsdist-response-actions-factory-generated.cc @@ -0,0 +1,45 @@ +// !! This file has been generated by dnsdist-rules-generator.py, do not edit by hand!! +std::shared_ptr getAllowResponseAction() +{ + return std::shared_ptr(new AllowResponseAction()); +} +std::shared_ptr getDelayResponseAction(uint32_t msec) +{ + return std::shared_ptr(new DelayResponseAction(msec)); +} +std::shared_ptr getDropResponseAction() +{ + return std::shared_ptr(new DropResponseAction()); +} +std::shared_ptr getLogResponseAction(const std::string& file_name, bool append, bool buffered, bool verbose_only, bool include_timestamp) +{ + return std::shared_ptr(new LogResponseAction(file_name, append, buffered, verbose_only, include_timestamp)); +} +std::shared_ptr getLuaFFIPerThreadResponseAction(const std::string& code) +{ + return std::shared_ptr(new LuaFFIPerThreadResponseAction(code)); +} +std::shared_ptr getSetExtendedDNSErrorResponseAction(uint16_t info_code, const std::string& extra_text) +{ + return std::shared_ptr(new SetExtendedDNSErrorResponseAction(info_code, extra_text)); +} +std::shared_ptr getSetReducedTTLResponseAction(uint8_t percentage) +{ + return std::shared_ptr(new SetReducedTTLResponseAction(percentage)); +} +std::shared_ptr getSetSkipCacheResponseAction() +{ + return std::shared_ptr(new SetSkipCacheResponseAction()); +} +std::shared_ptr getSetTagResponseAction(const std::string& tag, const std::string& value) +{ + return std::shared_ptr(new SetTagResponseAction(tag, value)); +} +std::shared_ptr getSNMPTrapResponseAction(const std::string& reason) +{ + return std::shared_ptr(new SNMPTrapResponseAction(reason)); +} +std::shared_ptr getTCResponseAction() +{ + return std::shared_ptr(new TCResponseAction()); +} diff --git a/pdns/dnsdistdist/dnsdist-response-actions-factory-generated.hh b/pdns/dnsdistdist/dnsdist-response-actions-factory-generated.hh new file mode 100644 index 000000000000..7eabf156d230 --- /dev/null +++ b/pdns/dnsdistdist/dnsdist-response-actions-factory-generated.hh @@ -0,0 +1,12 @@ +// !! This file has been generated by dnsdist-rules-generator.py, do not edit by hand!! +std::shared_ptr getAllowResponseAction(); +std::shared_ptr getDelayResponseAction(uint32_t msec); +std::shared_ptr getDropResponseAction(); +std::shared_ptr getLogResponseAction(const std::string& file_name, bool append, bool buffered, bool verbose_only, bool include_timestamp); +std::shared_ptr getLuaFFIPerThreadResponseAction(const std::string& code); +std::shared_ptr getSetExtendedDNSErrorResponseAction(uint16_t info_code, const std::string& extra_text); +std::shared_ptr getSetReducedTTLResponseAction(uint8_t percentage); +std::shared_ptr getSetSkipCacheResponseAction(); +std::shared_ptr getSetTagResponseAction(const std::string& tag, const std::string& value); +std::shared_ptr getSNMPTrapResponseAction(const std::string& reason); +std::shared_ptr getTCResponseAction(); diff --git a/pdns/dnsdistdist/dnsdist-rules-factory.hh b/pdns/dnsdistdist/dnsdist-rules-factory.hh new file mode 100644 index 000000000000..ccec8356b6fe --- /dev/null +++ b/pdns/dnsdistdist/dnsdist-rules-factory.hh @@ -0,0 +1,1514 @@ +/* + * 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 "cachecleaner.hh" +#include "dnsdist-ecs.hh" +#include "dnsdist-kvs.hh" +#include "dnsdist-lua.hh" +#include "dnsdist-lua-ffi.hh" +#include "dnsdist-rules.hh" +#include "dolog.hh" +#include "dnsparser.hh" +#include "dns_random.hh" +#include "uuid-utils.hh" + +namespace dnsdist::selectors +{ +using LuaSelectorFunction = std::function; +using LuaSelectorFFIFunction = std::function; +} + +class MaxQPSIPRule : public DNSRule +{ +public: + MaxQPSIPRule(unsigned int qps, unsigned int ipv4trunc = 32, unsigned int ipv6trunc = 64, unsigned int burst = 0, unsigned int expiration = 300, unsigned int cleanupDelay = 60, unsigned int scanFraction = 10, size_t shardsCount = 10) : + d_shards(shardsCount), d_qps(qps), d_burst(burst == 0 ? qps : burst), d_ipv4trunc(ipv4trunc), d_ipv6trunc(ipv6trunc), d_cleanupDelay(cleanupDelay), d_expiration(expiration), d_scanFraction(scanFraction) + { + d_cleaningUp.clear(); + gettime(&d_lastCleanup, true); + } + + void clear() + { + for (auto& shard : d_shards) { + shard.lock()->clear(); + } + } + + size_t cleanup(const struct timespec& cutOff, size_t* scannedCount = nullptr) const + { + size_t removed = 0; + if (scannedCount != nullptr) { + *scannedCount = 0; + } + + for (auto& shard : d_shards) { + auto limits = shard.lock(); + const size_t toLook = std::round((1.0 * limits->size()) / d_scanFraction) + 1; + size_t lookedAt = 0; + + auto& sequence = limits->get(); + for (auto entry = sequence.begin(); entry != sequence.end() && lookedAt < toLook; lookedAt++) { + if (entry->d_limiter.seenSince(cutOff)) { + /* entries are ordered from least recently seen to more recently + seen, as soon as we see one that has not expired yet, we are + done */ + lookedAt++; + break; + } + + entry = sequence.erase(entry); + removed++; + } + + if (scannedCount != nullptr) { + *scannedCount += lookedAt; + } + } + + return removed; + } + + void cleanupIfNeeded(const struct timespec& now) const + { + if (d_cleanupDelay > 0) { + struct timespec cutOff = d_lastCleanup; + cutOff.tv_sec += d_cleanupDelay; + + if (cutOff < now) { + try { + if (d_cleaningUp.test_and_set()) { + return; + } + + d_lastCleanup = now; + /* the QPS Limiter doesn't use realtime, be careful! */ + gettime(&cutOff, false); + cutOff.tv_sec -= d_expiration; + + cleanup(cutOff); + d_cleaningUp.clear(); + } + catch (...) { + d_cleaningUp.clear(); + throw; + } + } + } + } + + bool matches(const DNSQuestion* dq) const override + { + cleanupIfNeeded(dq->getQueryRealTime()); + + ComboAddress zeroport(dq->ids.origRemote); + zeroport.sin4.sin_port = 0; + zeroport.truncate(zeroport.sin4.sin_family == AF_INET ? d_ipv4trunc : d_ipv6trunc); + auto hash = ComboAddress::addressOnlyHash()(zeroport); + auto& shard = d_shards[hash % d_shards.size()]; + { + auto limits = shard.lock(); + auto iter = limits->find(zeroport); + if (iter == limits->end()) { + Entry e(zeroport, QPSLimiter(d_qps, d_burst)); + iter = limits->insert(e).first; + } + + moveCacheItemToBack(*limits, iter); + return !iter->d_limiter.check(d_qps, d_burst); + } + } + + string toString() const override + { + return "IP (/" + std::to_string(d_ipv4trunc) + ", /" + std::to_string(d_ipv6trunc) + ") match for QPS over " + std::to_string(d_qps) + " burst " + std::to_string(d_burst); + } + + size_t getEntriesCount() const + { + size_t count = 0; + for (auto& shard : d_shards) { + count += shard.lock()->size(); + } + return count; + } + + size_t getNumberOfShards() const + { + return d_shards.size(); + } + +private: + struct HashedTag + { + }; + struct SequencedTag + { + }; + struct Entry + { + Entry(const ComboAddress& addr, BasicQPSLimiter&& limiter) : + d_limiter(limiter), d_addr(addr) + { + } + mutable BasicQPSLimiter d_limiter; + ComboAddress d_addr; + }; + + using qpsContainer_t = multi_index_container< + Entry, + indexed_by< + hashed_unique, member, ComboAddress::addressOnlyHash>, + sequenced>>>; + + mutable std::vector> d_shards; + mutable struct timespec d_lastCleanup; + const unsigned int d_qps, d_burst, d_ipv4trunc, d_ipv6trunc, d_cleanupDelay, d_expiration; + const unsigned int d_scanFraction{10}; + mutable std::atomic_flag d_cleaningUp; +}; + +class MaxQPSRule : public DNSRule +{ +public: + MaxQPSRule(unsigned int qps) : + d_qps(qps, qps) + { + } + + MaxQPSRule(unsigned int qps, unsigned int burst) : + d_qps(qps, burst > 0 ? burst : qps) + { + } + + bool matches(const DNSQuestion* qd) const override + { + return d_qps.check(); + } + + string toString() const override + { + return "Max " + std::to_string(d_qps.getRate()) + " qps"; + } + +private: + mutable QPSLimiter d_qps; +}; + +class NetmaskGroupRule : public DNSRule +{ +public: + NetmaskGroupRule(const NetmaskGroup& nmg, bool src, bool quiet = false) : + d_nmg(nmg) + { + d_src = src; + d_quiet = quiet; + } + bool matches(const DNSQuestion* dq) const override + { + if (!d_src) { + return d_nmg.match(dq->ids.origDest); + } + return d_nmg.match(dq->ids.origRemote); + } + + string toString() const override + { + string ret = "Src: "; + if (!d_src) { + ret = "Dst: "; + } + if (d_quiet) { + return ret + "in-group"; + } + return ret + d_nmg.toString(); + } + +private: + NetmaskGroup d_nmg; + bool d_src; + bool d_quiet; +}; + +class TimedIPSetRule : public DNSRule, boost::noncopyable +{ +private: + struct IPv6 + { + IPv6(const ComboAddress& ca) + { + static_assert(sizeof(*this) == 16, "IPv6 struct has wrong size"); + memcpy((char*)this, ca.sin6.sin6_addr.s6_addr, 16); + } + bool operator==(const IPv6& rhs) const + { + return a == rhs.a && b == rhs.b; + } + uint64_t a, b; + }; + +public: + TimedIPSetRule() + { + } + ~TimedIPSetRule() + { + } + bool matches(const DNSQuestion* dq) const override + { + if (dq->ids.origRemote.sin4.sin_family == AF_INET) { + auto ip4s = d_ip4s.read_lock(); + auto fnd = ip4s->find(dq->ids.origRemote.sin4.sin_addr.s_addr); + if (fnd == ip4s->end()) { + return false; + } + return time(nullptr) < fnd->second; + } + else { + auto ip6s = d_ip6s.read_lock(); + auto fnd = ip6s->find({dq->ids.origRemote}); + if (fnd == ip6s->end()) { + return false; + } + return time(nullptr) < fnd->second; + } + } + + void add(const ComboAddress& ca, time_t ttd) + { + // think twice before adding templates here + if (ca.sin4.sin_family == AF_INET) { + auto res = d_ip4s.write_lock()->insert({ca.sin4.sin_addr.s_addr, ttd}); + if (!res.second && (time_t)res.first->second < ttd) { + res.first->second = (uint32_t)ttd; + } + } + else { + auto res = d_ip6s.write_lock()->insert({{ca}, ttd}); + if (!res.second && (time_t)res.first->second < ttd) { + // coverity[store_truncates_time_t] + res.first->second = (uint32_t)ttd; + } + } + } + + void remove(const ComboAddress& ca) + { + if (ca.sin4.sin_family == AF_INET) { + d_ip4s.write_lock()->erase(ca.sin4.sin_addr.s_addr); + } + else { + d_ip6s.write_lock()->erase({ca}); + } + } + + void clear() + { + d_ip4s.write_lock()->clear(); + d_ip6s.write_lock()->clear(); + } + + void cleanup() + { + time_t now = time(nullptr); + { + auto ip4s = d_ip4s.write_lock(); + for (auto iter = ip4s->begin(); iter != ip4s->end();) { + if (iter->second < now) { + iter = ip4s->erase(iter); + } + else { + ++iter; + } + } + } + + { + auto ip6s = d_ip6s.write_lock(); + for (auto iter = ip6s->begin(); iter != ip6s->end();) { + if (iter->second < now) { + iter = ip6s->erase(iter); + } + else { + ++iter; + } + } + } + } + + string toString() const override + { + time_t now = time(nullptr); + uint64_t count = 0; + + for (const auto& ip : *(d_ip4s.read_lock())) { + if (now < ip.second) { + ++count; + } + } + + for (const auto& ip : *(d_ip6s.read_lock())) { + if (now < ip.second) { + ++count; + } + } + + return "Src: " + std::to_string(count) + " ips"; + } + +private: + struct IPv6Hash + { + std::size_t operator()(const IPv6& ip) const + { + auto ah = std::hash{}(ip.a); + auto bh = std::hash{}(ip.b); + return ah & (bh << 1); + } + }; + mutable SharedLockGuarded> d_ip6s; + mutable SharedLockGuarded> d_ip4s; +}; + +class AllRule : public DNSRule +{ +public: + AllRule() {} + bool matches(const DNSQuestion* dq) const override + { + return true; + } + + string toString() const override + { + return "All"; + } +}; + +class DNSSECRule : public DNSRule +{ +public: + DNSSECRule() + { + } + bool matches(const DNSQuestion* dq) const override + { + return dq->getHeader()->cd || (dnsdist::getEDNSZ(*dq) & EDNS_HEADER_FLAG_DO); // turns out dig sets ad by default.. + } + + string toString() const override + { + return "DNSSEC"; + } +}; + +class AndRule : public DNSRule +{ +public: + AndRule(const std::vector>& rules) : + d_rules(rules) + { + } + + bool matches(const DNSQuestion* dq) const override + { + for (const auto& rule : d_rules) { + if (!rule->matches(dq)) { + return false; + } + } + return true; + } + + string toString() const override + { + string ret; + for (const auto& rule : d_rules) { + if (!ret.empty()) { + ret += " && "; + } + ret += "(" + rule->toString() + ")"; + } + return ret; + } + +private: + std::vector> d_rules; +}; + +class OrRule : public DNSRule +{ +public: + OrRule(const std::vector>& rules) : + d_rules(rules) + { + } + + bool matches(const DNSQuestion* dq) const override + { + for (const auto& rule : d_rules) { + if (rule->matches(dq)) { + return true; + } + } + return false; + } + + string toString() const override + { + string ret; + for (const auto& rule : d_rules) { + if (!ret.empty()) { + ret += " || "; + } + ret += "(" + rule->toString() + ")"; + } + return ret; + } + +private: + std::vector> d_rules; +}; + +class RegexRule : public DNSRule +{ +public: + RegexRule(const std::string& regex) : + d_regex(regex), d_visual(regex) + { + } + bool matches(const DNSQuestion* dq) const override + { + return d_regex.match(dq->ids.qname.toStringNoDot()); + } + + string toString() const override + { + return "Regex: " + d_visual; + } + +private: + Regex d_regex; + string d_visual; +}; + +#if defined(HAVE_RE2) +#include +class RE2Rule : public DNSRule +{ +public: + RE2Rule(const std::string& re2) : + d_re2(re2, RE2::Latin1), d_visual(re2) + { + } + bool matches(const DNSQuestion* dq) const override + { + return RE2::FullMatch(dq->ids.qname.toStringNoDot(), d_re2); + } + + string toString() const override + { + return "RE2 match: " + d_visual; + } + +private: + RE2 d_re2; + string d_visual; +}; +#else /* HAVE_RE2 */ +class RE2Rule : public DNSRule +{ +public: + RE2Rule(const std::string& re2) + { + throw std::runtime_error("RE2 support is disabled"); + } + bool matches(const DNSQuestion* dq) const override + { + return false; + } + + string toString() const override + { + return "Unsupported RE2"; + } +}; +#endif /* HAVE_RE2 */ + +class HTTPHeaderRule : public DNSRule +{ +public: + HTTPHeaderRule(const std::string& header, const std::string& regex); + bool matches(const DNSQuestion* dnsQuestion) const override; + string toString() const override; + +private: + string d_header; + Regex d_regex; + string d_visual; +}; + +class HTTPPathRule : public DNSRule +{ +public: + HTTPPathRule(std::string path); + bool matches(const DNSQuestion* dnsQuestion) const override; + string toString() const override; + +private: + string d_path; +}; + +class HTTPPathRegexRule : public DNSRule +{ +public: + HTTPPathRegexRule(const std::string& regex); + bool matches(const DNSQuestion* dnsQuestion) const override; + string toString() const override; + +private: + Regex d_regex; + std::string d_visual; +}; + +class SNIRule : public DNSRule +{ +public: + SNIRule(const std::string& name) : + d_sni(name) + { + } + bool matches(const DNSQuestion* dq) const override + { + return dq->sni == d_sni; + } + string toString() const override + { + return "SNI == " + d_sni; + } + +private: + std::string d_sni; +}; + +class SuffixMatchNodeRule : public DNSRule +{ +public: + SuffixMatchNodeRule(const SuffixMatchNode& smn, bool quiet = false) : + d_smn(smn), d_quiet(quiet) + { + } + bool matches(const DNSQuestion* dq) const override + { + return d_smn.check(dq->ids.qname); + } + string toString() const override + { + if (d_quiet) + return "qname==in-set"; + else + return "qname in " + d_smn.toString(); + } + +private: + SuffixMatchNode d_smn; + bool d_quiet; +}; + +class QNameRule : public DNSRule +{ +public: + QNameRule(const DNSName& qname) : + d_qname(qname) + { + } + + bool matches(const DNSQuestion* dq) const override + { + return d_qname == dq->ids.qname; + } + string toString() const override + { + return "qname==" + d_qname.toString(); + } + +private: + DNSName d_qname; +}; + +class QNameSetRule : public DNSRule +{ +public: + QNameSetRule(const DNSNameSet& names) : + qname_idx(names) {} + + bool matches(const DNSQuestion* dq) const override + { + return qname_idx.find(dq->ids.qname) != qname_idx.end(); + } + + string toString() const override + { + std::stringstream ss; + ss << "qname in DNSNameSet(" << qname_idx.size() << " FQDNs)"; + return ss.str(); + } + +private: + DNSNameSet qname_idx; +}; + +class QTypeRule : public DNSRule +{ +public: + QTypeRule(uint16_t qtype) : + d_qtype(qtype) + { + } + bool matches(const DNSQuestion* dq) const override + { + return d_qtype == dq->ids.qtype; + } + string toString() const override + { + QType qt(d_qtype); + return "qtype==" + qt.toString(); + } + +private: + uint16_t d_qtype; +}; + +class QClassRule : public DNSRule +{ +public: + QClassRule(uint16_t qclass) : + d_qclass(qclass) + { + } + bool matches(const DNSQuestion* dq) const override + { + return d_qclass == dq->ids.qclass; + } + string toString() const override + { + return "qclass==" + std::to_string(d_qclass); + } + +private: + uint16_t d_qclass; +}; + +class OpcodeRule : public DNSRule +{ +public: + OpcodeRule(uint8_t opcode) : + d_opcode(opcode) + { + } + bool matches(const DNSQuestion* dq) const override + { + return d_opcode == dq->getHeader()->opcode; + } + string toString() const override + { + return "opcode==" + std::to_string(d_opcode); + } + +private: + uint8_t d_opcode; +}; + +class DSTPortRule : public DNSRule +{ +public: + DSTPortRule(uint16_t port) : + d_port(port) + { + } + bool matches(const DNSQuestion* dq) const override + { + return htons(d_port) == dq->ids.origDest.sin4.sin_port; + } + string toString() const override + { + return "dst port==" + std::to_string(d_port); + } + +private: + uint16_t d_port; +}; + +class TCPRule : public DNSRule +{ +public: + TCPRule(bool tcp) : + d_tcp(tcp) + { + } + bool matches(const DNSQuestion* dq) const override + { + return dq->overTCP() == d_tcp; + } + string toString() const override + { + return (d_tcp ? "TCP" : "UDP"); + } + +private: + bool d_tcp; +}; + +class NotRule : public DNSRule +{ +public: + NotRule(const std::shared_ptr& rule) : + d_rule(rule) + { + } + bool matches(const DNSQuestion* dq) const override + { + return !d_rule->matches(dq); + } + string toString() const override + { + return "!(" + d_rule->toString() + ")"; + } + +private: + std::shared_ptr d_rule; +}; + +class RecordsCountRule : public DNSRule +{ +public: + RecordsCountRule(uint8_t section, uint16_t minCount, uint16_t maxCount) : + d_minCount(minCount), d_maxCount(maxCount), d_section(section) + { + } + bool matches(const DNSQuestion* dq) const override + { + uint16_t count = 0; + switch (d_section) { + case 0: + count = ntohs(dq->getHeader()->qdcount); + break; + case 1: + count = ntohs(dq->getHeader()->ancount); + break; + case 2: + count = ntohs(dq->getHeader()->nscount); + break; + case 3: + count = ntohs(dq->getHeader()->arcount); + break; + } + return count >= d_minCount && count <= d_maxCount; + } + string toString() const override + { + string section; + switch (d_section) { + case 0: + section = "QD"; + break; + case 1: + section = "AN"; + break; + case 2: + section = "NS"; + break; + case 3: + section = "AR"; + break; + } + return std::to_string(d_minCount) + " <= records in " + section + " <= " + std::to_string(d_maxCount); + } + +private: + uint16_t d_minCount; + uint16_t d_maxCount; + uint8_t d_section; +}; + +class RecordsTypeCountRule : public DNSRule +{ +public: + RecordsTypeCountRule(uint8_t section, uint16_t type, uint16_t minCount, uint16_t maxCount) : + d_type(type), d_minCount(minCount), d_maxCount(maxCount), d_section(section) + { + } + bool matches(const DNSQuestion* dq) const override + { + uint16_t count = 0; + switch (d_section) { + case 0: + count = ntohs(dq->getHeader()->qdcount); + break; + case 1: + count = ntohs(dq->getHeader()->ancount); + break; + case 2: + count = ntohs(dq->getHeader()->nscount); + break; + case 3: + count = ntohs(dq->getHeader()->arcount); + break; + } + if (count < d_minCount) { + return false; + } + count = getRecordsOfTypeCount(reinterpret_cast(dq->getData().data()), dq->getData().size(), d_section, d_type); + return count >= d_minCount && count <= d_maxCount; + } + string toString() const override + { + string section; + switch (d_section) { + case 0: + section = "QD"; + break; + case 1: + section = "AN"; + break; + case 2: + section = "NS"; + break; + case 3: + section = "AR"; + break; + } + return std::to_string(d_minCount) + " <= " + QType(d_type).toString() + " records in " + section + " <= " + std::to_string(d_maxCount); + } + +private: + uint16_t d_type; + uint16_t d_minCount; + uint16_t d_maxCount; + uint8_t d_section; +}; + +class TrailingDataRule : public DNSRule +{ +public: + TrailingDataRule() + { + } + bool matches(const DNSQuestion* dq) const override + { + uint16_t length = getDNSPacketLength(reinterpret_cast(dq->getData().data()), dq->getData().size()); + return length < dq->getData().size(); + } + string toString() const override + { + return "trailing data"; + } +}; + +class QNameLabelsCountRule : public DNSRule +{ +public: + QNameLabelsCountRule(unsigned int minLabelsCount, unsigned int maxLabelsCount) : + d_min(minLabelsCount), d_max(maxLabelsCount) + { + } + bool matches(const DNSQuestion* dq) const override + { + unsigned int count = dq->ids.qname.countLabels(); + return count < d_min || count > d_max; + } + string toString() const override + { + return "labels count < " + std::to_string(d_min) + " || labels count > " + std::to_string(d_max); + } + +private: + unsigned int d_min; + unsigned int d_max; +}; + +class QNameWireLengthRule : public DNSRule +{ +public: + QNameWireLengthRule(size_t min, size_t max) : + d_min(min), d_max(max) + { + } + bool matches(const DNSQuestion* dq) const override + { + size_t const wirelength = dq->ids.qname.wirelength(); + return wirelength < d_min || wirelength > d_max; + } + string toString() const override + { + return "wire length < " + std::to_string(d_min) + " || wire length > " + std::to_string(d_max); + } + +private: + size_t d_min; + size_t d_max; +}; + +class RCodeRule : public DNSRule +{ +public: + RCodeRule(uint8_t rcode) : + d_rcode(rcode) + { + } + bool matches(const DNSQuestion* dq) const override + { + return d_rcode == dq->getHeader()->rcode; + } + string toString() const override + { + return "rcode==" + RCode::to_s(d_rcode); + } + +private: + uint8_t d_rcode; +}; + +class ERCodeRule : public DNSRule +{ +public: + ERCodeRule(uint8_t rcode) : + d_rcode(rcode & 0xF), d_extrcode(rcode >> 4) + { + } + bool matches(const DNSQuestion* dq) const override + { + // avoid parsing EDNS OPT RR when not needed. + if (d_rcode != dq->getHeader()->rcode) { + return false; + } + + EDNS0Record edns0; + if (!getEDNS0Record(dq->getData(), edns0)) { + return false; + } + + return d_extrcode == edns0.extRCode; + } + string toString() const override + { + return "ercode==" + ERCode::to_s(d_rcode | (d_extrcode << 4)); + } + +private: + uint8_t d_rcode; // plain DNS Rcode + uint8_t d_extrcode; // upper bits in EDNS0 record +}; + +class EDNSVersionRule : public DNSRule +{ +public: + EDNSVersionRule(uint8_t version) : + d_version(version) + { + } + bool matches(const DNSQuestion* dq) const override + { + EDNS0Record edns0; + if (!getEDNS0Record(dq->getData(), edns0)) { + return false; + } + + return d_version < edns0.version; + } + string toString() const override + { + return "ednsversion>" + std::to_string(d_version); + } + +private: + uint8_t d_version; +}; + +class EDNSOptionRule : public DNSRule +{ +public: + EDNSOptionRule(uint16_t optcode) : + d_optcode(optcode) + { + } + bool matches(const DNSQuestion* dq) const override + { + uint16_t optStart; + size_t optLen = 0; + bool last = false; + int res = locateEDNSOptRR(dq->getData(), &optStart, &optLen, &last); + if (res != 0) { + // no EDNS OPT RR + return false; + } + + if (optLen < optRecordMinimumSize) { + return false; + } + + if (optStart < dq->getData().size() && dq->getData().at(optStart) != 0) { + // OPT RR Name != '.' + return false; + } + + return isEDNSOptionInOpt(dq->getData(), optStart, optLen, d_optcode); + } + string toString() const override + { + return "ednsoptcode==" + std::to_string(d_optcode); + } + +private: + uint16_t d_optcode; +}; + +class RDRule : public DNSRule +{ +public: + RDRule() + { + } + bool matches(const DNSQuestion* dq) const override + { + return dq->getHeader()->rd == 1; + } + string toString() const override + { + return "rd==1"; + } +}; + +class ProbaRule : public DNSRule +{ +public: + ProbaRule(double proba) : + d_proba(proba) + { + } + bool matches(const DNSQuestion* dq) const override + { + if (d_proba == 1.0) + return true; + double rnd = 1.0 * dns_random_uint32() / UINT32_MAX; + return rnd > (1.0 - d_proba); + } + string toString() const override + { + return "match with prob. " + (boost::format("%0.2f") % d_proba).str(); + } + +private: + double d_proba; +}; + +class TagRule : public DNSRule +{ +public: + TagRule(const std::string& tag, boost::optional value) : + d_value(std::move(value)), d_tag(tag) + { + } + bool matches(const DNSQuestion* dq) const override + { + if (!dq->ids.qTag) { + return false; + } + + const auto it = dq->ids.qTag->find(d_tag); + if (it == dq->ids.qTag->cend()) { + return false; + } + + if (!d_value || d_value->empty()) { + return true; + } + + return it->second == *d_value; + } + + string toString() const override + { + return "tag '" + d_tag + "' is set" + (d_value ? (" to '" + *d_value + "'") : ""); + } + +private: + boost::optional d_value; + std::string d_tag; +}; + +class PoolAvailableRule : public DNSRule +{ +public: + PoolAvailableRule(const std::string& poolname) : + d_poolname(poolname) + { + } + + bool matches(const DNSQuestion* dq) const override + { + return (getPool(d_poolname)->countServers(true) > 0); + } + + string toString() const override + { + return "pool '" + d_poolname + "' is available"; + } + +private: + std::string d_poolname; +}; + +class PoolOutstandingRule : public DNSRule +{ +public: + PoolOutstandingRule(const std::string& poolname, const size_t limit) : + d_poolname(poolname), d_limit(limit) + { + } + + bool matches(const DNSQuestion* dq) const override + { + return (getPool(d_poolname)->poolLoad()) > d_limit; + } + + string toString() const override + { + return "pool '" + d_poolname + "' outstanding > " + std::to_string(d_limit); + } + +private: + std::string d_poolname; + size_t d_limit; +}; + +class KeyValueStoreLookupRule : public DNSRule +{ +public: + KeyValueStoreLookupRule(const std::shared_ptr& kvs, const std::shared_ptr& lookupKey) : + d_kvs(kvs), d_key(lookupKey) + { + } + + bool matches(const DNSQuestion* dq) const override + { + std::vector keys = d_key->getKeys(*dq); + for (const auto& key : keys) { + if (d_kvs->keyExists(key) == true) { + return true; + } + } + + return false; + } + + string toString() const override + { + return "lookup key-value store based on '" + d_key->toString() + "'"; + } + +private: + std::shared_ptr d_kvs; + std::shared_ptr d_key; +}; + +class KeyValueStoreRangeLookupRule : public DNSRule +{ +public: + KeyValueStoreRangeLookupRule(const std::shared_ptr& kvs, const std::shared_ptr& lookupKey) : + d_kvs(kvs), d_key(lookupKey) + { + } + + bool matches(const DNSQuestion* dq) const override + { + std::vector keys = d_key->getKeys(*dq); + for (const auto& key : keys) { + std::string value; + if (d_kvs->getRangeValue(key, value) == true) { + return true; + } + } + + return false; + } + + string toString() const override + { + return "range-based lookup key-value store based on '" + d_key->toString() + "'"; + } + +private: + std::shared_ptr d_kvs; + std::shared_ptr d_key; +}; + +class LuaRule : public DNSRule +{ +public: + LuaRule(const dnsdist::selectors::LuaSelectorFunction& func) : + d_func(func) + {} + + bool matches(const DNSQuestion* dq) const override + { + try { + auto lock = g_lua.lock(); + return d_func(dq); + } + catch (const std::exception& e) { + warnlog("LuaRule failed inside Lua: %s", e.what()); + } + catch (...) { + warnlog("LuaRule failed inside Lua: [unknown exception]"); + } + return false; + } + + string toString() const override + { + return "Lua script"; + } + +private: + dnsdist::selectors::LuaSelectorFunction d_func; +}; + +class LuaFFIRule : public DNSRule +{ +public: + LuaFFIRule(const dnsdist::selectors::LuaSelectorFFIFunction& func) : + d_func(func) + {} + + bool matches(const DNSQuestion* dq) const override + { + dnsdist_ffi_dnsquestion_t dqffi(const_cast(dq)); + try { + auto lock = g_lua.lock(); + return d_func(&dqffi); + } + catch (const std::exception& e) { + warnlog("LuaFFIRule failed inside Lua: %s", e.what()); + } + catch (...) { + warnlog("LuaFFIRule failed inside Lua: [unknown exception]"); + } + return false; + } + + string toString() const override + { + return "Lua FFI script"; + } + +private: + dnsdist::selectors::LuaSelectorFFIFunction d_func; +}; + +class LuaFFIPerThreadRule : public DNSRule +{ +public: + LuaFFIPerThreadRule(const std::string& code) : + d_functionCode(code), d_functionID(s_functionsCounter++) + { + } + + bool matches(const DNSQuestion* dq) 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 false; + } + + dnsdist_ffi_dnsquestion_t dqffi(const_cast(dq)); + return state.d_func(&dqffi); + } + catch (const std::exception& e) { + warnlog("LuaFFIPerthreadRule failed inside Lua: %s", e.what()); + } + catch (...) { + warnlog("LuaFFIPerThreadRule failed inside Lua: [unknown exception]"); + } + return false; + } + + string toString() const override + { + return "Lua FFI per-thread script"; + } + +private: + struct PerThreadState + { + LuaContext d_luaContext; + dnsdist::selectors::LuaSelectorFFIFunction d_func; + bool d_initialized{false}; + }; + + static std::atomic s_functionsCounter; + static thread_local std::map t_perThreadStates; + const std::string d_functionCode; + const uint64_t d_functionID; +}; + +class ProxyProtocolValueRule : public DNSRule +{ +public: + ProxyProtocolValueRule(uint8_t type, boost::optional value) : + d_value(std::move(value)), d_type(type) + { + } + + bool matches(const DNSQuestion* dq) const override + { + if (!dq->proxyProtocolValues) { + return false; + } + + for (const auto& entry : *dq->proxyProtocolValues) { + if (entry.type == d_type && (!d_value || d_value->empty() || entry.content == *d_value)) { + return true; + } + } + + return false; + } + + string toString() const override + { + if (d_value) { + return "proxy protocol value of type " + std::to_string(d_type) + " matches"; + } + return "proxy protocol value of type " + std::to_string(d_type) + " is present"; + } + +private: + boost::optional d_value; + uint8_t d_type; +}; + +class PayloadSizeRule : public DNSRule +{ + enum class Comparisons : uint8_t + { + equal, + greater, + greaterOrEqual, + smaller, + smallerOrEqual + }; + +public: + PayloadSizeRule(const std::string& comparison, uint16_t size) : + d_size(size) + { + if (comparison == "equal") { + d_comparison = Comparisons::equal; + } + else if (comparison == "greater") { + d_comparison = Comparisons::greater; + } + else if (comparison == "greaterOrEqual") { + d_comparison = Comparisons::greaterOrEqual; + } + else if (comparison == "smaller") { + d_comparison = Comparisons::smaller; + } + else if (comparison == "smallerOrEqual") { + d_comparison = Comparisons::smallerOrEqual; + } + else { + throw std::runtime_error("Unsupported comparison '" + comparison + "'"); + } + } + + bool matches(const DNSQuestion* dq) const override + { + const auto size = dq->getData().size(); + + switch (d_comparison) { + case Comparisons::equal: + return size == d_size; + case Comparisons::greater: + return size > d_size; + case Comparisons::greaterOrEqual: + return size >= d_size; + case Comparisons::smaller: + return size < d_size; + case Comparisons::smallerOrEqual: + return size <= d_size; + default: + return false; + } + } + + string toString() const override + { + static const std::array comparisonStr{ + "equal to", + "greater than", + "equal to or greater than", + "smaller than", + "equal to or smaller than"}; + return "payload size is " + comparisonStr.at(static_cast(d_comparison)) + " " + std::to_string(d_size); + } + +private: + uint16_t d_size; + Comparisons d_comparison; +}; + +namespace dnsdist::selectors +{ +std::shared_ptr getAndSelector(const std::vector>& rules); +std::shared_ptr getOrSelector(const std::vector>& rules); +std::shared_ptr getNotSelector(const std::shared_ptr& rule); +std::shared_ptr getLuaSelector(const dnsdist::selectors::LuaSelectorFunction& func); +std::shared_ptr getLuaFFISelector(const dnsdist::selectors::LuaSelectorFFIFunction& func); +std::shared_ptr getQNameSelector(const DNSName& qname); +std::shared_ptr getQNameSetSelector(const DNSNameSet& qnames); +std::shared_ptr getQNameSuffixSelector(const SuffixMatchNode& suffixes, bool quiet); +std::shared_ptr getQTypeSelector(const std::string& qtypeStr, uint16_t qtypeCode); +std::shared_ptr getQClassSelector(const std::string& qclassStr, uint16_t qclassCode); +std::shared_ptr getNetmaskGroupSelector(const NetmaskGroup& nmg, bool source, bool quiet); +std::shared_ptr getKeyValueStoreLookupSelector(const std::shared_ptr& kvs, const std::shared_ptr& lookupKey); +std::shared_ptr getKeyValueStoreRangeLookupSelector(const std::shared_ptr& kvs, const std::shared_ptr& lookupKey); + +#include "dnsdist-selectors-factory-generated.hh" +} diff --git a/pdns/dnsdistdist/dnsdist-rules-generator.py b/pdns/dnsdistdist/dnsdist-rules-generator.py new file mode 100644 index 000000000000..7a92c0f98364 --- /dev/null +++ b/pdns/dnsdistdist/dnsdist-rules-generator.py @@ -0,0 +1,285 @@ +#!/usr/bin/python3 +"""Load action and selector definitions and generates C++ factory and Lua bindings code.""" +# 1/ Loads the action definitions from: +# - dnsdist-actions-definitions.yml +# - dnsdist-response-actions-definitions.yml +# and generates C++ factory to create the objects +# for these actions from the corresponding parameters: +# - dnsdist-actions-factory-generated.cc +# - dnsdist-actions-factory-generated.hh +# - dnsdist-response-actions-factory-generated.cc +# - dnsdist-response-actions-factory-generated.hh +# as well as Lua bindings for them: +# - dnsdist-lua-actions-generated.cc +# - dnsdist-lua-response-actions-generated.cc +# 2/ Loads the selector definitions from: +# - dnsdist-selectors-definitions.yml +# and generates C++ factory to create the objects +# for these selectors from the corresponding parameters: +# - dnsdist-selectors-factory-generated.cc +# - dnsdist-selectors-factory-generated.hh +# as well as the Lua bindings for them: +# - dnsdist-lua-selectors-generated.cc +# The format of the definitions, in YAML, is a simple list of items. +# Each item has a name and an optional list of parameters. +# Parameters have a name, a type, and optionally a default value +# Types are the Rust ones, converted to the C++ equivalent when needed +# Default values are written as quoted strings, with the exception of the +# special unquoted true value which means to use the default value for the +# object type, which needs to exist. +# Items can optionally have the following properties: +# - 'skip-cpp' means that the corresponding C++ factory and Lua bindings will not be generated, which is useful for objects taking parameters that cannot be directly mapped +# - 'skip-rust' is not used by this script but is used by the dnsdist-settings-generator.py one, where it means that the C++ code to create the Rust-side version of an action or selector will not generated +# - 'skip-serde' is not used by this script but is used by the dnsdist-settings-generator.py one, where it means that the Rust structure representing that action or selector in the YAML setting will not be directly created by Serde. It is used for selectors that reference another selector themselves, or actions referencing another action. +import os +import tempfile +import yaml + +def get_definitions_from_file(def_file): + with open(def_file, 'rt', encoding="utf-8") as fd: + definitions = yaml.safe_load(fd.read()) + return definitions + +def is_vector_of(type_str): + return type_str.startswith('Vec<') + +def type_to_cpp(type_str, lua_interface, inside_container=False): + if is_vector_of(type_str): + sub_type = type_str[4:-1] + return 'std::vector<' + type_to_cpp(sub_type, lua_interface, True) + '>' + + if type_str == 'u8': + return 'uint8_t' + if type_str == 'u16': + return 'uint16_t' + if type_str == 'u32': + return 'uint32_t' + if type_str == 'u64': + return 'uint64_t' + if type_str == 'f64': + return 'double' + if type_str == 'String': + if lua_interface: + return 'std::string' + if inside_container: + return 'std::string' + return 'const std::string&' + return type_str + +def get_cpp_object_name(name, is_class=True): + object_name = '' + capitalize = is_class + for char in name: + if char == '-': + capitalize = True + continue + if capitalize: + char = char.upper() + capitalize = False + object_name += char + + return object_name + +def get_cpp_parameter_name(name): + return get_cpp_object_name(name, is_class=False) + +def get_cpp_parameters_definition(parameters, lua_interface): + output = '' + for parameter in parameters: + pname = get_cpp_parameter_name(parameter['name']) + ptype = type_to_cpp(parameter['type'], lua_interface) + if 'default' in parameter: + if lua_interface: + ptype = type_to_cpp(parameter['type'], lua_interface, True) + ptype = f'boost::optional<{ptype}>' + elif not 'cpp-optional' in parameter or parameter['cpp-optional']: + ptype = type_to_cpp(parameter['type'], lua_interface, True) + ptype = f'std::optional<{ptype}>' + if len(output) > 0: + output += ', ' + output += f'{ptype} {pname}' + return output + +def get_cpp_parameters(parameters, lua_interface): + output = '' + for parameter in parameters: + pname = get_cpp_parameter_name(parameter['name']) + if len(output) > 0: + output += ', ' + default = None + if not 'default' in parameter: + output += f'{pname}' + continue + + cpp_optional = not 'cpp-optional' in parameter or parameter['cpp-optional'] + if lua_interface and not cpp_optional: + # We are the Lua binding, and the factory does not handle optional values + # -> pass the value if any, and the default otherwise + default = parameter['default'] + elif lua_interface and cpp_optional: + # we are the Lua binding, the factory does handle optional values, + # -> boost::optional to std::optional + output += f'boostToStandardOptional({pname})' + continue + elif not lua_interface and cpp_optional: + # We are the C++ factory and we do handle optional values + # -> pass the value if any, and the default otherwise + default = parameter['default'] + else: + # We are the C++ factory and we do not handle optional values + # -> pass the value we received + output += f'{pname}' + continue + + if default == '': + default = '""' + if default == True: + default = '{}' + + output += f'{pname} ? *{pname} : {default}' + + return output + +def get_temporary_file_for_generated_code(): + generated_fp = tempfile.NamedTemporaryFile(mode='w+t', encoding='utf-8', dir='.', delete=False) + generated_fp.write('// !! This file has been generated by dnsdist-rules-generator.py, do not edit by hand!!\n') + return generated_fp + +def generate_actions_factory_header(definitions, response=False): + suffix = 'ResponseAction' if response else 'Action' + shared_object_type = f'DNS{suffix}' + generated_fp = get_temporary_file_for_generated_code() + + for action in definitions: + if 'skip-cpp' in action and action['skip-cpp']: + continue + name = get_cpp_object_name(action['name']) + output = f'std::shared_ptr<{shared_object_type}> get{name}{suffix}(' + if 'parameters' in action: + output += get_cpp_parameters_definition(action['parameters'], False) + output += ');\n' + generated_fp.write(output) + + output_file_name = 'dnsdist-response-actions-factory-generated.hh' if response else 'dnsdist-actions-factory-generated.hh' + os.rename(generated_fp.name, output_file_name) + +def generate_actions_factory(definitions, response=False): + suffix = 'ResponseAction' if response else 'Action' + generated_fp = get_temporary_file_for_generated_code() + + for action in definitions: + if 'skip-cpp' in action and action['skip-cpp']: + continue + name = get_cpp_object_name(action['name']) + output = f'std::shared_ptr get{name}{suffix}(' + if 'parameters' in action: + output += get_cpp_parameters_definition(action['parameters'], False) + output += ')\n{\n' + output += f' return std::shared_ptr(new {name}{suffix}(' + if 'parameters' in action: + output += get_cpp_parameters(action['parameters'], False) + output += '));\n' + output += '}\n' + generated_fp.write(output) + + output_file_name = 'dnsdist-response-actions-factory-generated.cc' if response else 'dnsdist-actions-factory-generated.cc' + os.rename(generated_fp.name, output_file_name) + +def generate_lua_actions_bindings(definitions, response=False): + suffix = 'ResponseAction' if response else 'Action' + generated_fp = get_temporary_file_for_generated_code() + + for action in definitions: + if 'skip-cpp' in action and action['skip-cpp']: + continue + name = get_cpp_object_name(action['name']) + output = f'luaCtx.writeFunction("{name}{suffix}", [](' + if 'parameters' in action: + output += get_cpp_parameters_definition(action['parameters'], True) + output += ') {\n' + output += f' return dnsdist::actions::get{name}{suffix}(' + if 'parameters' in action: + output += get_cpp_parameters(action['parameters'], True) + output += ');\n' + output += '});\n' + generated_fp.write(output) + + output_file_name = 'dnsdist-lua-response-actions-generated.cc' if response else 'dnsdist-lua-actions-generated.cc' + os.rename(generated_fp.name, output_file_name) + +def generate_selectors_factory_header(definitions): + generated_fp = get_temporary_file_for_generated_code() + + for selector in definitions: + if 'skip-cpp' in selector and selector['skip-cpp']: + continue + name = get_cpp_object_name(selector['name']) + output = f'std::shared_ptr<{name}Rule> get{name}Selector(' + if 'parameters' in selector: + output += get_cpp_parameters_definition(selector['parameters'], False) + output += ');\n' + generated_fp.write(output) + + output_file_name = 'dnsdist-selectors-factory-generated.hh' + os.rename(generated_fp.name, output_file_name) + +def generate_selectors_factory(definitions, response=False): + generated_fp = get_temporary_file_for_generated_code() + + for selector in definitions: + if 'skip-cpp' in selector and selector['skip-cpp']: + continue + name = get_cpp_object_name(selector['name']) + output = f'std::shared_ptr<{name}Rule> get{name}Selector(' + if 'parameters' in selector: + output += get_cpp_parameters_definition(selector['parameters'], False) + output += ')\n{\n' + output += f' return std::make_shared<{name}Rule>(' + if 'parameters' in selector: + output += get_cpp_parameters(selector['parameters'], False) + output += ');\n' + output += '}\n' + generated_fp.write(output) + + output_file_name = 'dnsdist-selectors-factory-generated.cc' + os.rename(generated_fp.name, output_file_name) + +def generate_lua_selectors_bindings(definitions): + generated_fp = get_temporary_file_for_generated_code() + + for selector in definitions: + if 'skip-cpp' in selector and selector['skip-cpp']: + continue + name = get_cpp_object_name(selector['name']) + output = f'luaCtx.writeFunction("{name}Rule", [](' + if 'parameters' in selector: + output += get_cpp_parameters_definition(selector['parameters'], True) + output += ') {\n' + output += f' return std::shared_ptr(dnsdist::selectors::get{name}Selector(' + if 'parameters' in selector: + output += get_cpp_parameters(selector['parameters'], True) + output += '));\n' + output += '});\n' + generated_fp.write(output) + + output_file_name = 'dnsdist-lua-selectors-generated.cc' + os.rename(generated_fp.name, output_file_name) + +def main(): + definitions = get_definitions_from_file('dnsdist-actions-definitions.yml') + generate_actions_factory_header(definitions) + generate_actions_factory(definitions) + generate_lua_actions_bindings(definitions) + + definitions = get_definitions_from_file('dnsdist-response-actions-definitions.yml') + generate_actions_factory_header(definitions, response=True) + generate_actions_factory(definitions, response=True) + generate_lua_actions_bindings(definitions, response=True) + + definitions = get_definitions_from_file('dnsdist-selectors-definitions.yml') + generate_selectors_factory_header(definitions) + generate_selectors_factory(definitions) + generate_lua_selectors_bindings(definitions) + +if __name__ == '__main__': + main() diff --git a/pdns/dnsdistdist/dnsdist-rules.cc b/pdns/dnsdistdist/dnsdist-rules.cc index 96666a21436e..3d90c5b3a778 100644 --- a/pdns/dnsdistdist/dnsdist-rules.cc +++ b/pdns/dnsdistdist/dnsdist-rules.cc @@ -19,8 +19,188 @@ * 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-rules.hh" +#include "dnsdist-rules-factory.hh" std::atomic LuaFFIPerThreadRule::s_functionsCounter = 0; thread_local std::map LuaFFIPerThreadRule::t_perThreadStates; + +HTTPHeaderRule::HTTPHeaderRule(const std::string& header, const std::string& regex) : + d_header(toLower(header)), d_regex(regex), d_visual("http[" + header + "] ~ " + regex) +{ +#if !defined(HAVE_DNS_OVER_HTTPS) && !defined(HAVE_DNS_OVER_HTTP3) + throw std::runtime_error("Using HTTPHeaderRule while DoH support is not enabled"); +#endif /* HAVE_DNS_OVER_HTTPS || HAVE_DNS_OVER_HTTP3 */ +} + +bool HTTPHeaderRule::matches(const DNSQuestion* dnsQuestion) const +{ +#if defined(HAVE_DNS_OVER_HTTPS) + if (dnsQuestion->ids.du) { + const auto& headers = dnsQuestion->ids.du->getHTTPHeaders(); + for (const auto& header : headers) { + if (header.first == d_header) { + return d_regex.match(header.second); + } + } + return false; + } +#endif /* HAVE_DNS_OVER_HTTPS */ +#if defined(HAVE_DNS_OVER_HTTP3) + if (dnsQuestion->ids.doh3u) { + const auto& headers = dnsQuestion->ids.doh3u->getHTTPHeaders(); + for (const auto& header : headers) { + if (header.first == d_header) { + return d_regex.match(header.second); + } + } + return false; + } +#endif /* defined(HAVE_DNS_OVER_HTTP3) */ + return false; +} + +string HTTPHeaderRule::toString() const +{ + return d_visual; +} + +HTTPPathRule::HTTPPathRule(std::string path) : + d_path(std::move(path)) +{ +#if !defined(HAVE_DNS_OVER_HTTPS) && !defined(HAVE_DNS_OVER_HTTP3) + throw std::runtime_error("Using HTTPPathRule while DoH support is not enabled"); +#endif /* HAVE_DNS_OVER_HTTPS || HAVE_DNS_OVER_HTTP3 */ +} + +bool HTTPPathRule::matches(const DNSQuestion* dnsQuestion) const +{ +#if defined(HAVE_DNS_OVER_HTTPS) + if (dnsQuestion->ids.du) { + const auto path = dnsQuestion->ids.du->getHTTPPath(); + return d_path == path; + } +#endif /* HAVE_DNS_OVER_HTTPS */ +#if defined(HAVE_DNS_OVER_HTTP3) + if (dnsQuestion->ids.doh3u) { + return dnsQuestion->ids.doh3u->getHTTPPath() == d_path; + } +#endif /* defined(HAVE_DNS_OVER_HTTP3) */ + return false; +} + +string HTTPPathRule::toString() const +{ + return "url path == " + d_path; +} + +HTTPPathRegexRule::HTTPPathRegexRule(const std::string& regex) : + d_regex(regex), d_visual("http path ~ " + regex) +{ +#if !defined(HAVE_DNS_OVER_HTTPS) && !defined(HAVE_DNS_OVER_HTTP3) + throw std::runtime_error("Using HTTPRegexRule while DoH support is not enabled"); +#endif /* HAVE_DNS_OVER_HTTPS || HAVE_DNS_OVER_HTTP3 */ +} + +bool HTTPPathRegexRule::matches(const DNSQuestion* dnsQuestion) const +{ +#if defined(HAVE_DNS_OVER_HTTPS) + if (dnsQuestion->ids.du) { + const auto path = dnsQuestion->ids.du->getHTTPPath(); + return d_regex.match(path); + } +#endif /* HAVE_DNS_OVER_HTTPS */ +#if defined(HAVE_DNS_OVER_HTTP3) + if (dnsQuestion->ids.doh3u) { + return d_regex.match(dnsQuestion->ids.doh3u->getHTTPPath()); + } + return false; +#endif /* HAVE_DNS_OVER_HTTP3 */ + return false; +} + +string HTTPPathRegexRule::toString() const +{ + return d_visual; +} + +namespace dnsdist::selectors +{ +std::shared_ptr getQClassSelector(const std::string& qclassStr, uint16_t qclassCode) +{ + QClass qclass(qclassCode); + if (!qclassStr.empty()) { + qclass = QClass(std::string(qclassStr)); + } + + return std::make_shared(qclass); +} + +std::shared_ptr getQTypeSelector(const std::string& qtypeStr, uint16_t qtypeCode) +{ + QType qtype(qtypeCode); + if (!qtypeStr.empty()) { + qtype = std::string(qtypeStr); + } + + return std::make_shared(qtype); +} + +std::shared_ptr getQNameSuffixSelector(const SuffixMatchNode& suffixes, bool quiet) +{ + return std::make_shared(suffixes, quiet); +} + +std::shared_ptr getQNameSetSelector(const DNSNameSet& qnames) +{ + return std::make_shared(qnames); +} + +std::shared_ptr getQNameSelector(const DNSName& qname) +{ + return std::make_shared(qname); +} + +std::shared_ptr getNetmaskGroupSelector(const NetmaskGroup& nmg, bool source, bool quiet) +{ + return std::make_shared(nmg, source, quiet); +} + +std::shared_ptr getKeyValueStoreLookupSelector(const std::shared_ptr& kvs, const std::shared_ptr& lookupKey) +{ + return std::make_shared(kvs, lookupKey); +} + +std::shared_ptr getKeyValueStoreRangeLookupSelector(const std::shared_ptr& kvs, const std::shared_ptr& lookupKey) +{ + return std::make_shared(kvs, lookupKey); +} + +std::shared_ptr getAndSelector(const std::vector>& rules) +{ + return std::make_shared(rules); +} + +std::shared_ptr getOrSelector(const std::vector>& rules) +{ + return std::make_shared(rules); +} + +std::shared_ptr getNotSelector(const std::shared_ptr& rule) +{ + return std::make_shared(rule); +} + +std::shared_ptr getLuaSelector(const dnsdist::selectors::LuaSelectorFunction& func) +{ + return std::make_shared(func); +} + +std::shared_ptr getLuaFFISelector(const dnsdist::selectors::LuaSelectorFFIFunction& func) +{ + return std::make_shared(func); +} + +// NOLINTNEXTLINE(bugprone-suspicious-include) +#include "dnsdist-selectors-factory-generated.cc" + +} diff --git a/pdns/dnsdistdist/dnsdist-rules.hh b/pdns/dnsdistdist/dnsdist-rules.hh index 129da7c8b96f..aca662b9624e 100644 --- a/pdns/dnsdistdist/dnsdist-rules.hh +++ b/pdns/dnsdistdist/dnsdist-rules.hh @@ -21,1394 +21,19 @@ */ #pragma once -#include -#include -#include -#include +#include -#include "cachecleaner.hh" #include "dnsdist.hh" -#include "dnsdist-ecs.hh" -#include "dnsdist-kvs.hh" -#include "dnsdist-lua.hh" -#include "dnsdist-lua-ffi.hh" -#include "dolog.hh" -#include "dnsparser.hh" -#include "dns_random.hh" +#include "stat_t.hh" -class MaxQPSIPRule : public DNSRule +class DNSRule { public: - MaxQPSIPRule(unsigned int qps, unsigned int burst, unsigned int ipv4trunc=32, unsigned int ipv6trunc=64, unsigned int expiration=300, unsigned int cleanupDelay=60, unsigned int scanFraction=10, size_t shardsCount=10): - d_shards(shardsCount), d_qps(qps), d_burst(burst), d_ipv4trunc(ipv4trunc), d_ipv6trunc(ipv6trunc), d_cleanupDelay(cleanupDelay), d_expiration(expiration), d_scanFraction(scanFraction) + virtual ~DNSRule() { - d_cleaningUp.clear(); - gettime(&d_lastCleanup, true); } + virtual bool matches(const DNSQuestion* dq) const = 0; + virtual string toString() const = 0; - void clear() - { - for (auto& shard : d_shards) { - shard.lock()->clear(); - } - } - - size_t cleanup(const struct timespec& cutOff, size_t* scannedCount=nullptr) const - { - size_t removed = 0; - if (scannedCount != nullptr) { - *scannedCount = 0; - } - - for (auto& shard : d_shards) { - auto limits = shard.lock(); - const size_t toLook = std::round((1.0 * limits->size()) / d_scanFraction)+ 1; - size_t lookedAt = 0; - - auto& sequence = limits->get(); - for (auto entry = sequence.begin(); entry != sequence.end() && lookedAt < toLook; lookedAt++) { - if (entry->d_limiter.seenSince(cutOff)) { - /* entries are ordered from least recently seen to more recently - seen, as soon as we see one that has not expired yet, we are - done */ - lookedAt++; - break; - } - - entry = sequence.erase(entry); - removed++; - } - - if (scannedCount != nullptr) { - *scannedCount += lookedAt; - } - } - - return removed; - } - - void cleanupIfNeeded(const struct timespec& now) const - { - if (d_cleanupDelay > 0) { - struct timespec cutOff = d_lastCleanup; - cutOff.tv_sec += d_cleanupDelay; - - if (cutOff < now) { - try { - if (d_cleaningUp.test_and_set()) { - return; - } - - d_lastCleanup = now; - /* the QPS Limiter doesn't use realtime, be careful! */ - gettime(&cutOff, false); - cutOff.tv_sec -= d_expiration; - - cleanup(cutOff); - d_cleaningUp.clear(); - } - catch (...) { - d_cleaningUp.clear(); - throw; - } - } - } - } - - bool matches(const DNSQuestion* dq) const override - { - cleanupIfNeeded(dq->getQueryRealTime()); - - ComboAddress zeroport(dq->ids.origRemote); - zeroport.sin4.sin_port=0; - zeroport.truncate(zeroport.sin4.sin_family == AF_INET ? d_ipv4trunc : d_ipv6trunc); - auto hash = ComboAddress::addressOnlyHash()(zeroport); - auto& shard = d_shards[hash % d_shards.size()]; - { - auto limits = shard.lock(); - auto iter = limits->find(zeroport); - if (iter == limits->end()) { - Entry e(zeroport, QPSLimiter(d_qps, d_burst)); - iter = limits->insert(e).first; - } - - moveCacheItemToBack(*limits, iter); - return !iter->d_limiter.check(d_qps, d_burst); - } - } - - string toString() const override - { - return "IP (/"+std::to_string(d_ipv4trunc)+", /"+std::to_string(d_ipv6trunc)+") match for QPS over " + std::to_string(d_qps) + " burst "+ std::to_string(d_burst); - } - - size_t getEntriesCount() const - { - size_t count = 0; - for (auto& shard : d_shards) { - count += shard.lock()->size(); - } - return count; - } - - size_t getNumberOfShards() const - { - return d_shards.size(); - } - -private: - struct HashedTag {}; - struct SequencedTag {}; - struct Entry - { - Entry(const ComboAddress& addr, BasicQPSLimiter&& limiter): d_limiter(limiter), d_addr(addr) - { - } - mutable BasicQPSLimiter d_limiter; - ComboAddress d_addr; - }; - - typedef multi_index_container< - Entry, - indexed_by < - hashed_unique, member, ComboAddress::addressOnlyHash >, - sequenced > - > - > qpsContainer_t; - - mutable std::vector> d_shards; - mutable struct timespec d_lastCleanup; - const unsigned int d_qps, d_burst, d_ipv4trunc, d_ipv6trunc, d_cleanupDelay, d_expiration; - const unsigned int d_scanFraction{10}; - mutable std::atomic_flag d_cleaningUp; -}; - -class MaxQPSRule : public DNSRule -{ -public: - MaxQPSRule(unsigned int qps) - : d_qps(qps, qps) - {} - - MaxQPSRule(unsigned int qps, unsigned int burst) - : d_qps(qps, burst) - {} - - - bool matches(const DNSQuestion* qd) const override - { - return d_qps.check(); - } - - string toString() const override - { - return "Max " + std::to_string(d_qps.getRate()) + " qps"; - } - - -private: - mutable QPSLimiter d_qps; -}; - -class NMGRule : public DNSRule -{ -public: - NMGRule(const NetmaskGroup& nmg) : d_nmg(nmg) {} -protected: - NetmaskGroup d_nmg; -}; - -class NetmaskGroupRule : public NMGRule -{ -public: - NetmaskGroupRule(const NetmaskGroup& nmg, bool src, bool quiet = false) : NMGRule(nmg) - { - d_src = src; - d_quiet = quiet; - } - bool matches(const DNSQuestion* dq) const override - { - if(!d_src) { - return d_nmg.match(dq->ids.origDest); - } - return d_nmg.match(dq->ids.origRemote); - } - - string toString() const override - { - string ret = "Src: "; - if(!d_src) { - ret = "Dst: "; - } - if (d_quiet) { - return ret + "in-group"; - } - return ret + d_nmg.toString(); - } -private: - bool d_src; - bool d_quiet; -}; - -class TimedIPSetRule : public DNSRule, boost::noncopyable -{ -private: - struct IPv6 { - IPv6(const ComboAddress& ca) - { - static_assert(sizeof(*this)==16, "IPv6 struct has wrong size"); - memcpy((char*)this, ca.sin6.sin6_addr.s6_addr, 16); - } - bool operator==(const IPv6& rhs) const - { - return a==rhs.a && b==rhs.b; - } - uint64_t a, b; - }; - -public: - TimedIPSetRule() - { - } - ~TimedIPSetRule() - { - } - bool matches(const DNSQuestion* dq) const override - { - if (dq->ids.origRemote.sin4.sin_family == AF_INET) { - auto ip4s = d_ip4s.read_lock(); - auto fnd = ip4s->find(dq->ids.origRemote.sin4.sin_addr.s_addr); - if (fnd == ip4s->end()) { - return false; - } - return time(nullptr) < fnd->second; - } else { - auto ip6s = d_ip6s.read_lock(); - auto fnd = ip6s->find({dq->ids.origRemote}); - if (fnd == ip6s->end()) { - return false; - } - return time(nullptr) < fnd->second; - } - } - - void add(const ComboAddress& ca, time_t ttd) - { - // think twice before adding templates here - if (ca.sin4.sin_family == AF_INET) { - auto res = d_ip4s.write_lock()->insert({ca.sin4.sin_addr.s_addr, ttd}); - if (!res.second && (time_t)res.first->second < ttd) { - res.first->second = (uint32_t)ttd; - } - } - else { - auto res = d_ip6s.write_lock()->insert({{ca}, ttd}); - if (!res.second && (time_t)res.first->second < ttd) { - // coverity[store_truncates_time_t] - res.first->second = (uint32_t)ttd; - } - } - } - - void remove(const ComboAddress& ca) - { - if (ca.sin4.sin_family == AF_INET) { - d_ip4s.write_lock()->erase(ca.sin4.sin_addr.s_addr); - } - else { - d_ip6s.write_lock()->erase({ca}); - } - } - - void clear() - { - d_ip4s.write_lock()->clear(); - d_ip6s.write_lock()->clear(); - } - - void cleanup() - { - time_t now = time(nullptr); - { - auto ip4s = d_ip4s.write_lock(); - for (auto iter = ip4s->begin(); iter != ip4s->end(); ) { - if (iter->second < now) { - iter = ip4s->erase(iter); - } - else { - ++iter; - } - } - } - - { - auto ip6s = d_ip6s.write_lock(); - for (auto iter = ip6s->begin(); iter != ip6s->end(); ) { - if (iter->second < now) { - iter = ip6s->erase(iter); - } - else { - ++iter; - } - } - - } - - } - - string toString() const override - { - time_t now = time(nullptr); - uint64_t count = 0; - - for (const auto& ip : *(d_ip4s.read_lock())) { - if (now < ip.second) { - ++count; - } - } - - for (const auto& ip : *(d_ip6s.read_lock())) { - if (now < ip.second) { - ++count; - } - } - - return "Src: "+std::to_string(count)+" ips"; - } -private: - struct IPv6Hash - { - std::size_t operator()(const IPv6& ip) const - { - auto ah=std::hash{}(ip.a); - auto bh=std::hash{}(ip.b); - return ah & (bh<<1); - } - }; - mutable SharedLockGuarded> d_ip6s; - mutable SharedLockGuarded> d_ip4s; -}; - - -class AllRule : public DNSRule -{ -public: - AllRule() {} - bool matches(const DNSQuestion* dq) const override - { - return true; - } - - string toString() const override - { - return "All"; - } - -}; - - -class DNSSECRule : public DNSRule -{ -public: - DNSSECRule() - { - - } - bool matches(const DNSQuestion* dq) const override - { - return dq->getHeader()->cd || (dnsdist::getEDNSZ(*dq) & EDNS_HEADER_FLAG_DO); // turns out dig sets ad by default.. - } - - string toString() const override - { - return "DNSSEC"; - } -}; - -class AndRule : public DNSRule -{ -public: - AndRule(const std::vector > >& rules) - { - for (const auto& r : rules) { - d_rules.push_back(r.second); - } - } - - bool matches(const DNSQuestion* dq) const override - { - for (const auto& rule : d_rules) { - if (!rule->matches(dq)) { - return false; - } - } - return true; - } - - string toString() const override - { - string ret; - for (const auto& rule : d_rules) { - if (!ret.empty()) { - ret+= " && "; - } - ret += "("+ rule->toString()+")"; - } - return ret; - } -private: - std::vector > d_rules; -}; - - -class OrRule : public DNSRule -{ -public: - OrRule(const std::vector > >& rules) - { - for (const auto& r : rules) { - d_rules.push_back(r.second); - } - } - - bool matches(const DNSQuestion* dq) const override - { - for (const auto& rule: d_rules) { - if (rule->matches(dq)) { - return true; - } - } - return false; - } - - string toString() const override - { - string ret; - for (const auto& rule : d_rules) { - if (!ret.empty()) { - ret+= " || "; - } - ret += "("+ rule->toString()+")"; - } - return ret; - } -private: - std::vector > d_rules; -}; - - -class RegexRule : public DNSRule -{ -public: - RegexRule(const std::string& regex) : d_regex(regex), d_visual(regex) - { - - } - bool matches(const DNSQuestion* dq) const override - { - return d_regex.match(dq->ids.qname.toStringNoDot()); - } - - string toString() const override - { - return "Regex: "+d_visual; - } -private: - Regex d_regex; - string d_visual; -}; - -#ifdef HAVE_RE2 -#include -class RE2Rule : public DNSRule -{ -public: - RE2Rule(const std::string& re2) : d_re2(re2, RE2::Latin1), d_visual(re2) - { - - } - bool matches(const DNSQuestion* dq) const override - { - return RE2::FullMatch(dq->ids.qname.toStringNoDot(), d_re2); - } - - string toString() const override - { - return "RE2 match: "+d_visual; - } -private: - RE2 d_re2; - string d_visual; -}; -#endif - -#ifdef HAVE_DNS_OVER_HTTPS -class HTTPHeaderRule : public DNSRule -{ -public: - HTTPHeaderRule(const std::string& header, const std::string& regex); - bool matches(const DNSQuestion* dq) const override; - string toString() const override; -private: - string d_header; - Regex d_regex; - string d_visual; -}; - -class HTTPPathRule : public DNSRule -{ -public: - HTTPPathRule(std::string path); - bool matches(const DNSQuestion* dq) const override; - string toString() const override; -private: - string d_path; -}; - -class HTTPPathRegexRule : public DNSRule -{ -public: - HTTPPathRegexRule(const std::string& regex); - bool matches(const DNSQuestion* dq) const override; - string toString() const override; -private: - Regex d_regex; - std::string d_visual; -}; -#endif - -class SNIRule : public DNSRule -{ -public: - SNIRule(const std::string& name) : d_sni(name) - { - } - bool matches(const DNSQuestion* dq) const override - { - return dq->sni == d_sni; - } - string toString() const override - { - return "SNI == " + d_sni; - } -private: - std::string d_sni; -}; - -class SuffixMatchNodeRule : public DNSRule -{ -public: - SuffixMatchNodeRule(const SuffixMatchNode& smn, bool quiet=false) : d_smn(smn), d_quiet(quiet) - { - } - bool matches(const DNSQuestion* dq) const override - { - return d_smn.check(dq->ids.qname); - } - string toString() const override - { - if(d_quiet) - return "qname==in-set"; - else - return "qname in "+d_smn.toString(); - } -private: - SuffixMatchNode d_smn; - bool d_quiet; -}; - -class QNameRule : public DNSRule -{ -public: - QNameRule(const DNSName& qname) : d_qname(qname) - { - } - - bool matches(const DNSQuestion* dq) const override - { - return d_qname==dq->ids.qname; - } - string toString() const override - { - return "qname=="+d_qname.toString(); - } -private: - DNSName d_qname; -}; - -class QNameSetRule : public DNSRule { -public: - QNameSetRule(const DNSNameSet& names) : qname_idx(names) {} - - bool matches(const DNSQuestion* dq) const override { - return qname_idx.find(dq->ids.qname) != qname_idx.end(); - } - - string toString() const override { - std::stringstream ss; - ss << "qname in DNSNameSet(" << qname_idx.size() << " FQDNs)"; - return ss.str(); - } -private: - DNSNameSet qname_idx; -}; - -class QTypeRule : public DNSRule -{ -public: - QTypeRule(uint16_t qtype) : d_qtype(qtype) - { - } - bool matches(const DNSQuestion* dq) const override - { - return d_qtype == dq->ids.qtype; - } - string toString() const override - { - QType qt(d_qtype); - return "qtype=="+qt.toString(); - } -private: - uint16_t d_qtype; -}; - -class QClassRule : public DNSRule -{ -public: - QClassRule(uint16_t qclass) : d_qclass(qclass) - { - } - bool matches(const DNSQuestion* dq) const override - { - return d_qclass == dq->ids.qclass; - } - string toString() const override - { - return "qclass=="+std::to_string(d_qclass); - } -private: - uint16_t d_qclass; -}; - -class OpcodeRule : public DNSRule -{ -public: - OpcodeRule(uint8_t opcode) : d_opcode(opcode) - { - } - bool matches(const DNSQuestion* dq) const override - { - return d_opcode == dq->getHeader()->opcode; - } - string toString() const override - { - return "opcode=="+std::to_string(d_opcode); - } -private: - uint8_t d_opcode; -}; - -class DSTPortRule : public DNSRule -{ -public: - DSTPortRule(uint16_t port) : d_port(port) - { - } - bool matches(const DNSQuestion* dq) const override - { - return htons(d_port) == dq->ids.origDest.sin4.sin_port; - } - string toString() const override - { - return "dst port=="+std::to_string(d_port); - } -private: - uint16_t d_port; -}; - -class TCPRule : public DNSRule -{ -public: - TCPRule(bool tcp): d_tcp(tcp) - { - } - bool matches(const DNSQuestion* dq) const override - { - return dq->overTCP() == d_tcp; - } - string toString() const override - { - return (d_tcp ? "TCP" : "UDP"); - } -private: - bool d_tcp; -}; - - -class NotRule : public DNSRule -{ -public: - NotRule(const std::shared_ptr& rule): d_rule(rule) - { - } - bool matches(const DNSQuestion* dq) const override - { - return !d_rule->matches(dq); - } - string toString() const override - { - return "!("+ d_rule->toString()+")"; - } -private: - std::shared_ptr d_rule; -}; - -class RecordsCountRule : public DNSRule -{ -public: - RecordsCountRule(uint8_t section, uint16_t minCount, uint16_t maxCount): d_minCount(minCount), d_maxCount(maxCount), d_section(section) - { - } - bool matches(const DNSQuestion* dq) const override - { - uint16_t count = 0; - switch(d_section) { - case 0: - count = ntohs(dq->getHeader()->qdcount); - break; - case 1: - count = ntohs(dq->getHeader()->ancount); - break; - case 2: - count = ntohs(dq->getHeader()->nscount); - break; - case 3: - count = ntohs(dq->getHeader()->arcount); - break; - } - return count >= d_minCount && count <= d_maxCount; - } - string toString() const override - { - string section; - switch(d_section) { - case 0: - section = "QD"; - break; - case 1: - section = "AN"; - break; - case 2: - section = "NS"; - break; - case 3: - section = "AR"; - break; - } - return std::to_string(d_minCount) + " <= records in " + section + " <= "+ std::to_string(d_maxCount); - } -private: - uint16_t d_minCount; - uint16_t d_maxCount; - uint8_t d_section; -}; - -class RecordsTypeCountRule : public DNSRule -{ -public: - RecordsTypeCountRule(uint8_t section, uint16_t type, uint16_t minCount, uint16_t maxCount): d_type(type), d_minCount(minCount), d_maxCount(maxCount), d_section(section) - { - } - bool matches(const DNSQuestion* dq) const override - { - uint16_t count = 0; - switch(d_section) { - case 0: - count = ntohs(dq->getHeader()->qdcount); - break; - case 1: - count = ntohs(dq->getHeader()->ancount); - break; - case 2: - count = ntohs(dq->getHeader()->nscount); - break; - case 3: - count = ntohs(dq->getHeader()->arcount); - break; - } - if (count < d_minCount) { - return false; - } - count = getRecordsOfTypeCount(reinterpret_cast(dq->getData().data()), dq->getData().size(), d_section, d_type); - return count >= d_minCount && count <= d_maxCount; - } - string toString() const override - { - string section; - switch(d_section) { - case 0: - section = "QD"; - break; - case 1: - section = "AN"; - break; - case 2: - section = "NS"; - break; - case 3: - section = "AR"; - break; - } - return std::to_string(d_minCount) + " <= " + QType(d_type).toString() + " records in " + section + " <= "+ std::to_string(d_maxCount); - } -private: - uint16_t d_type; - uint16_t d_minCount; - uint16_t d_maxCount; - uint8_t d_section; -}; - -class TrailingDataRule : public DNSRule -{ -public: - TrailingDataRule() - { - } - bool matches(const DNSQuestion* dq) const override - { - uint16_t length = getDNSPacketLength(reinterpret_cast(dq->getData().data()), dq->getData().size()); - return length < dq->getData().size(); - } - string toString() const override - { - return "trailing data"; - } -}; - -class QNameLabelsCountRule : public DNSRule -{ -public: - QNameLabelsCountRule(unsigned int minLabelsCount, unsigned int maxLabelsCount): d_min(minLabelsCount), d_max(maxLabelsCount) - { - } - bool matches(const DNSQuestion* dq) const override - { - unsigned int count = dq->ids.qname.countLabels(); - return count < d_min || count > d_max; - } - string toString() const override - { - return "labels count < " + std::to_string(d_min) + " || labels count > " + std::to_string(d_max); - } -private: - unsigned int d_min; - unsigned int d_max; -}; - -class QNameWireLengthRule : public DNSRule -{ -public: - QNameWireLengthRule(size_t min, size_t max): d_min(min), d_max(max) - { - } - bool matches(const DNSQuestion* dq) const override - { - size_t const wirelength = dq->ids.qname.wirelength(); - return wirelength < d_min || wirelength > d_max; - } - string toString() const override - { - return "wire length < " + std::to_string(d_min) + " || wire length > " + std::to_string(d_max); - } -private: - size_t d_min; - size_t d_max; -}; - -class RCodeRule : public DNSRule -{ -public: - RCodeRule(uint8_t rcode) : d_rcode(rcode) - { - } - bool matches(const DNSQuestion* dq) const override - { - return d_rcode == dq->getHeader()->rcode; - } - string toString() const override - { - return "rcode=="+RCode::to_s(d_rcode); - } -private: - uint8_t d_rcode; -}; - -class ERCodeRule : public DNSRule -{ -public: - ERCodeRule(uint8_t rcode) : d_rcode(rcode & 0xF), d_extrcode(rcode >> 4) - { - } - bool matches(const DNSQuestion* dq) const override - { - // avoid parsing EDNS OPT RR when not needed. - if (d_rcode != dq->getHeader()->rcode) { - return false; - } - - EDNS0Record edns0; - if (!getEDNS0Record(dq->getData(), edns0)) { - return false; - } - - return d_extrcode == edns0.extRCode; - } - string toString() const override - { - return "ercode=="+ERCode::to_s(d_rcode | (d_extrcode << 4)); - } -private: - uint8_t d_rcode; // plain DNS Rcode - uint8_t d_extrcode; // upper bits in EDNS0 record -}; - -class EDNSVersionRule : public DNSRule -{ -public: - EDNSVersionRule(uint8_t version) : d_version(version) - { - } - bool matches(const DNSQuestion* dq) const override - { - EDNS0Record edns0; - if (!getEDNS0Record(dq->getData(), edns0)) { - return false; - } - - return d_version < edns0.version; - } - string toString() const override - { - return "ednsversion>"+std::to_string(d_version); - } -private: - uint8_t d_version; -}; - -class EDNSOptionRule : public DNSRule -{ -public: - EDNSOptionRule(uint16_t optcode) : d_optcode(optcode) - { - } - bool matches(const DNSQuestion* dq) const override - { - uint16_t optStart; - size_t optLen = 0; - bool last = false; - int res = locateEDNSOptRR(dq->getData(), &optStart, &optLen, &last); - if (res != 0) { - // no EDNS OPT RR - return false; - } - - if (optLen < optRecordMinimumSize) { - return false; - } - - if (optStart < dq->getData().size() && dq->getData().at(optStart) != 0) { - // OPT RR Name != '.' - return false; - } - - return isEDNSOptionInOpt(dq->getData(), optStart, optLen, d_optcode); - } - string toString() const override - { - return "ednsoptcode=="+std::to_string(d_optcode); - } -private: - uint16_t d_optcode; -}; - -class RDRule : public DNSRule -{ -public: - RDRule() - { - } - bool matches(const DNSQuestion* dq) const override - { - return dq->getHeader()->rd == 1; - } - string toString() const override - { - return "rd==1"; - } -}; - -class ProbaRule : public DNSRule -{ -public: - ProbaRule(double proba) : d_proba(proba) - { - } - bool matches(const DNSQuestion* dq) const override - { - if(d_proba == 1.0) - return true; - double rnd = 1.0*dns_random_uint32() / UINT32_MAX; - return rnd > (1.0 - d_proba); - } - string toString() const override - { - return "match with prob. " + (boost::format("%0.2f") % d_proba).str(); - } -private: - double d_proba; -}; - -class TagRule : public DNSRule -{ -public: - TagRule(const std::string& tag, boost::optional value) : d_value(std::move(value)), d_tag(tag) - { - } - bool matches(const DNSQuestion* dq) const override - { - if (!dq->ids.qTag) { - return false; - } - - const auto it = dq->ids.qTag->find(d_tag); - if (it == dq->ids.qTag->cend()) { - return false; - } - - if (!d_value) { - return true; - } - - return it->second == *d_value; - } - - string toString() const override - { - return "tag '" + d_tag + "' is set" + (d_value ? (" to '" + *d_value + "'") : ""); - } - -private: - boost::optional d_value; - std::string d_tag; -}; - -class PoolAvailableRule : public DNSRule -{ -public: - PoolAvailableRule(const std::string& poolname) : d_poolname(poolname) - { - } - - bool matches(const DNSQuestion* dq) const override - { - return (getPool(d_poolname)->countServers(true) > 0); - } - - string toString() const override - { - return "pool '" + d_poolname + "' is available"; - } -private: - std::string d_poolname; -}; - -class PoolOutstandingRule : public DNSRule -{ -public: - PoolOutstandingRule(const std::string& poolname, const size_t limit) : d_poolname(poolname), d_limit(limit) - { - } - - bool matches(const DNSQuestion* dq) const override - { - return (getPool(d_poolname)->poolLoad()) > d_limit; - } - - string toString() const override - { - return "pool '" + d_poolname + "' outstanding > " + std::to_string(d_limit); - } -private: - std::string d_poolname; - size_t d_limit; -}; - -class KeyValueStoreLookupRule: public DNSRule -{ -public: - KeyValueStoreLookupRule(std::shared_ptr& kvs, std::shared_ptr& lookupKey): d_kvs(kvs), d_key(lookupKey) - { - } - - bool matches(const DNSQuestion* dq) const override - { - std::vector keys = d_key->getKeys(*dq); - for (const auto& key : keys) { - if (d_kvs->keyExists(key) == true) { - return true; - } - } - - return false; - } - - string toString() const override - { - return "lookup key-value store based on '" + d_key->toString() + "'"; - } - -private: - std::shared_ptr d_kvs; - std::shared_ptr d_key; -}; - -class KeyValueStoreRangeLookupRule: public DNSRule -{ -public: - KeyValueStoreRangeLookupRule(std::shared_ptr& kvs, std::shared_ptr& lookupKey): d_kvs(kvs), d_key(lookupKey) - { - } - - bool matches(const DNSQuestion* dq) const override - { - std::vector keys = d_key->getKeys(*dq); - for (const auto& key : keys) { - std::string value; - if (d_kvs->getRangeValue(key, value) == true) { - return true; - } - } - - return false; - } - - string toString() const override - { - return "range-based lookup key-value store based on '" + d_key->toString() + "'"; - } - -private: - std::shared_ptr d_kvs; - std::shared_ptr d_key; -}; - -class LuaRule : public DNSRule -{ -public: - typedef std::function func_t; - LuaRule(const func_t& func): d_func(func) - {} - - bool matches(const DNSQuestion* dq) const override - { - try { - auto lock = g_lua.lock(); - return d_func(dq); - } catch (const std::exception &e) { - warnlog("LuaRule failed inside Lua: %s", e.what()); - } catch (...) { - warnlog("LuaRule failed inside Lua: [unknown exception]"); - } - return false; - } - - string toString() const override - { - return "Lua script"; - } -private: - func_t d_func; -}; - -class LuaFFIRule : public DNSRule -{ -public: - typedef std::function func_t; - LuaFFIRule(const func_t& func): d_func(func) - {} - - bool matches(const DNSQuestion* dq) const override - { - dnsdist_ffi_dnsquestion_t dqffi(const_cast(dq)); - try { - auto lock = g_lua.lock(); - return d_func(&dqffi); - } catch (const std::exception &e) { - warnlog("LuaFFIRule failed inside Lua: %s", e.what()); - } catch (...) { - warnlog("LuaFFIRule failed inside Lua: [unknown exception]"); - } - return false; - } - - string toString() const override - { - return "Lua FFI script"; - } -private: - func_t d_func; -}; - -class LuaFFIPerThreadRule : public DNSRule -{ -public: - typedef std::function func_t; - - LuaFFIPerThreadRule(const std::string& code): d_functionCode(code), d_functionID(s_functionsCounter++) - { - } - - bool matches(const DNSQuestion* dq) 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 false; - } - - dnsdist_ffi_dnsquestion_t dqffi(const_cast(dq)); - return state.d_func(&dqffi); - } - catch (const std::exception &e) { - warnlog("LuaFFIPerthreadRule failed inside Lua: %s", e.what()); - } - catch (...) { - warnlog("LuaFFIPerThreadRule failed inside Lua: [unknown exception]"); - } - return false; - } - - 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; - const std::string d_functionCode; - const uint64_t d_functionID; -}; - -class ProxyProtocolValueRule : public DNSRule -{ -public: - ProxyProtocolValueRule(uint8_t type, boost::optional value): d_value(std::move(value)), d_type(type) - { - } - - bool matches(const DNSQuestion* dq) const override - { - if (!dq->proxyProtocolValues) { - return false; - } - - for (const auto& entry : *dq->proxyProtocolValues) { - if (entry.type == d_type && (!d_value || entry.content == *d_value)) { - return true; - } - } - - return false; - } - - string toString() const override - { - if (d_value) { - return "proxy protocol value of type " + std::to_string(d_type) + " matches"; - } - return "proxy protocol value of type " + std::to_string(d_type) + " is present"; - } - -private: - boost::optional d_value; - uint8_t d_type; -}; - -class PayloadSizeRule : public DNSRule -{ - enum class Comparisons : uint8_t { equal, greater, greaterOrEqual, smaller, smallerOrEqual }; -public: - PayloadSizeRule(const std::string& comparison, uint16_t size): d_size(size) - { - if (comparison == "equal") { - d_comparison = Comparisons::equal; - } - else if (comparison == "greater") { - d_comparison = Comparisons::greater; - } - else if (comparison == "greaterOrEqual") { - d_comparison = Comparisons::greaterOrEqual; - } - else if (comparison == "smaller") { - d_comparison = Comparisons::smaller; - } - else if (comparison == "smallerOrEqual") { - d_comparison = Comparisons::smallerOrEqual; - } - else { - throw std::runtime_error("Unsupported comparison '" + comparison + "'"); - } - } - - bool matches(const DNSQuestion* dq) const override - { - const auto size = dq->getData().size(); - - switch (d_comparison) { - case Comparisons::equal: - return size == d_size; - case Comparisons::greater: - return size > d_size; - case Comparisons::greaterOrEqual: - return size >= d_size; - case Comparisons::smaller: - return size < d_size; - case Comparisons::smallerOrEqual: - return size <= d_size; - default: - return false; - } - } - - string toString() const override - { - static const std::array comparisonStr{ - "equal to" , - "greater than", - "equal to or greater than", - "smaller than", - "equal to or smaller than" - }; - return "payload size is " + comparisonStr.at(static_cast(d_comparison)) + " " + std::to_string(d_size); - } - -private: - uint16_t d_size; - Comparisons d_comparison; + mutable stat_t d_matches{0}; }; diff --git a/pdns/dnsdistdist/dnsdist-rust-bridge-actions-generated.cc b/pdns/dnsdistdist/dnsdist-rust-bridge-actions-generated.cc new file mode 100644 index 000000000000..c4de7e5bb238 --- /dev/null +++ b/pdns/dnsdistdist/dnsdist-rust-bridge-actions-generated.cc @@ -0,0 +1,216 @@ +// !! This file has been generated by dnsdist-settings-generator.py, do not edit by hand!! +std::shared_ptr getAllowAction(const AllowActionConfiguration& config) +{ + auto action = dnsdist::actions::getAllowAction(); + return newDNSActionWrapper(std::move(action), config.name); +} +std::shared_ptr getDelayAction(const DelayActionConfiguration& config) +{ + auto action = dnsdist::actions::getDelayAction(config.msec); + return newDNSActionWrapper(std::move(action), config.name); +} +std::shared_ptr getDropAction(const DropActionConfiguration& config) +{ + auto action = dnsdist::actions::getDropAction(); + return newDNSActionWrapper(std::move(action), config.name); +} +std::shared_ptr getSetEDNSOptionAction(const SetEDNSOptionActionConfiguration& config) +{ + auto action = dnsdist::actions::getSetEDNSOptionAction(config.code, std::string(config.data)); + return newDNSActionWrapper(std::move(action), config.name); +} +std::shared_ptr getERCodeAction(const ERCodeActionConfiguration& config) +{ + auto action = dnsdist::actions::getERCodeAction(config.rcode, convertResponseConfig(config.vars)); + return newDNSActionWrapper(std::move(action), config.name); +} +std::shared_ptr getHTTPStatusAction(const HTTPStatusActionConfiguration& config) +{ + auto action = dnsdist::actions::getHTTPStatusAction(config.status, PacketBuffer(config.body.data(), config.body.data() + config.body.size()), std::string(config.content_type), convertResponseConfig(config.vars)); + return newDNSActionWrapper(std::move(action), config.name); +} +std::shared_ptr getLogAction(const LogActionConfiguration& config) +{ + auto action = dnsdist::actions::getLogAction(std::string(config.file_name), config.binary, config.append, config.buffered, config.verbose_only, config.include_timestamp); + return newDNSActionWrapper(std::move(action), config.name); +} +std::shared_ptr getLuaFFIPerThreadAction(const LuaFFIPerThreadActionConfiguration& config) +{ + auto action = dnsdist::actions::getLuaFFIPerThreadAction(std::string(config.code)); + return newDNSActionWrapper(std::move(action), config.name); +} +std::shared_ptr getNegativeAndSOAAction(const NegativeAndSOAActionConfiguration& config) +{ + auto action = dnsdist::actions::getNegativeAndSOAAction(config.nxd, DNSName(std::string(config.zone)), config.ttl, DNSName(std::string(config.mname)), DNSName(std::string(config.rname)), convertSOAParams(config.soa_parameters), config.soa_in_authority, convertResponseConfig(config.vars)); + return newDNSActionWrapper(std::move(action), config.name); +} +std::shared_ptr getNoneAction(const NoneActionConfiguration& config) +{ + auto action = dnsdist::actions::getNoneAction(); + return newDNSActionWrapper(std::move(action), config.name); +} +std::shared_ptr getPoolAction(const PoolActionConfiguration& config) +{ + auto action = dnsdist::actions::getPoolAction(std::string(config.pool_name), config.stop_processing); + return newDNSActionWrapper(std::move(action), config.name); +} +std::shared_ptr getQPSAction(const QPSActionConfiguration& config) +{ + auto action = dnsdist::actions::getQPSAction(config.limit); + return newDNSActionWrapper(std::move(action), config.name); +} +std::shared_ptr getQPSPoolAction(const QPSPoolActionConfiguration& config) +{ + auto action = dnsdist::actions::getQPSPoolAction(config.limit, std::string(config.pool_name), config.stop_processing); + return newDNSActionWrapper(std::move(action), config.name); +} +std::shared_ptr getRCodeAction(const RCodeActionConfiguration& config) +{ + auto action = dnsdist::actions::getRCodeAction(config.rcode, convertResponseConfig(config.vars)); + return newDNSActionWrapper(std::move(action), config.name); +} +std::shared_ptr getSetAdditionalProxyProtocolValueAction(const SetAdditionalProxyProtocolValueActionConfiguration& config) +{ + auto action = dnsdist::actions::getSetAdditionalProxyProtocolValueAction(config.proxy_type, std::string(config.value)); + return newDNSActionWrapper(std::move(action), config.name); +} +std::shared_ptr getSetDisableECSAction(const SetDisableECSActionConfiguration& config) +{ + auto action = dnsdist::actions::getSetDisableECSAction(); + return newDNSActionWrapper(std::move(action), config.name); +} +std::shared_ptr getSetDisableValidationAction(const SetDisableValidationActionConfiguration& config) +{ + auto action = dnsdist::actions::getSetDisableValidationAction(); + return newDNSActionWrapper(std::move(action), config.name); +} +std::shared_ptr getSetECSAction(const SetECSActionConfiguration& config) +{ + auto action = dnsdist::actions::getSetECSAction(std::string(config.ipv4), std::string(config.ipv6)); + return newDNSActionWrapper(std::move(action), config.name); +} +std::shared_ptr getSetECSOverrideAction(const SetECSOverrideActionConfiguration& config) +{ + auto action = dnsdist::actions::getSetECSOverrideAction(config.override_existing); + return newDNSActionWrapper(std::move(action), config.name); +} +std::shared_ptr getSetECSPrefixLengthAction(const SetECSPrefixLengthActionConfiguration& config) +{ + auto action = dnsdist::actions::getSetECSPrefixLengthAction(config.ipv4, config.ipv6); + return newDNSActionWrapper(std::move(action), config.name); +} +std::shared_ptr getSetExtendedDNSErrorAction(const SetExtendedDNSErrorActionConfiguration& config) +{ + auto action = dnsdist::actions::getSetExtendedDNSErrorAction(config.info_code, std::string(config.extra_text)); + return newDNSActionWrapper(std::move(action), config.name); +} +std::shared_ptr getSetMacAddrAction(const SetMacAddrActionConfiguration& config) +{ + auto action = dnsdist::actions::getSetMacAddrAction(config.code); + return newDNSActionWrapper(std::move(action), config.name); +} +std::shared_ptr getSetMaxReturnedTTLAction(const SetMaxReturnedTTLActionConfiguration& config) +{ + auto action = dnsdist::actions::getSetMaxReturnedTTLAction(config.max); + return newDNSActionWrapper(std::move(action), config.name); +} +std::shared_ptr getSetNoRecurseAction(const SetNoRecurseActionConfiguration& config) +{ + auto action = dnsdist::actions::getSetNoRecurseAction(); + return newDNSActionWrapper(std::move(action), config.name); +} +std::shared_ptr getSetSkipCacheAction(const SetSkipCacheActionConfiguration& config) +{ + auto action = dnsdist::actions::getSetSkipCacheAction(); + return newDNSActionWrapper(std::move(action), config.name); +} +std::shared_ptr getSetTagAction(const SetTagActionConfiguration& config) +{ + auto action = dnsdist::actions::getSetTagAction(std::string(config.tag), std::string(config.value)); + return newDNSActionWrapper(std::move(action), config.name); +} +std::shared_ptr getSetTempFailureCacheTTLAction(const SetTempFailureCacheTTLActionConfiguration& config) +{ + auto action = dnsdist::actions::getSetTempFailureCacheTTLAction(config.ttl); + return newDNSActionWrapper(std::move(action), config.name); +} +std::shared_ptr getSNMPTrapAction(const SNMPTrapActionConfiguration& config) +{ + auto action = dnsdist::actions::getSNMPTrapAction(std::string(config.reason)); + return newDNSActionWrapper(std::move(action), config.name); +} +std::shared_ptr getSpoofSVCAction(const SpoofSVCActionConfiguration& config) +{ + auto action = dnsdist::actions::getSpoofSVCAction(convertSVCRecordParameters(config.parameters), convertResponseConfig(config.vars)); + return newDNSActionWrapper(std::move(action), config.name); +} +std::shared_ptr getTCAction(const TCActionConfiguration& config) +{ + auto action = dnsdist::actions::getTCAction(); + return newDNSActionWrapper(std::move(action), config.name); +} +std::shared_ptr getTeeAction(const TeeActionConfiguration& config) +{ + auto action = dnsdist::actions::getTeeAction(ComboAddress(std::string(config.rca)), ComboAddress(std::string(config.lca)), config.add_ecs, config.add_proxy_protocol); + return newDNSActionWrapper(std::move(action), config.name); +} +std::shared_ptr getAllowResponseAction(const AllowResponseActionConfiguration& config) +{ + auto action = dnsdist::actions::getAllowResponseAction(); + return newDNSResponseActionWrapper(std::move(action), config.name); +} +std::shared_ptr getDelayResponseAction(const DelayResponseActionConfiguration& config) +{ + auto action = dnsdist::actions::getDelayResponseAction(config.msec); + return newDNSResponseActionWrapper(std::move(action), config.name); +} +std::shared_ptr getDropResponseAction(const DropResponseActionConfiguration& config) +{ + auto action = dnsdist::actions::getDropResponseAction(); + return newDNSResponseActionWrapper(std::move(action), config.name); +} +std::shared_ptr getLogResponseAction(const LogResponseActionConfiguration& config) +{ + auto action = dnsdist::actions::getLogResponseAction(std::string(config.file_name), config.append, config.buffered, config.verbose_only, config.include_timestamp); + return newDNSResponseActionWrapper(std::move(action), config.name); +} +std::shared_ptr getLuaFFIPerThreadResponseAction(const LuaFFIPerThreadResponseActionConfiguration& config) +{ + auto action = dnsdist::actions::getLuaFFIPerThreadResponseAction(std::string(config.code)); + return newDNSResponseActionWrapper(std::move(action), config.name); +} +std::shared_ptr getSetExtendedDNSErrorResponseAction(const SetExtendedDNSErrorResponseActionConfiguration& config) +{ + auto action = dnsdist::actions::getSetExtendedDNSErrorResponseAction(config.info_code, std::string(config.extra_text)); + return newDNSResponseActionWrapper(std::move(action), config.name); +} +std::shared_ptr getSetMaxReturnedTTLResponseAction(const SetMaxReturnedTTLResponseActionConfiguration& config) +{ + auto action = dnsdist::actions::getSetMaxReturnedTTLResponseAction(config.max); + return newDNSResponseActionWrapper(std::move(action), config.name); +} +std::shared_ptr getSetReducedTTLResponseAction(const SetReducedTTLResponseActionConfiguration& config) +{ + auto action = dnsdist::actions::getSetReducedTTLResponseAction(config.percentage); + return newDNSResponseActionWrapper(std::move(action), config.name); +} +std::shared_ptr getSetSkipCacheResponseAction(const SetSkipCacheResponseActionConfiguration& config) +{ + auto action = dnsdist::actions::getSetSkipCacheResponseAction(); + return newDNSResponseActionWrapper(std::move(action), config.name); +} +std::shared_ptr getSetTagResponseAction(const SetTagResponseActionConfiguration& config) +{ + auto action = dnsdist::actions::getSetTagResponseAction(std::string(config.tag), std::string(config.value)); + return newDNSResponseActionWrapper(std::move(action), config.name); +} +std::shared_ptr getSNMPTrapResponseAction(const SNMPTrapResponseActionConfiguration& config) +{ + auto action = dnsdist::actions::getSNMPTrapResponseAction(std::string(config.reason)); + return newDNSResponseActionWrapper(std::move(action), config.name); +} +std::shared_ptr getTCResponseAction(const TCResponseActionConfiguration& config) +{ + auto action = dnsdist::actions::getTCResponseAction(); + return newDNSResponseActionWrapper(std::move(action), config.name); +} diff --git a/pdns/dnsdistdist/dnsdist-rust-bridge-actions-generated.hh b/pdns/dnsdistdist/dnsdist-rust-bridge-actions-generated.hh new file mode 100644 index 000000000000..e628aad99df0 --- /dev/null +++ b/pdns/dnsdistdist/dnsdist-rust-bridge-actions-generated.hh @@ -0,0 +1,127 @@ +// !! This file has been generated by dnsdist-settings-generator.py, do not edit by hand!! +struct AllowActionConfiguration; +std::shared_ptr getAllowAction(const AllowActionConfiguration& config); +struct ContinueActionConfiguration; +std::shared_ptr getContinueAction(const ContinueActionConfiguration& config); +struct DelayActionConfiguration; +std::shared_ptr getDelayAction(const DelayActionConfiguration& config); +struct DnstapLogActionConfiguration; +std::shared_ptr getDnstapLogAction(const DnstapLogActionConfiguration& config); +struct DropActionConfiguration; +std::shared_ptr getDropAction(const DropActionConfiguration& config); +struct SetEDNSOptionActionConfiguration; +std::shared_ptr getSetEDNSOptionAction(const SetEDNSOptionActionConfiguration& config); +struct ERCodeActionConfiguration; +std::shared_ptr getERCodeAction(const ERCodeActionConfiguration& config); +struct HTTPStatusActionConfiguration; +std::shared_ptr getHTTPStatusAction(const HTTPStatusActionConfiguration& config); +struct KeyValueStoreLookupActionConfiguration; +std::shared_ptr getKeyValueStoreLookupAction(const KeyValueStoreLookupActionConfiguration& config); +struct KeyValueStoreRangeLookupActionConfiguration; +std::shared_ptr getKeyValueStoreRangeLookupAction(const KeyValueStoreRangeLookupActionConfiguration& config); +struct LogActionConfiguration; +std::shared_ptr getLogAction(const LogActionConfiguration& config); +struct LuaActionConfiguration; +std::shared_ptr getLuaAction(const LuaActionConfiguration& config); +struct LuaFFIActionConfiguration; +std::shared_ptr getLuaFFIAction(const LuaFFIActionConfiguration& config); +struct LuaFFIPerThreadActionConfiguration; +std::shared_ptr getLuaFFIPerThreadAction(const LuaFFIPerThreadActionConfiguration& config); +struct NegativeAndSOAActionConfiguration; +std::shared_ptr getNegativeAndSOAAction(const NegativeAndSOAActionConfiguration& config); +struct NoneActionConfiguration; +std::shared_ptr getNoneAction(const NoneActionConfiguration& config); +struct PoolActionConfiguration; +std::shared_ptr getPoolAction(const PoolActionConfiguration& config); +struct QPSActionConfiguration; +std::shared_ptr getQPSAction(const QPSActionConfiguration& config); +struct QPSPoolActionConfiguration; +std::shared_ptr getQPSPoolAction(const QPSPoolActionConfiguration& config); +struct RCodeActionConfiguration; +std::shared_ptr getRCodeAction(const RCodeActionConfiguration& config); +struct RemoteLogActionConfiguration; +std::shared_ptr getRemoteLogAction(const RemoteLogActionConfiguration& config); +struct SetAdditionalProxyProtocolValueActionConfiguration; +std::shared_ptr getSetAdditionalProxyProtocolValueAction(const SetAdditionalProxyProtocolValueActionConfiguration& config); +struct SetDisableECSActionConfiguration; +std::shared_ptr getSetDisableECSAction(const SetDisableECSActionConfiguration& config); +struct SetDisableValidationActionConfiguration; +std::shared_ptr getSetDisableValidationAction(const SetDisableValidationActionConfiguration& config); +struct SetECSActionConfiguration; +std::shared_ptr getSetECSAction(const SetECSActionConfiguration& config); +struct SetECSOverrideActionConfiguration; +std::shared_ptr getSetECSOverrideAction(const SetECSOverrideActionConfiguration& config); +struct SetECSPrefixLengthActionConfiguration; +std::shared_ptr getSetECSPrefixLengthAction(const SetECSPrefixLengthActionConfiguration& config); +struct SetExtendedDNSErrorActionConfiguration; +std::shared_ptr getSetExtendedDNSErrorAction(const SetExtendedDNSErrorActionConfiguration& config); +struct SetMacAddrActionConfiguration; +std::shared_ptr getSetMacAddrAction(const SetMacAddrActionConfiguration& config); +struct SetMaxReturnedTTLActionConfiguration; +std::shared_ptr getSetMaxReturnedTTLAction(const SetMaxReturnedTTLActionConfiguration& config); +struct SetNoRecurseActionConfiguration; +std::shared_ptr getSetNoRecurseAction(const SetNoRecurseActionConfiguration& config); +struct SetProxyProtocolValuesActionConfiguration; +std::shared_ptr getSetProxyProtocolValuesAction(const SetProxyProtocolValuesActionConfiguration& config); +struct SetSkipCacheActionConfiguration; +std::shared_ptr getSetSkipCacheAction(const SetSkipCacheActionConfiguration& config); +struct SetTagActionConfiguration; +std::shared_ptr getSetTagAction(const SetTagActionConfiguration& config); +struct SetTempFailureCacheTTLActionConfiguration; +std::shared_ptr getSetTempFailureCacheTTLAction(const SetTempFailureCacheTTLActionConfiguration& config); +struct SNMPTrapActionConfiguration; +std::shared_ptr getSNMPTrapAction(const SNMPTrapActionConfiguration& config); +struct SpoofActionConfiguration; +std::shared_ptr getSpoofAction(const SpoofActionConfiguration& config); +struct SpoofCNAMEActionConfiguration; +std::shared_ptr getSpoofCNAMEAction(const SpoofCNAMEActionConfiguration& config); +struct SpoofPacketActionConfiguration; +std::shared_ptr getSpoofPacketAction(const SpoofPacketActionConfiguration& config); +struct SpoofRawActionConfiguration; +std::shared_ptr getSpoofRawAction(const SpoofRawActionConfiguration& config); +struct SpoofSVCActionConfiguration; +std::shared_ptr getSpoofSVCAction(const SpoofSVCActionConfiguration& config); +struct TCActionConfiguration; +std::shared_ptr getTCAction(const TCActionConfiguration& config); +struct TeeActionConfiguration; +std::shared_ptr getTeeAction(const TeeActionConfiguration& config); +struct AllowResponseActionConfiguration; +std::shared_ptr getAllowResponseAction(const AllowResponseActionConfiguration& config); +struct ClearRecordTypesResponseActionConfiguration; +std::shared_ptr getClearRecordTypesResponseAction(const ClearRecordTypesResponseActionConfiguration& config); +struct DelayResponseActionConfiguration; +std::shared_ptr getDelayResponseAction(const DelayResponseActionConfiguration& config); +struct DnstapLogResponseActionConfiguration; +std::shared_ptr getDnstapLogResponseAction(const DnstapLogResponseActionConfiguration& config); +struct DropResponseActionConfiguration; +std::shared_ptr getDropResponseAction(const DropResponseActionConfiguration& config); +struct LimitTTLResponseActionConfiguration; +std::shared_ptr getLimitTTLResponseAction(const LimitTTLResponseActionConfiguration& config); +struct LogResponseActionConfiguration; +std::shared_ptr getLogResponseAction(const LogResponseActionConfiguration& config); +struct LuaResponseActionConfiguration; +std::shared_ptr getLuaResponseAction(const LuaResponseActionConfiguration& config); +struct LuaFFIResponseActionConfiguration; +std::shared_ptr getLuaFFIResponseAction(const LuaFFIResponseActionConfiguration& config); +struct LuaFFIPerThreadResponseActionConfiguration; +std::shared_ptr getLuaFFIPerThreadResponseAction(const LuaFFIPerThreadResponseActionConfiguration& config); +struct RemoteLogResponseActionConfiguration; +std::shared_ptr getRemoteLogResponseAction(const RemoteLogResponseActionConfiguration& config); +struct SetExtendedDNSErrorResponseActionConfiguration; +std::shared_ptr getSetExtendedDNSErrorResponseAction(const SetExtendedDNSErrorResponseActionConfiguration& config); +struct SetMaxReturnedTTLResponseActionConfiguration; +std::shared_ptr getSetMaxReturnedTTLResponseAction(const SetMaxReturnedTTLResponseActionConfiguration& config); +struct SetMaxTTLResponseActionConfiguration; +std::shared_ptr getSetMaxTTLResponseAction(const SetMaxTTLResponseActionConfiguration& config); +struct SetMinTTLResponseActionConfiguration; +std::shared_ptr getSetMinTTLResponseAction(const SetMinTTLResponseActionConfiguration& config); +struct SetReducedTTLResponseActionConfiguration; +std::shared_ptr getSetReducedTTLResponseAction(const SetReducedTTLResponseActionConfiguration& config); +struct SetSkipCacheResponseActionConfiguration; +std::shared_ptr getSetSkipCacheResponseAction(const SetSkipCacheResponseActionConfiguration& config); +struct SetTagResponseActionConfiguration; +std::shared_ptr getSetTagResponseAction(const SetTagResponseActionConfiguration& config); +struct SNMPTrapResponseActionConfiguration; +std::shared_ptr getSNMPTrapResponseAction(const SNMPTrapResponseActionConfiguration& config); +struct TCResponseActionConfiguration; +std::shared_ptr getTCResponseAction(const TCResponseActionConfiguration& config); diff --git a/pdns/dnsdistdist/dnsdist-rust-bridge-selectors-generated.cc b/pdns/dnsdistdist/dnsdist-rust-bridge-selectors-generated.cc new file mode 100644 index 000000000000..fad3bc995217 --- /dev/null +++ b/pdns/dnsdistdist/dnsdist-rust-bridge-selectors-generated.cc @@ -0,0 +1,161 @@ +// !! This file has been generated by dnsdist-settings-generator.py, do not edit by hand!! +std::shared_ptr getAllSelector(const AllSelectorConfiguration& config) +{ + auto selector = dnsdist::selectors::getAllSelector(); + return newDNSSelector(std::move(selector), config.name); +} +std::shared_ptr getDNSSECSelector(const DNSSECSelectorConfiguration& config) +{ + auto selector = dnsdist::selectors::getDNSSECSelector(); + return newDNSSelector(std::move(selector), config.name); +} +std::shared_ptr getDSTPortSelector(const DSTPortSelectorConfiguration& config) +{ + auto selector = dnsdist::selectors::getDSTPortSelector(config.port); + return newDNSSelector(std::move(selector), config.name); +} +std::shared_ptr getEDNSOptionSelector(const EDNSOptionSelectorConfiguration& config) +{ + auto selector = dnsdist::selectors::getEDNSOptionSelector(config.option_code); + return newDNSSelector(std::move(selector), config.name); +} +std::shared_ptr getEDNSVersionSelector(const EDNSVersionSelectorConfiguration& config) +{ + auto selector = dnsdist::selectors::getEDNSVersionSelector(config.version); + return newDNSSelector(std::move(selector), config.name); +} +std::shared_ptr getERCodeSelector(const ERCodeSelectorConfiguration& config) +{ + auto selector = dnsdist::selectors::getERCodeSelector(config.rcode); + return newDNSSelector(std::move(selector), config.name); +} +std::shared_ptr getHTTPHeaderSelector(const HTTPHeaderSelectorConfiguration& config) +{ + auto selector = dnsdist::selectors::getHTTPHeaderSelector(std::string(config.header), std::string(config.expression)); + return newDNSSelector(std::move(selector), config.name); +} +std::shared_ptr getHTTPPathSelector(const HTTPPathSelectorConfiguration& config) +{ + auto selector = dnsdist::selectors::getHTTPPathSelector(std::string(config.path)); + return newDNSSelector(std::move(selector), config.name); +} +std::shared_ptr getHTTPPathRegexSelector(const HTTPPathRegexSelectorConfiguration& config) +{ + auto selector = dnsdist::selectors::getHTTPPathRegexSelector(std::string(config.expression)); + return newDNSSelector(std::move(selector), config.name); +} +std::shared_ptr getLuaFFIPerThreadSelector(const LuaFFIPerThreadSelectorConfiguration& config) +{ + auto selector = dnsdist::selectors::getLuaFFIPerThreadSelector(std::string(config.code)); + return newDNSSelector(std::move(selector), config.name); +} +std::shared_ptr getMaxQPSSelector(const MaxQPSSelectorConfiguration& config) +{ + auto selector = dnsdist::selectors::getMaxQPSSelector(config.qps, config.burst); + return newDNSSelector(std::move(selector), config.name); +} +std::shared_ptr getMaxQPSIPSelector(const MaxQPSIPSelectorConfiguration& config) +{ + auto selector = dnsdist::selectors::getMaxQPSIPSelector(config.qps, config.ipv4_mask, config.ipv6_mask, config.burst, config.expiration, config.cleanup_delay, config.scan_fraction, config.shards); + return newDNSSelector(std::move(selector), config.name); +} +std::shared_ptr getOpcodeSelector(const OpcodeSelectorConfiguration& config) +{ + auto selector = dnsdist::selectors::getOpcodeSelector(config.code); + return newDNSSelector(std::move(selector), config.name); +} +std::shared_ptr getPayloadSizeSelector(const PayloadSizeSelectorConfiguration& config) +{ + auto selector = dnsdist::selectors::getPayloadSizeSelector(std::string(config.comparison), config.size); + return newDNSSelector(std::move(selector), config.name); +} +std::shared_ptr getPoolAvailableSelector(const PoolAvailableSelectorConfiguration& config) +{ + auto selector = dnsdist::selectors::getPoolAvailableSelector(std::string(config.pool)); + return newDNSSelector(std::move(selector), config.name); +} +std::shared_ptr getPoolOutstandingSelector(const PoolOutstandingSelectorConfiguration& config) +{ + auto selector = dnsdist::selectors::getPoolOutstandingSelector(std::string(config.pool), config.max_outstanding); + return newDNSSelector(std::move(selector), config.name); +} +std::shared_ptr getProbaSelector(const ProbaSelectorConfiguration& config) +{ + auto selector = dnsdist::selectors::getProbaSelector(config.probability); + return newDNSSelector(std::move(selector), config.name); +} +std::shared_ptr getProxyProtocolValueSelector(const ProxyProtocolValueSelectorConfiguration& config) +{ + auto selector = dnsdist::selectors::getProxyProtocolValueSelector(config.option_type, std::string(config.option_value)); + return newDNSSelector(std::move(selector), config.name); +} +std::shared_ptr getQClassSelector(const QClassSelectorConfiguration& config) +{ + auto selector = dnsdist::selectors::getQClassSelector(std::string(config.qclass), config.numeric_value); + return newDNSSelector(std::move(selector), config.name); +} +std::shared_ptr getQNameLabelsCountSelector(const QNameLabelsCountSelectorConfiguration& config) +{ + auto selector = dnsdist::selectors::getQNameLabelsCountSelector(config.min_labels_count, config.max_labels_count); + return newDNSSelector(std::move(selector), config.name); +} +std::shared_ptr getQNameWireLengthSelector(const QNameWireLengthSelectorConfiguration& config) +{ + auto selector = dnsdist::selectors::getQNameWireLengthSelector(config.min, config.max); + return newDNSSelector(std::move(selector), config.name); +} +std::shared_ptr getQTypeSelector(const QTypeSelectorConfiguration& config) +{ + auto selector = dnsdist::selectors::getQTypeSelector(std::string(config.qtype), config.numeric_value); + return newDNSSelector(std::move(selector), config.name); +} +std::shared_ptr getRCodeSelector(const RCodeSelectorConfiguration& config) +{ + auto selector = dnsdist::selectors::getRCodeSelector(config.rcode); + return newDNSSelector(std::move(selector), config.name); +} +std::shared_ptr getRDSelector(const RDSelectorConfiguration& config) +{ + auto selector = dnsdist::selectors::getRDSelector(); + return newDNSSelector(std::move(selector), config.name); +} +std::shared_ptr getRE2Selector(const RE2SelectorConfiguration& config) +{ + auto selector = dnsdist::selectors::getRE2Selector(std::string(config.expression)); + return newDNSSelector(std::move(selector), config.name); +} +std::shared_ptr getRecordsCountSelector(const RecordsCountSelectorConfiguration& config) +{ + auto selector = dnsdist::selectors::getRecordsCountSelector(config.section, config.minimum, config.maximum); + return newDNSSelector(std::move(selector), config.name); +} +std::shared_ptr getRecordsTypeCountSelector(const RecordsTypeCountSelectorConfiguration& config) +{ + auto selector = dnsdist::selectors::getRecordsTypeCountSelector(config.section, config.record_type, config.minimum, config.maximum); + return newDNSSelector(std::move(selector), config.name); +} +std::shared_ptr getRegexSelector(const RegexSelectorConfiguration& config) +{ + auto selector = dnsdist::selectors::getRegexSelector(std::string(config.expression)); + return newDNSSelector(std::move(selector), config.name); +} +std::shared_ptr getSNISelector(const SNISelectorConfiguration& config) +{ + auto selector = dnsdist::selectors::getSNISelector(std::string(config.server_name)); + return newDNSSelector(std::move(selector), config.name); +} +std::shared_ptr getTagSelector(const TagSelectorConfiguration& config) +{ + auto selector = dnsdist::selectors::getTagSelector(std::string(config.tag), std::string(config.value)); + return newDNSSelector(std::move(selector), config.name); +} +std::shared_ptr getTCPSelector(const TCPSelectorConfiguration& config) +{ + auto selector = dnsdist::selectors::getTCPSelector(config.tcp); + return newDNSSelector(std::move(selector), config.name); +} +std::shared_ptr getTrailingDataSelector(const TrailingDataSelectorConfiguration& config) +{ + auto selector = dnsdist::selectors::getTrailingDataSelector(); + return newDNSSelector(std::move(selector), config.name); +} diff --git a/pdns/dnsdistdist/dnsdist-rust-bridge-selectors-generated.hh b/pdns/dnsdistdist/dnsdist-rust-bridge-selectors-generated.hh new file mode 100644 index 000000000000..796c3480a802 --- /dev/null +++ b/pdns/dnsdistdist/dnsdist-rust-bridge-selectors-generated.hh @@ -0,0 +1,89 @@ +// !! This file has been generated by dnsdist-settings-generator.py, do not edit by hand!! +struct AllSelectorConfiguration; +std::shared_ptr getAllSelector(const AllSelectorConfiguration& config); +struct AndSelectorConfiguration; +std::shared_ptr getAndSelector(const AndSelectorConfiguration& config); +struct ByNameSelectorConfiguration; +std::shared_ptr getByNameSelector(const ByNameSelectorConfiguration& config); +struct DNSSECSelectorConfiguration; +std::shared_ptr getDNSSECSelector(const DNSSECSelectorConfiguration& config); +struct DSTPortSelectorConfiguration; +std::shared_ptr getDSTPortSelector(const DSTPortSelectorConfiguration& config); +struct EDNSOptionSelectorConfiguration; +std::shared_ptr getEDNSOptionSelector(const EDNSOptionSelectorConfiguration& config); +struct EDNSVersionSelectorConfiguration; +std::shared_ptr getEDNSVersionSelector(const EDNSVersionSelectorConfiguration& config); +struct ERCodeSelectorConfiguration; +std::shared_ptr getERCodeSelector(const ERCodeSelectorConfiguration& config); +struct HTTPHeaderSelectorConfiguration; +std::shared_ptr getHTTPHeaderSelector(const HTTPHeaderSelectorConfiguration& config); +struct HTTPPathSelectorConfiguration; +std::shared_ptr getHTTPPathSelector(const HTTPPathSelectorConfiguration& config); +struct HTTPPathRegexSelectorConfiguration; +std::shared_ptr getHTTPPathRegexSelector(const HTTPPathRegexSelectorConfiguration& config); +struct KeyValueStoreLookupSelectorConfiguration; +std::shared_ptr getKeyValueStoreLookupSelector(const KeyValueStoreLookupSelectorConfiguration& config); +struct KeyValueStoreRangeLookupSelectorConfiguration; +std::shared_ptr getKeyValueStoreRangeLookupSelector(const KeyValueStoreRangeLookupSelectorConfiguration& config); +struct LuaSelectorConfiguration; +std::shared_ptr getLuaSelector(const LuaSelectorConfiguration& config); +struct LuaFFISelectorConfiguration; +std::shared_ptr getLuaFFISelector(const LuaFFISelectorConfiguration& config); +struct LuaFFIPerThreadSelectorConfiguration; +std::shared_ptr getLuaFFIPerThreadSelector(const LuaFFIPerThreadSelectorConfiguration& config); +struct MaxQPSSelectorConfiguration; +std::shared_ptr getMaxQPSSelector(const MaxQPSSelectorConfiguration& config); +struct MaxQPSIPSelectorConfiguration; +std::shared_ptr getMaxQPSIPSelector(const MaxQPSIPSelectorConfiguration& config); +struct NetmaskGroupSelectorConfiguration; +std::shared_ptr getNetmaskGroupSelector(const NetmaskGroupSelectorConfiguration& config); +struct NotSelectorConfiguration; +std::shared_ptr getNotSelector(const NotSelectorConfiguration& config); +struct OpcodeSelectorConfiguration; +std::shared_ptr getOpcodeSelector(const OpcodeSelectorConfiguration& config); +struct OrSelectorConfiguration; +std::shared_ptr getOrSelector(const OrSelectorConfiguration& config); +struct PayloadSizeSelectorConfiguration; +std::shared_ptr getPayloadSizeSelector(const PayloadSizeSelectorConfiguration& config); +struct PoolAvailableSelectorConfiguration; +std::shared_ptr getPoolAvailableSelector(const PoolAvailableSelectorConfiguration& config); +struct PoolOutstandingSelectorConfiguration; +std::shared_ptr getPoolOutstandingSelector(const PoolOutstandingSelectorConfiguration& config); +struct ProbaSelectorConfiguration; +std::shared_ptr getProbaSelector(const ProbaSelectorConfiguration& config); +struct ProxyProtocolValueSelectorConfiguration; +std::shared_ptr getProxyProtocolValueSelector(const ProxyProtocolValueSelectorConfiguration& config); +struct QClassSelectorConfiguration; +std::shared_ptr getQClassSelector(const QClassSelectorConfiguration& config); +struct QNameSelectorConfiguration; +std::shared_ptr getQNameSelector(const QNameSelectorConfiguration& config); +struct QNameLabelsCountSelectorConfiguration; +std::shared_ptr getQNameLabelsCountSelector(const QNameLabelsCountSelectorConfiguration& config); +struct QNameSetSelectorConfiguration; +std::shared_ptr getQNameSetSelector(const QNameSetSelectorConfiguration& config); +struct QNameSuffixSelectorConfiguration; +std::shared_ptr getQNameSuffixSelector(const QNameSuffixSelectorConfiguration& config); +struct QNameWireLengthSelectorConfiguration; +std::shared_ptr getQNameWireLengthSelector(const QNameWireLengthSelectorConfiguration& config); +struct QTypeSelectorConfiguration; +std::shared_ptr getQTypeSelector(const QTypeSelectorConfiguration& config); +struct RCodeSelectorConfiguration; +std::shared_ptr getRCodeSelector(const RCodeSelectorConfiguration& config); +struct RDSelectorConfiguration; +std::shared_ptr getRDSelector(const RDSelectorConfiguration& config); +struct RE2SelectorConfiguration; +std::shared_ptr getRE2Selector(const RE2SelectorConfiguration& config); +struct RecordsCountSelectorConfiguration; +std::shared_ptr getRecordsCountSelector(const RecordsCountSelectorConfiguration& config); +struct RecordsTypeCountSelectorConfiguration; +std::shared_ptr getRecordsTypeCountSelector(const RecordsTypeCountSelectorConfiguration& config); +struct RegexSelectorConfiguration; +std::shared_ptr getRegexSelector(const RegexSelectorConfiguration& config); +struct SNISelectorConfiguration; +std::shared_ptr getSNISelector(const SNISelectorConfiguration& config); +struct TagSelectorConfiguration; +std::shared_ptr getTagSelector(const TagSelectorConfiguration& config); +struct TCPSelectorConfiguration; +std::shared_ptr getTCPSelector(const TCPSelectorConfiguration& config); +struct TrailingDataSelectorConfiguration; +std::shared_ptr getTrailingDataSelector(const TrailingDataSelectorConfiguration& config); diff --git a/pdns/dnsdistdist/dnsdist-rust-bridge.hh b/pdns/dnsdistdist/dnsdist-rust-bridge.hh new file mode 100644 index 000000000000..8ac417839a32 --- /dev/null +++ b/pdns/dnsdistdist/dnsdist-rust-bridge.hh @@ -0,0 +1,43 @@ +#pragma once + +#include +#include + +class DNSAction; +class DNSResponseAction; +class DNSRule; + +#include "rust/cxx.h" + +namespace dnsdist::rust::settings +{ + +struct DNSSelector +{ + std::shared_ptr d_rule; + std::string d_name; +}; + +struct DNSActionWrapper +{ + std::shared_ptr d_action; + std::string d_name; +}; + +struct DNSResponseActionWrapper +{ + std::shared_ptr d_action; + std::string d_name; +}; + +struct ProtobufLoggerConfiguration; +struct DnstapLoggerConfiguration; +struct KeyValueStoresConfiguration; + +void registerProtobufLogger(const ProtobufLoggerConfiguration& config); +void registerDnstapLogger(const DnstapLoggerConfiguration& config); +void registerKVSObjects(const KeyValueStoresConfiguration& config); + +#include "dnsdist-rust-bridge-actions-generated.hh" +#include "dnsdist-rust-bridge-selectors-generated.hh" +} diff --git a/pdns/dnsdistdist/dnsdist-rust-lib/.gitignore b/pdns/dnsdistdist/dnsdist-rust-lib/.gitignore new file mode 100644 index 000000000000..b336cc7cec94 --- /dev/null +++ b/pdns/dnsdistdist/dnsdist-rust-lib/.gitignore @@ -0,0 +1,2 @@ +/Makefile +/Makefile.in diff --git a/pdns/dnsdistdist/dnsdist-rust-lib/Makefile.am b/pdns/dnsdistdist/dnsdist-rust-lib/Makefile.am new file mode 100644 index 000000000000..e023c81c8f87 --- /dev/null +++ b/pdns/dnsdistdist/dnsdist-rust-lib/Makefile.am @@ -0,0 +1,21 @@ +EXTRA_DIST = \ + dnsdist-configuration-yaml-items-generated-pre-in.cc \ + dnsdist-configuration-yaml-items-generated.cc \ + dnsdist-settings-documentation-generator.py \ + dnsdist-settings-generator.py \ + rust-pre-in.rs \ + rust-middle-in.rs \ + rust-post-in.rs \ + rust/src/lib.rs + +BUILT_SOURCES=rust/src/lib.rs \ + dnsdist-configuration-yaml-items-generated.cc + +all: rust/src/lib.rs dnsdist-configuration-yaml-items-generated.cc + +rust/src/lib.rs dnsdist-configuration-yaml-items-generated.cc: dnsdist-settings-generator.py ../dnsdist-settings-definitions.yml rust-pre-in.rs rust-middle-in.rs rust-post-in.rs dnsdist-configuration-yaml-items-generated-pre-in.cc ../dnsdist-actions-definitions.yml ../dnsdist-response-actions-definitions.yml ../dnsdist-selectors-definitions.yml + @if test "$(PYTHON)" = ":"; then echo "Settings definitions have changed, python is needed to regenerate the related settings files but python was not found. Please install python and re-run configure"; exit 1; fi + @if ! $(PYTHON) --version | grep -q "Python 3"; then echo $(PYTHON) should be at least version 3. Please install python 3 and re-run configure; exit 1; fi + $(MAKE) -C rust clean + (cd ${srcdir} && $(PYTHON) dnsdist-settings-generator.py ../dnsdist-settings-definitions.yml) + $(PYTHON) dnsdist-settings-documentation-generator.py diff --git a/pdns/dnsdistdist/dnsdist-rust-lib/dnsdist-configuration-yaml-items-generated-pre-in.cc b/pdns/dnsdistdist/dnsdist-rust-lib/dnsdist-configuration-yaml-items-generated-pre-in.cc new file mode 100644 index 000000000000..bae85b634c0f --- /dev/null +++ b/pdns/dnsdistdist/dnsdist-rust-lib/dnsdist-configuration-yaml-items-generated-pre-in.cc @@ -0,0 +1,24 @@ +/* + * This file is part of PowerDNS or dnsdist. + * Copyright -- PowerDNS.COM B.V. and its contributors + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * In addition, for the avoidance of any doubt, permission is granted to + * link this program with OpenSSL and to (re)distribute the binaries + * produced as the result of such linking. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "dnsdist-configuration.hh" +#include "dnsdist-configuration-yaml.hh" diff --git a/pdns/dnsdistdist/dnsdist-rust-lib/dnsdist-configuration-yaml-items-generated.cc b/pdns/dnsdistdist/dnsdist-rust-lib/dnsdist-configuration-yaml-items-generated.cc new file mode 100644 index 000000000000..fa81278bbd10 --- /dev/null +++ b/pdns/dnsdistdist/dnsdist-rust-lib/dnsdist-configuration-yaml-items-generated.cc @@ -0,0 +1,223 @@ +// !! This file has been generated by dnsdist-settings-generator.py, do not edit by hand!! +// START INCLUDE ./dnsdist-configuration-yaml-items-generated-pre-in.cc +/* + * 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-configuration.hh" +#include "dnsdist-configuration-yaml.hh" +// END INCLUDE ./dnsdist-configuration-yaml-items-generated-pre-in.cc +#if defined(HAVE_YAML_CONFIGURATION) +#include "rust/cxx.h" +#include "rust/lib.rs.h" +#include "dnsdist-configuration-yaml-internal.hh" + +namespace dnsdist::configuration::yaml +{ +void convertRuntimeFlatSettingsFromRust(const dnsdist::rust::settings::GlobalConfiguration& yamlConfig, dnsdist::configuration::RuntimeConfiguration& config) +{ + if (config.d_consoleOutputMsgMaxSize == 10000000) { + config.d_consoleOutputMsgMaxSize = yamlConfig.console.maximum_output_size; + } + if (config.d_logConsoleConnections == true) { + config.d_logConsoleConnections = yamlConfig.console.log_connections; + } + if (config.d_ecsOverride == false) { + config.d_ecsOverride = yamlConfig.edns_client_subnet.override_existing; + } + if (config.d_ECSSourcePrefixV4 == 32) { + config.d_ECSSourcePrefixV4 = yamlConfig.edns_client_subnet.source_prefix_v4; + } + if (config.d_ECSSourcePrefixV6 == 56) { + config.d_ECSSourcePrefixV6 = yamlConfig.edns_client_subnet.source_prefix_v6; + } + if (config.d_dynBlocksPurgeInterval == 60) { + config.d_dynBlocksPurgeInterval = yamlConfig.dynamic_rules_settings.purge_interval; + } + if (config.d_tcpRecvTimeout == 2) { + config.d_tcpRecvTimeout = yamlConfig.tuning.tcp.receive_timeout; + } + if (config.d_tcpSendTimeout == 2) { + config.d_tcpSendTimeout = yamlConfig.tuning.tcp.send_timeout; + } + if (config.d_maxTCPQueriesPerConn == 0) { + config.d_maxTCPQueriesPerConn = yamlConfig.tuning.tcp.max_queries_per_connection; + } + if (config.d_maxTCPConnectionDuration == 0) { + config.d_maxTCPConnectionDuration = yamlConfig.tuning.tcp.max_connection_duration; + } + if (config.d_tlsSessionCacheCleanupDelay == 60) { + config.d_tlsSessionCacheCleanupDelay = yamlConfig.tuning.tls.outgoing_tickets_cache_cleanup_delay; + } + if (config.d_tlsSessionCacheSessionValidity == 600) { + config.d_tlsSessionCacheSessionValidity = yamlConfig.tuning.tls.outgoing_tickets_cache_validity; + } + if (config.d_tlsSessionCacheMaxSessionsPerBackend == 20) { + config.d_tlsSessionCacheMaxSessionsPerBackend = yamlConfig.tuning.tls.max_outgoing_tickets_per_backend; + } + if (config.d_staleCacheEntriesTTL == 0) { + config.d_staleCacheEntriesTTL = yamlConfig.cache_settings.stale_entries_ttl; + } + if (config.d_cacheCleaningDelay == 60) { + config.d_cacheCleaningDelay = yamlConfig.cache_settings.cleaning_delay; + } + if (config.d_cacheCleaningPercentage == 100) { + config.d_cacheCleaningPercentage = yamlConfig.cache_settings.cleaning_percentage; + } + if (config.d_secPollInterval == 3600) { + config.d_secPollInterval = yamlConfig.security_polling.polling_interval; + } + if (config.d_secPollSuffix == "secpoll.powerdns.com.") { + config.d_secPollSuffix = std::string(yamlConfig.security_polling.suffix); + } + if (config.d_verbose == false) { + config.d_verbose = yamlConfig.logging.verbose; + } + if (config.d_verboseHealthChecks == false) { + config.d_verboseHealthChecks = yamlConfig.logging.verbose_health_checks; + } + if (config.d_payloadSizeSelfGenAnswers == 1232) { + config.d_payloadSizeSelfGenAnswers = yamlConfig.general.edns_udp_payload_size_self_generated_answers; + } + if (config.d_addEDNSToSelfGeneratedResponses == true) { + config.d_addEDNSToSelfGeneratedResponses = yamlConfig.general.add_edns_to_self_generated_answers; + } + if (config.d_truncateTC == false) { + config.d_truncateTC = yamlConfig.general.truncate_tc_answers; + } + if (config.d_fixupCase == false) { + config.d_fixupCase = yamlConfig.general.fixup_case; + } + if (config.d_allowEmptyResponse == false) { + config.d_allowEmptyResponse = yamlConfig.general.allow_empty_responses; + } + if (config.d_dropEmptyQueries == false) { + config.d_dropEmptyQueries = yamlConfig.general.drop_empty_queries; + } + if (config.d_proxyProtocolMaximumSize == 512) { + config.d_proxyProtocolMaximumSize = yamlConfig.proxy_protocol.maximum_payload_size; + } + if (config.d_applyACLToProxiedClients == false) { + config.d_applyACLToProxiedClients = yamlConfig.proxy_protocol.apply_acl_to_proxied_clients; + } + if (config.d_servFailOnNoPolicy == false) { + config.d_servFailOnNoPolicy = yamlConfig.load_balancing_policies.servfail_on_no_server; + } + if (config.d_roundrobinFailOnNoServer == false) { + config.d_roundrobinFailOnNoServer = yamlConfig.load_balancing_policies.round_robin_servfail_on_no_server; + } +} +void convertImmutableFlatSettingsFromRust(const dnsdist::rust::settings::GlobalConfiguration& yamlConfig, dnsdist::configuration::ImmutableConfiguration& config) +{ + if (config.d_consoleMaxConcurrentConnections == 0) { + config.d_consoleMaxConcurrentConnections = yamlConfig.console.max_concurrent_connections; + } + if (config.d_ringsCapacity == 10000) { + config.d_ringsCapacity = yamlConfig.ring_buffers.size; + } + if (config.d_ringsNumberOfShards == 10) { + config.d_ringsNumberOfShards = yamlConfig.ring_buffers.shards; + } + if (config.d_ringsNbLockTries == 5) { + config.d_ringsNbLockTries = yamlConfig.ring_buffers.lock_retries; + } + if (config.d_ringsRecordQueries == true) { + config.d_ringsRecordQueries = yamlConfig.ring_buffers.record_queries; + } + if (config.d_ringsRecordResponses == true) { + config.d_ringsRecordResponses = yamlConfig.ring_buffers.record_responses; + } + if (config.d_maxTCPClientThreads == 10) { + config.d_maxTCPClientThreads = yamlConfig.tuning.tcp.worker_threads; + } + if (config.d_maxTCPQueuedConnections == 10000) { + config.d_maxTCPQueuedConnections = yamlConfig.tuning.tcp.max_queued_connections; + } + if (config.d_tcpInternalPipeBufferSize == 1048576) { + config.d_tcpInternalPipeBufferSize = yamlConfig.tuning.tcp.internal_pipe_buffer_size; + } + if (config.d_outgoingTCPMaxIdleTime == 300) { + config.d_outgoingTCPMaxIdleTime = yamlConfig.tuning.tcp.outgoing_max_idle_time; + } + if (config.d_outgoingTCPCleanupInterval == 60) { + config.d_outgoingTCPCleanupInterval = yamlConfig.tuning.tcp.outgoing_cleanup_interval; + } + if (config.d_outgoingTCPMaxIdlePerBackend == 10) { + config.d_outgoingTCPMaxIdlePerBackend = yamlConfig.tuning.tcp.outgoing_max_idle_connection_per_backend; + } + if (config.d_maxTCPConnectionsPerClient == 0) { + config.d_maxTCPConnectionsPerClient = yamlConfig.tuning.tcp.max_connections_per_client; + } + if (config.d_udpVectorSize == 1) { + config.d_udpVectorSize = yamlConfig.tuning.udp.messages_per_round; + } + if (config.d_socketUDPSendBuffer == 0) { + config.d_socketUDPSendBuffer = yamlConfig.tuning.udp.send_buffer_size; + } + if (config.d_socketUDPRecvBuffer == 0) { + config.d_socketUDPRecvBuffer = yamlConfig.tuning.udp.receive_buffer_size; + } + if (config.d_maxUDPOutstanding == 65535) { + config.d_maxUDPOutstanding = yamlConfig.tuning.udp.max_outstanding_per_backend; + } + if (config.d_udpTimeout == 2) { + config.d_udpTimeout = yamlConfig.tuning.udp.timeout; + } + if (config.d_randomizeUDPSocketsToBackend == false) { + config.d_randomizeUDPSocketsToBackend = yamlConfig.tuning.udp.randomize_outgoing_sockets_to_backend; + } + if (config.d_randomizeIDsToBackend == false) { + config.d_randomizeIDsToBackend = yamlConfig.tuning.udp.randomize_ids_to_backend; + } + if (config.d_outgoingDoHWorkers == 10) { + config.d_outgoingDoHWorkers = yamlConfig.tuning.doh.outgoing_worker_threads; + } + if (config.d_outgoingDoHMaxIdleTime == 300) { + config.d_outgoingDoHMaxIdleTime = yamlConfig.tuning.doh.outgoing_max_idle_time; + } + if (config.d_outgoingDoHCleanupInterval == 60) { + config.d_outgoingDoHCleanupInterval = yamlConfig.tuning.doh.outgoing_cleanup_interval; + } + if (config.d_outgoingDoHMaxIdlePerBackend == 10) { + config.d_outgoingDoHMaxIdlePerBackend = yamlConfig.tuning.doh.outgoing_max_idle_connection_per_backend; + } + if (config.d_snmpEnabled == false) { + config.d_snmpEnabled = yamlConfig.snmp.enabled; + } + if (config.d_snmpTrapsEnabled == false) { + config.d_snmpTrapsEnabled = yamlConfig.snmp.traps_enabled; + } + if (config.d_snmpDaemonSocketPath == "") { + config.d_snmpDaemonSocketPath = std::string(yamlConfig.snmp.daemon_socket); + } + if (config.d_weightedBalancingFactor == 0.0) { + config.d_weightedBalancingFactor = yamlConfig.load_balancing_policies.weighted_balancing_factor; + } + if (config.d_consistentHashBalancingFactor == 0.0) { + config.d_consistentHashBalancingFactor = yamlConfig.load_balancing_policies.consistent_hashing_balancing_factor; + } + if (config.d_hashPerturbation == 0) { + config.d_hashPerturbation = yamlConfig.load_balancing_policies.hash_perturbation; + } +} + +} +#endif /* defined(HAVE_YAML_CONFIGURATION) */ diff --git a/pdns/dnsdistdist/dnsdist-rust-lib/dnsdist-settings-documentation-generator.py b/pdns/dnsdistdist/dnsdist-rust-lib/dnsdist-settings-documentation-generator.py new file mode 100644 index 000000000000..4eef23a3b224 --- /dev/null +++ b/pdns/dnsdistdist/dnsdist-rust-lib/dnsdist-settings-documentation-generator.py @@ -0,0 +1,210 @@ +#!/usr/bin/python3 +"""Load settings definitions and generates the corresponding documentation.""" +import os +import sys +import tempfile +import yaml + +def quote(arg): + """Return a quoted string""" + return '"' + arg + '"' + +def get_vector_sub_type(rust_type): + return rust_type[4:-1] + +def is_vector_of(rust_type): + return rust_type.startswith('Vec<') + +def is_type_native(rust_type): + if is_vector_of(rust_type): + sub_type = get_vector_sub_type(rust_type) + return is_type_native(sub_type) + return rust_type in ['bool', 'u8', 'u16', 'u32', 'u64', 'f64', 'String'] + +def get_definitions_from_file(def_file): + with open(def_file, 'rt', encoding="utf-8") as fd: + definitions = yaml.safe_load(fd.read()) + return definitions + +def get_rust_object_name(name): + object_name = '' + capitalize = True + for char in name: + if char in ['-', '_']: + capitalize = True + continue + if capitalize: + char = char.upper() + capitalize = False + object_name += char + + return object_name + +def get_objects(def_file): + objects = {} + definitions = get_definitions_from_file(def_file) + for definition_name, keys in definitions.items(): + object_name = get_rust_object_name(definition_name) + 'Configuration' + objects[object_name] = keys + + return objects + +def rust_type_to_human_str(rust_type, entry_type, generate_ref=True): + if is_vector_of(rust_type): + return 'Sequence of ' + rust_type_to_human_str(get_vector_sub_type(rust_type), entry_type, generate_ref) + if rust_type in ['u8', 'u16', 'u32', 'u64']: + return 'Unsigned integer' + if rust_type == 'f64': + return 'Double' + if rust_type == 'bool': + return 'Boolean' + if rust_type == 'String': + return 'String' + if generate_ref: + return f':ref:`{rust_type} `' + return f'{rust_type}' + +def print_structure(parameters, entry_type): + output = '' + # list + for parameter in parameters: + output += f'- **{parameter["name"]}**: ' + ptype = parameter['type'] + if 'rust-type' in parameter: + ptype = parameter['rust-type'] + human_type = rust_type_to_human_str(ptype, entry_type) + output += f'{human_type}' + + if 'default' in parameter: + default = parameter['default'] + if default is not True: + if default == '': + output += ' ``("")``' + else: + output += f' ``({default})``' + + if 'description' in parameter: + description = parameter['description'] + output += ' - ' + description + + if 'supported-values' in parameter: + values = ', '.join(parameter['supported-values']) + output += '. Supported values are: ' + values + + output += '\n' + + output += '\n' + + return output + +def process_object(object_name, entries, entry_type, is_setting_struct=False, lua_equivalent=None): + output = f'.. _yaml-{entry_type}-{object_name}:\n\n' + + output += f'{object_name}\n' + output += '-' * len(object_name) + '\n' + output += '\n' + + if 'description' in entries: + description = entries['description'] + output += description + '\n' + output += '\n' + + if lua_equivalent is not None: + output += f'Lua equivalent: :func:`{lua_equivalent}`\n\n' + + if 'parameters' in entries: + if not is_setting_struct: + output += "Parameters:\n\n" + parameters = entries['parameters'] + output += print_structure(parameters, entry_type) + output += '\n' + + return output + +def get_temporary_file_for_generated_content(directory): + generated_fp = tempfile.NamedTemporaryFile(mode='w+t', encoding='utf-8', dir=directory, delete=False) + generated_fp.write('.. THIS IS A GENERATED FILE. DO NOT EDIT. See dnsdist-settings-documentation-generator.py\n\n') + return generated_fp + +def process_settings(): + output = '''.. raw:: latex + + \\setcounter{secnumdepth}{-1} + +YAML configuration reference +============================ + +Since 2.0.0, :program:`dnsdist` supports the YAML configuration format in addition to the existing Lua one. + +If the configuration file passed to :program:`dnsdist` via the ``-C`` command-line switch ends in ``.yml``, it is assumed to be in the new YAML format, and an attempt to load a Lua configuration file with the same name but the ``.lua`` will be done before loading the YAML configuration. If the names ends in ``.lua``, there will also be an attempt to find a file with the same name but ending in ``.yml``. Otherwise the existing Lua configuration format is assumed. + +A YAML configuration file contains several sections, that are described below. + +.. code-block:: yaml\n +''' + + objects = get_objects('../dnsdist-settings-definitions.yml') + for object_name, entries in sorted(objects.items()): + if object_name == 'GlobalConfiguration': + output += process_object(object_name, entries, 'settings', True) + break + + output += '\n' + + for object_name, entries in sorted(objects.items()): + if object_name != 'GlobalConfiguration': + output += process_object(object_name, entries, 'settings', True, entries['lua-name'] if 'lua-name' in entries else None) + + return output + +def process_selectors_or_actions(def_file, entry_type): + title = f'YAML {entry_type} reference' + object_name = get_rust_object_name(entry_type) + output = f'''.. raw:: latex + + \\setcounter{{secnumdepth}}{{-1}} + +.. _yaml-settings-{object_name}: + +{title} +''' + output += len(title)*'=' + '\n\n' + entries = get_definitions_from_file(def_file) + + suffix = object_name + for entry in entries: + object_name = get_rust_object_name(entry['name']) + lua_equivalent = object_name + ('Rule' if entry_type == 'selector' else suffix) + if 'no-lua-equivalent' in entry: + lua_equivalent = None + output += process_object(object_name + suffix, entry, 'settings', lua_equivalent=lua_equivalent) + + return output + +def main(): + if not os.path.isdir('../docs'): + print('Skipping settings documentation generation because the ../docs/ folder does not exist') + return + + generated_fp = get_temporary_file_for_generated_content('../docs/') + output = process_settings() + generated_fp.write(output) + os.rename(generated_fp.name, '../docs/reference/yaml-settings.rst') + + generated_fp = get_temporary_file_for_generated_content('../docs/') + output = process_selectors_or_actions('../dnsdist-actions-definitions.yml', 'action') + generated_fp.write(output) + os.rename(generated_fp.name, '../docs/reference/yaml-actions.rst') + + generated_fp = get_temporary_file_for_generated_content('../docs/') + output = process_selectors_or_actions('../dnsdist-response-actions-definitions.yml', 'response-action') + generated_fp.write(output) + os.rename(generated_fp.name, '../docs/reference/yaml-response-actions.rst') + + generated_fp = get_temporary_file_for_generated_content('../docs/') + output = process_selectors_or_actions('../dnsdist-selectors-definitions.yml', 'selector') + generated_fp.write(output) + os.rename(generated_fp.name, '../docs/reference/yaml-selectors.rst') + +if __name__ == '__main__': + main() diff --git a/pdns/dnsdistdist/dnsdist-rust-lib/dnsdist-settings-generator.py b/pdns/dnsdistdist/dnsdist-rust-lib/dnsdist-settings-generator.py new file mode 100644 index 000000000000..755f372f3544 --- /dev/null +++ b/pdns/dnsdistdist/dnsdist-rust-lib/dnsdist-settings-generator.py @@ -0,0 +1,810 @@ +#!/usr/bin/python3 +"""Load settings definitions and generates C++ and Rust code to handle them.""" +# 1/ Loads the settings definitions from +# - dnsdist-settings-definitions.yml +# and generates Rust structures and functions that are used to parse the +# YAML settings and populate the Rust structures (via Serde): +# rust/src/lib.rs +# Note that some existing structures and functions present in +# - rust-pre-in.rs +# - rust-middle-in.rs +# - rust-post-in.rs +# are also included into the final rust/src/lib.rs file +# Note that during the compilation of the Rust code to create the static +# dnsdist_rust library, the cxx module also creates corresponding C++ structures +# for interoperability +# 2/ Creates methods to fill DNSdist's internal configuration structures +# from the YAML parameters for all trivial values: +# - dnsdist-configuration-yaml-items-generated.cc +# 3/ Loads the action definitions from: +# - dnsdist-actions-definitions.yml +# - dnsdist-response-actions-definitions.yml +# and generates C++ headers and code to create the wrappers +# for these actions from the Rust structures: +# - dnsdist-rust-bridge-actions-generated.hh +# - dnsdist-rust-bridge-actions-generated.cc +# 2/ Loads the selector definitions from: +# - dnsdist-selectors-definitions.yml +# - dnsdist-rust-bridge-selectors-generated.hh +# - dnsdist-rust-bridge-selectors-generated.cc +# and generates C++ headers and code to create the wrappers +# for these selectors from the Rust structures: +# The format of the definitions, in YAML, is a simple list of items. +# Each item has a name and an optional list of parameters. +# Parameters have a name, a type, and optionally a default value +# Types are the Rust ones, converted to the C++ equivalent when needed +# Default values are written as quoted strings, with the exception of the +# special unquoted true value which means to use the default value for the +# object type, which needs to exist. +# Items can optionally have the following properties: +# - 'skip-cpp' is not used by this script but is used by the dnsdist-rules-generator.py one, where it means that the corresponding C++ factory and Lua bindinds will not be generated, which is useful for objects taking parameters that cannot be directly mapped +# - 'skip-rust' means that the C++ code to create the Rust-side version of an action or selector will not generated +# - 'skip-serde' means that the Rust structure representing that action or selector in the YAML setting will not be directly created by Serde. It is used for selectors that reference another selector themselves, or actions referencing another action. +# - 'lua-name' name of the Lua directive for this setting +# - 'internal-field-name' name of the corresponding field in DNSdist's internal configuration structures, which is used to generate 'dnsdist-configuration-yaml-items-generated.cc' +# - 'runtime-configurable' whether this setting can be set at runtime or can only be set at configuration time + +import os +import re +import sys +import tempfile +import yaml + +def quote(arg): + """Return a quoted string""" + return '"' + arg + '"' + +def get_vector_sub_type(rust_type): + return rust_type[4:-1] + +def is_vector_of(rust_type): + return rust_type.startswith('Vec<') + +def is_type_native(rust_type): + if is_vector_of(rust_type): + sub_type = get_vector_sub_type(rust_type) + return is_type_native(sub_type) + return rust_type in ['bool', 'u8', 'u16', 'u32', 'u64', 'f64', 'String'] + +def is_value_rust_default(rust_type, value): + """Is a value the same as its corresponding Rust default?""" + if rust_type == 'bool': + return value == 'false' + if rust_type in ('u8', 'u16', 'u32', 'u64'): + return value in (0, '0', '') + if rust_type == 'f64': + return value in ('0.0', 0.0) + if rust_type == 'String': + return value == '' + if rust_type == 'Vec': + return value == '' + return False + +def get_rust_field_name(name): + return name.replace('-', '_').lower() + +def get_rust_object_name(name): + object_name = '' + capitalize = True + for char in name: + if char in ['-', '_']: + capitalize = True + continue + if capitalize: + char = char.upper() + capitalize = False + object_name += char + + return object_name + +def gen_rust_vec_default_functions(name, type_name, def_value): + """Generate Rust code for the default handling of a vector for type_name""" + ret = f'// DEFAULT HANDLING for {name}\n' + ret += f'fn default_value_{name}() -> Vec {{\n' + ret += f' let msg = "default value defined for `{name}\' should be valid YAML";' + ret += f' let deserialized: Vec = serde_yaml::from_str({quote(def_value)}).expect(&msg);\n' + ret += ' deserialized\n' + ret += '}\n' + ret += f'fn default_value_equal_{name}(value: &Vec)' + ret += '-> bool {\n' + ret += f' let def = default_value_{name}();\n' + ret += ' &def == value\n' + ret += '}\n\n' + return ret + +# Example snippet generated +# fn default_value_general_query_local_address() -> Vec { +# vec![String::from("0.0.0.0"), ] +#} +#fn default_value_equal_general_query_local_address(value: &Vec) -> bool { +# let def = default_value_general_query_local_address(); +# &def == value +#} +def gen_rust_stringvec_default_functions(default, name): + """Generate Rust code for the default handling of a vector for Strings""" + ret = f'// DEFAULT HANDLING for {name}\n' + ret += f'fn default_value_{name}() -> Vec {{\n' + parts = re.split('[ \t,]+', default) + if len(parts) > 0: + ret += ' vec![\n' + for part in parts: + if part == '': + continue + ret += f' String::from({quote(part)}),\n' + ret += ' ]\n' + else: + ret += ' vec![]\n' + ret += '}\n' + ret += f'fn default_value_equal_{name}(value: &Vec) -> bool {{\n' + ret += f' let def = default_value_{name}();\n' + ret += ' &def == value\n' + ret += '}\n\n' + return ret + +def gen_rust_default_functions(rust_type, default, name): + """Generate Rust code for the default handling""" + if rust_type in ['Vec']: + return gen_rust_stringvec_default_functions(default, name) + ret = f'// DEFAULT HANDLING for {name}\n' + ret += f'fn default_value_{name}() -> {rust_type} {{\n' + rustdef = quote(default) + ret += f" String::from({rustdef})\n" + ret += '}\n' + if rust_type == 'String': + rust_type = 'str' + ret += f'fn default_value_equal_{name}(value: &{rust_type})' + ret += '-> bool {\n' + ret += f' value == default_value_{name}()\n' + ret += '}\n\n' + return ret + +def write_rust_default_trait_impl(struct, skip_namespace=False): + """Generate Rust code for the default Trait for a structure""" + namespace = 'dnsdistsettings::' if not skip_namespace else '' + result = '' + result += f'impl Default for {namespace}{struct} {{\n' + result += ' fn default() -> Self {\n' + result += f' let deserialized: {namespace}{struct} = serde_yaml::from_str("").unwrap();\n' + result += ' deserialized\n' + result += ' }\n' + result += '}\n\n' + return result + +def get_rust_serde_annotations(rust_type, default, rename, obj, field, default_functions): + rename_value = f'rename = "{rename}", ' if rename else '' + if default is None: + if not rename_value: + return '' + return f'#[serde({rename_value})]' + if default is True or is_value_rust_default(rust_type, default): + return f'#[serde({rename_value}default, skip_serializing_if = "crate::is_default")]' + type_upper = rust_type.capitalize() + if rust_type == 'bool': + return f'''#[serde({rename_value}default = "crate::{type_upper}::<{default}>::value", skip_serializing_if = "crate::if_true")]''' + if rust_type in ['String', 'Vec']: + basename = obj + '_' + field + default_functions.append(gen_rust_default_functions(rust_type, default, basename)) + return f'''#[serde({rename_value}default = "crate::default_value_{basename}", skip_serializing_if = "crate::default_value_equal_{basename}")]''' + return f'''#[serde({rename_value}default = "crate::{type_upper}::<{default}>::value", skip_serializing_if = "crate::{type_upper}::<{default}>::is_equal")]''' + +def get_converted_serde_type(rust_type): + if is_type_native(rust_type): + return rust_type + + if is_vector_of(rust_type): + sub_type = get_vector_sub_type(rust_type) + if sub_type in ['Action', 'Selector']: + return rust_type + if sub_type in ['QueryRuleConfiguration', 'ResponseRuleConfiguration']: + return f'Vec<{sub_type}Serde>' + return f'Vec' + + return f'dnsdistsettings::{rust_type}' + +def get_rust_struct_fields_from_definition(name, keys, default_functions, indent_spaces, special_serde_object=False): + if not 'parameters' in keys: + return '' + output = '' + indent = ' '*indent_spaces + for parameter in keys['parameters']: + parameter_name = get_rust_field_name(parameter['name']) if not 'rename' in parameter else parameter['rename'] + rust_type = parameter['type'] + if 'rust-type' in parameter: + rust_type = parameter['rust-type'] + if special_serde_object: + rust_type = get_converted_serde_type(rust_type) + else: + # cxx does not support Enums, so we have to convert them to opaque types + if rust_type == 'Action': + rust_type = 'SharedDNSAction' + elif rust_type == 'ResponseAction': + rust_type = 'SharedDNSResponseAction' + elif rust_type == 'Selector': + rust_type = 'SharedDNSSelector' + elif rust_type == 'Vec': + rust_type = 'Vec' + rename = parameter['name'] if parameter_name != parameter['name'] else None + if special_serde_object or not 'skip-serde' in keys or not keys['skip-serde']: + default_str = get_rust_serde_annotations(rust_type, parameter['default'] if 'default' in parameter else None, rename, get_rust_field_name(name), parameter_name, default_functions) + if default_str: + output += indent + default_str + '\n' + output += f'{indent}{parameter_name}: {rust_type},\n' + + return output + +def get_rust_struct_from_definition(name, keys, default_functions, indent_spaces=4, special_serde_object=False): + if not 'parameters' in keys: + return '' + obj_name = get_rust_object_name(name) + indent = ' '*indent_spaces + output = '' + if special_serde_object or not 'skip-serde' in keys or not keys['skip-serde']: + output += f'''{indent}#[derive(Deserialize, Serialize, Debug, PartialEq)] +{indent}#[serde(deny_unknown_fields)] +''' + elif name == 'global': + output += f'{indent}#[derive(Default)]\n' + + name_suffix = 'Serde' if name == 'global' and special_serde_object else '' + output += f'''{indent}struct {obj_name}Configuration{name_suffix} {{ +''' + indent_spaces += 4 + indent = ' '*indent_spaces + output += get_rust_struct_fields_from_definition(name, keys, default_functions, indent_spaces, special_serde_object=special_serde_object) + output += ' }\n' + if special_serde_object or not 'skip-serde' in keys or not keys['skip-serde']: + default_functions.append(write_rust_default_trait_impl(f'{obj_name}Configuration{name_suffix}', special_serde_object)) + return output + +def should_validate_type(rust_type): + if is_vector_of(rust_type): + sub_type = get_vector_sub_type(rust_type) + return should_validate_type(sub_type) + if rust_type in ['bool', 'u8', 'u16', 'u32', 'u64', 'f64', 'String']: + return False + if rust_type in ['Action', 'Selector', 'dnsdistsettings::SelectorsConfiguration']: + return False + return True + +def get_validation_for_field(field_name, rust_type): + if not should_validate_type(rust_type): + return '' + if not is_vector_of(rust_type): + return f' self.{field_name}.validate()?;\n' + + return f''' for sub_type in &self.{field_name} {{ + sub_type.validate()?; + }} +''' + +def get_struct_validation_function_from_definition(name, parameters, special_serde_object=False): + if len(parameters) == 0: + return '' + namespace = 'dnsdistsettings::' if not special_serde_object else '' + suffix = 'Serde' if special_serde_object else '' + struct_name = get_rust_object_name(name) + output = f'''impl {namespace}{struct_name}Configuration{suffix} {{ + fn validate(&self) -> Result<(), ValidationError> {{ +''' + for parameter in parameters: + field_name = get_rust_field_name(parameter['name']) if parameter['name'] != 'namespace' else 'name_space' + rust_type = parameter['type'] + output += get_validation_for_field(field_name, rust_type) + output += ''' Ok(()) + } +}''' + return output + +def get_definitions_from_file(def_file): + with open(def_file, 'rt', encoding="utf-8") as fd: + definitions = yaml.safe_load(fd.read()) + return definitions + +def include_file(out_fp, include_file_name): + with open(include_file_name, mode='r', encoding='utf-8') as in_fp: + out_fp.write(f'// START INCLUDE {include_file_name}\n') + out_fp.write(in_fp.read()) + out_fp.write(f'// END INCLUDE {include_file_name}\n') + +def generate_flat_settings_for_cxx(definitions, out_file_path): + cxx_flat_settings_fp = get_temporary_file_for_generated_code(out_file_path) + + include_file(cxx_flat_settings_fp, out_file_path + 'dnsdist-configuration-yaml-items-generated-pre-in.cc') + + # first we do runtime-settable settings + cxx_flat_settings_fp.write('''#if defined(HAVE_YAML_CONFIGURATION) +#include "rust/cxx.h" +#include "rust/lib.rs.h" +#include "dnsdist-configuration-yaml-internal.hh" + +namespace dnsdist::configuration::yaml +{ +void convertRuntimeFlatSettingsFromRust(const dnsdist::rust::settings::GlobalConfiguration& yamlConfig, dnsdist::configuration::RuntimeConfiguration& config) +{\n''') + for category_name, keys in definitions.items(): + if not 'parameters' in keys: + continue + + if 'category' in keys: + category_name = keys['category'] + else: + category_name = get_rust_field_name(category_name) + + for parameter in keys['parameters']: + if not 'internal-field-name' in parameter or not 'runtime-configurable' in parameter or not parameter['runtime-configurable']: + continue + internal_field_name = parameter['internal-field-name'] + rust_field_name = get_rust_field_name(parameter['name']) if not 'rename' in parameter else parameter['rename'] + default = parameter['default'] if parameter['type'] != 'String' else '"' + parameter['default'] + '"' + cxx_flat_settings_fp.write(f' if (config.{internal_field_name} == {default}) {{\n') + if parameter['type'] != 'String': + cxx_flat_settings_fp.write(f' config.{internal_field_name} = yamlConfig.{category_name}.{rust_field_name};\n') + else: + cxx_flat_settings_fp.write(f' config.{internal_field_name} = std::string(yamlConfig.{category_name}.{rust_field_name});\n') + cxx_flat_settings_fp.write(' }\n') + + cxx_flat_settings_fp.write('''}\n''') + + # then immutable ones + cxx_flat_settings_fp.write('''void convertImmutableFlatSettingsFromRust(const dnsdist::rust::settings::GlobalConfiguration& yamlConfig, dnsdist::configuration::ImmutableConfiguration& config) +{\n''') + for category_name, keys in definitions.items(): + if not 'parameters' in keys: + continue + + if 'category' in keys: + category_name = keys['category'] + else: + category_name = get_rust_field_name(category_name) + + for parameter in keys['parameters']: + if not 'internal-field-name' in parameter or not 'runtime-configurable' in parameter or parameter['runtime-configurable']: + continue + internal_field_name = parameter['internal-field-name'] + rust_field_name = get_rust_field_name(parameter['name']) if not 'rename' in parameter else parameter['rename'] + default = parameter['default'] if parameter['type'] != 'String' else '"' + parameter['default'] + '"' + cxx_flat_settings_fp.write(f' if (config.{internal_field_name} == {default}) {{\n') + if parameter['type'] != 'String': + cxx_flat_settings_fp.write(f' config.{internal_field_name} = yamlConfig.{category_name}.{rust_field_name};\n') + else: + cxx_flat_settings_fp.write(f' config.{internal_field_name} = std::string(yamlConfig.{category_name}.{rust_field_name});\n') + cxx_flat_settings_fp.write(' }\n') + + cxx_flat_settings_fp.write('''}\n +} +#endif /* defined(HAVE_YAML_CONFIGURATION) */ +''') + + os.rename(cxx_flat_settings_fp.name, out_file_path + 'dnsdist-configuration-yaml-items-generated.cc') + +def generate_actions_config(output, response, default_functions): + suffix = 'ResponseAction' if response else 'Action' + actions_definitions = get_actions_definitions(response) + action_buffer = '' + for action in actions_definitions: + name = get_rust_object_name(action['name']) + struct_name = f'{name}{suffix}Configuration' + indent = ' ' * 4 + if not 'skip-serde' in action or not action['skip-serde']: + action_buffer += f'''{indent}#[derive(Deserialize, Serialize, Debug, PartialEq)] +{indent}#[serde(deny_unknown_fields)]\n''' + else: + action_buffer += f'{indent}#[derive(Default)]\n' + + action_buffer += f'{indent}struct {struct_name} {{\n' + + indent = ' ' * 8 + if not 'skip-serde' in action or not action['skip-serde']: + action_buffer += f'{indent}#[serde(default, skip_serializing_if = "crate::is_default")]\n' + action_buffer += f'{indent}name: String,\n' + + action_buffer += get_rust_struct_fields_from_definition(struct_name, action, default_functions, 8) + + action_buffer += ' }\n\n' + + output.write(action_buffer) + +def generate_selectors_config(output, default_functions): + suffix = 'Selector' + selectors_definitions = get_selectors_definitions() + selector_buffer = '' + for selector in selectors_definitions: + name = get_rust_object_name(selector['name']) + struct_name = f'{name}{suffix}Configuration' + indent = ' ' * 4 + if not 'skip-serde' in selector or not selector['skip-serde']: + selector_buffer += f'''{indent}#[derive(Deserialize, Serialize, Debug, PartialEq)] +{indent}#[serde(deny_unknown_fields)]\n''' + else: + selector_buffer += f'{indent}#[derive(Default)]\n' + + selector_buffer += f'{indent}struct {struct_name} {{\n' + + indent = ' ' * 8 + if not 'skip-serde' in selector or not selector['skip-serde']: + selector_buffer += f'{indent}#[serde(default, skip_serializing_if = "crate::is_default")]\n' + selector_buffer += f'{indent}name: String,\n' + + selector_buffer += get_rust_struct_fields_from_definition(struct_name, selector, default_functions, 8) + + selector_buffer += ' }\n\n' + + output.write(selector_buffer) + +def generate_cpp_action_headers(): + cpp_action_headers_fp = get_temporary_file_for_generated_code('..') + header_buffer = '' + + # query actions + actions_definitions = get_actions_definitions(False) + suffix = 'Action' + for action in actions_definitions: + name = get_rust_object_name(action['name']) + struct_name = f'{name}{suffix}Configuration' + header_buffer += f'struct {struct_name};\n' + header_buffer += f'std::shared_ptr get{name}{suffix}(const {struct_name}& config);\n' + + # response actions + actions_definitions = get_actions_definitions(True) + suffix = 'ResponseAction' + for action in actions_definitions: + name = get_rust_object_name(action['name']) + struct_name = f'{name}{suffix}Configuration' + header_buffer += f'struct {struct_name};\n' + header_buffer += f'std::shared_ptr get{name}{suffix}(const {struct_name}& config);\n' + + cpp_action_headers_fp.write(header_buffer) + os.rename(cpp_action_headers_fp.name, '../dnsdist-rust-bridge-actions-generated.hh') + +def generate_cpp_selector_headers(): + cpp_selector_headers_fp = get_temporary_file_for_generated_code('..') + header_buffer = '' + + selectors_definitions = get_selectors_definitions() + suffix = 'Selector' + for selector in selectors_definitions: + name = get_rust_object_name(selector['name']) + struct_name = f'{name}{suffix}Configuration' + header_buffer += f'struct {struct_name};\n' + header_buffer += f'std::shared_ptr get{name}{suffix}(const {struct_name}& config);\n' + cpp_selector_headers_fp.write(header_buffer) + os.rename(cpp_selector_headers_fp.name, '../dnsdist-rust-bridge-selectors-generated.hh') + +def get_cpp_parameters(struct_type, struct_name, parameters, skip_name): + output = '' + for parameter in parameters: + name = parameter['name'] + ptype = parameter['type'] + if name == 'name' and skip_name: + continue + pname = get_rust_field_name(name) + if len(output) > 0: + output += ', ' + field = f'{struct_name}.{pname}' + if ptype == 'PacketBuffer': + field = f'PacketBuffer({field}.data(), {field}.data() + {field}.size())' + elif ptype == 'DNSName': + field = f'DNSName(std::string({field}))' + elif ptype == 'ComboAddress': + field = f'ComboAddress(std::string({field}))' + elif ptype == 'String': + field = f'std::string({field})' + elif ptype == 'ResponseConfig': + field = f'convertResponseConfig({field})' + elif ptype == 'Vec': + field = f'convertSVCRecordParameters({field})' + elif ptype == 'SOAParams': + field = f'convertSOAParams({field})' + output += field + return output + +def generate_cpp_action_wrappers(): + cpp_action_wrappers_fp = get_temporary_file_for_generated_code('..') + wrappers_buffer = '' + + # query actions + actions_definitions = get_actions_definitions(False) + suffix = 'Action' + for action in actions_definitions: + if 'skip-rust' in action and action['skip-rust']: + continue + name = get_rust_object_name(action['name']) + struct_name = f'{name}{suffix}Configuration' + parameters = get_cpp_parameters(struct_name, 'config', action['parameters'], True) if 'parameters' in action else '' + wrappers_buffer += f'''std::shared_ptr get{name}{suffix}(const {struct_name}& config) +{{ + auto action = dnsdist::actions::get{name}{suffix}({parameters}); + return newDNSActionWrapper(std::move(action), config.name); +}} +''' + + # response actions + actions_definitions = get_actions_definitions(True) + suffix = 'ResponseAction' + for action in actions_definitions: + if 'skip-rust' in action and action['skip-rust']: + continue + name = get_rust_object_name(action['name']) + struct_name = f'{name}{suffix}Configuration' + parameters = get_cpp_parameters(struct_name, 'config', action['parameters'], True) if 'parameters' in action else '' + wrappers_buffer += f'''std::shared_ptr get{name}{suffix}(const {struct_name}& config) +{{ + auto action = dnsdist::actions::get{name}{suffix}({parameters}); + return newDNSResponseActionWrapper(std::move(action), config.name); +}} +''' + + cpp_action_wrappers_fp.write(wrappers_buffer) + os.rename(cpp_action_wrappers_fp.name, '../dnsdist-rust-bridge-actions-generated.cc') + +def generate_cpp_selector_wrappers(): + cpp_selector_wrappers_fp = get_temporary_file_for_generated_code('..') + wrappers_buffer = '' + + selectors_definitions = get_selectors_definitions() + suffix = 'Selector' + for selector in selectors_definitions: + if 'skip-rust' in selector and selector['skip-rust']: + continue + name = get_rust_object_name(selector['name']) + struct_name = f'{name}{suffix}Configuration' + parameters = get_cpp_parameters(struct_name, 'config', selector['parameters'], True) if 'parameters' in selector else '' + wrappers_buffer += f'''std::shared_ptr get{name}{suffix}(const {struct_name}& config) +{{ + auto selector = dnsdist::selectors::get{name}{suffix}({parameters}); + return newDNSSelector(std::move(selector), config.name); +}} +''' + + cpp_selector_wrappers_fp.write(wrappers_buffer) + os.rename(cpp_selector_wrappers_fp.name, '../dnsdist-rust-bridge-selectors-generated.cc') + +def generate_rust_actions_enum(output, response): + suffix = 'ResponseAction' if response else 'Action' + actions_definitions = get_actions_definitions(response) + enum_buffer = f'''#[derive(Default, Serialize, Deserialize, Debug, PartialEq)] +#[serde(tag = "type")] +enum {suffix} {{ + #[default] + Default, +''' + + for action in actions_definitions: + name = get_rust_object_name(action['name']) + struct_name = f'{name}{suffix}Configuration' + if struct_name in ['ContinueActionConfiguration']: + # special version for Serde + enum_buffer += f' {name}({struct_name}Serde),\n' + else: + enum_buffer += f' {name}(dnsdistsettings::{struct_name}),\n' + + enum_buffer += '}\n\n' + + output.write(enum_buffer) + +def generate_rust_selectors_enum(output): + suffix = 'Selector' + selectors_definitions = get_selectors_definitions() + enum_buffer = f'''#[derive(Default, Serialize, Deserialize, Debug, PartialEq)] +#[serde(tag = "type")] +enum {suffix} {{ + #[default] + Default, +''' + + for selector in selectors_definitions: + name = get_rust_object_name(selector['name']) + struct_name = f'{name}{suffix}Configuration' + if struct_name in ['AndSelectorConfiguration', 'OrSelectorConfiguration', 'NotSelectorConfiguration']: + # special version for Serde + enum_buffer += f' {name}({struct_name}Serde),\n' + else: + enum_buffer += f' {name}(dnsdistsettings::{struct_name}),\n' + + enum_buffer += '}\n\n' + + output.write(enum_buffer) + +def get_actions_definitions(response): + def_file = '../dnsdist-response-actions-definitions.yml' if response else '../dnsdist-actions-definitions.yml' + return get_definitions_from_file(def_file) + +def get_selectors_definitions(): + def_file = '../dnsdist-selectors-definitions.yml' + return get_definitions_from_file(def_file) + +def generate_cpp_action_selector_functions_callable_from_rust(output): + output_buffer = ''' + /* + * Functions callable from Rust (actions and selectors) + */ + unsafe extern "C++" { +''' + # first query actions + actions_definitions = get_actions_definitions(False) + suffix = 'Action' + for action in actions_definitions: + name = get_rust_object_name(action['name']) + output_buffer += f' fn get{name}{suffix}(config: &{name}{suffix}Configuration) -> SharedPtr;\n' + + # then response actions + actions_definitions = get_actions_definitions(True) + suffix = 'ResponseAction' + for action in actions_definitions: + name = get_rust_object_name(action['name']) + output_buffer += f' fn get{name}{suffix}(config: &{name}{suffix}Configuration) -> SharedPtr;\n' + + # then selectors + selectors_definitions = get_selectors_definitions() + suffix = 'Selector' + for selector in selectors_definitions: + name = get_rust_object_name(selector['name']) + output_buffer += f' fn get{name}{suffix}(config: &{name}{suffix}Configuration) -> SharedPtr;\n' + + output_buffer += ' }\n' + output.write(output_buffer) + +def generate_rust_action_to_config(output, response): + suffix = 'ResponseAction' if response else 'Action' + actions_definitions = get_actions_definitions(response) + function_name = 'get_one_action_from_serde' if not response else 'get_one_response_action_from_serde' + enum_buffer = f'''fn {function_name}(action: &{suffix}) -> Option {{ + match action {{ + {suffix}::Default => {{}} +''' + + for action in actions_definitions: + name = get_rust_object_name(action['name']) + var = name.lower() + if name in ['Continue']: + enum_buffer += f''' {suffix}::{name}(cont) => {{ + let mut config: dnsdistsettings::{name}{suffix}Configuration = Default::default(); + let new_action = get_one_action_from_serde(&*cont.action); + if new_action.is_some() {{ + config.action = new_action.unwrap(); + }} + return Some(dnsdistsettings::SharedDNS{suffix} {{ + action: dnsdistsettings::get{name}{suffix}(&config), + }}); + }} +''' + else: + enum_buffer += f''' {suffix}::{name}(config) => {{ + let tmp_action = dnsdistsettings::get{name}{suffix}(&config); + return Some(dnsdistsettings::SharedDNS{suffix} {{ + action: tmp_action, + }}); + }} +''' + + enum_buffer += ''' } + None +} +''' + + output.write(enum_buffer) + +def generate_rust_selector_to_config(output): + suffix = 'Selector' + selectors_definitions = get_selectors_definitions() + function_name = 'get_one_selector_from_serde' + enum_buffer = f'''fn {function_name}(selector: &{suffix}) -> Option {{ + match selector {{ + {suffix}::Default => {{}} +''' + + for selector in selectors_definitions: + name = get_rust_object_name(selector['name']) + var = name.lower() + if name in ['And', 'Or']: + enum_buffer += f''' {suffix}::{name}({var}) => {{ + let mut config: dnsdistsettings::{name}{suffix}Configuration = Default::default(); + for sub_selector in &{var}.selectors {{ + let new_selector = get_one_selector_from_serde(&sub_selector); + if new_selector.is_some() {{ + config.selectors.push(new_selector.unwrap()); + }} + }} + return Some(dnsdistsettings::SharedDNS{suffix} {{ + selector: dnsdistsettings::get{name}{suffix}(&config), + }}); + }} +''' + elif name in ['Not']: + enum_buffer += f''' {suffix}::{name}({var}) => {{ + let mut config: dnsdistsettings::{name}{suffix}Configuration = Default::default(); + let new_selector = get_one_selector_from_serde(&*{var}.selector); + if new_selector.is_some() {{ + config.selector = new_selector.unwrap(); + }} + return Some(dnsdistsettings::SharedDNS{suffix} {{ + selector: dnsdistsettings::get{name}{suffix}(&config), + }}); + }} +''' + else: + enum_buffer += f''' {suffix}::{name}({var}) => {{ + let tmp_selector = dnsdistsettings::get{name}{suffix}(&{var}); + return Some(dnsdistsettings::SharedDNS{suffix} {{ + selector: tmp_selector, + }}); + }} +''' + + enum_buffer += ''' } + None +} +''' + + output.write(enum_buffer) + +def handle_structures(generated_fp, definitions, default_functions, validation_functions): + for definition_name, keys in definitions.items(): + generated_fp.write(get_rust_struct_from_definition(definition_name, keys, default_functions) + '\n') + if definition_name not in ['global', 'proto_buf_meta', 'proxy_protocol_value', 'query_rule', 'response_rule']: + validation_functions.append(get_struct_validation_function_from_definition(definition_name, keys['parameters'] if 'parameters' in keys else [])) + +def get_temporary_file_for_generated_code(directory): + generated_fp = tempfile.NamedTemporaryFile(mode='w+t', encoding='utf-8', dir=directory, delete=False) + generated_fp.write('// !! This file has been generated by dnsdist-settings-generator.py, do not edit by hand!!\n') + return generated_fp + +def main(): + if len(sys.argv) != 2: + print(f'Usage: {sys.argv[0]} ') + sys.exit(1) + + src_dir = './' + definitions = get_definitions_from_file(sys.argv[1]) + default_functions = [] + validation_functions = [] + global_objects = {} + + generate_cpp_action_headers() + generate_cpp_action_wrappers() + generate_cpp_selector_headers() + generate_cpp_selector_wrappers() + + generated_fp = get_temporary_file_for_generated_code(src_dir + '/rust/src/') + include_file(generated_fp, src_dir + 'rust-pre-in.rs') + + generate_actions_config(generated_fp, False, default_functions) + generate_actions_config(generated_fp, True, default_functions) + generate_selectors_config(generated_fp, default_functions) + + generate_flat_settings_for_cxx(definitions, src_dir) + + handle_structures(generated_fp, definitions, default_functions, validation_functions) + + generate_cpp_action_selector_functions_callable_from_rust(generated_fp) + + include_file(generated_fp, src_dir + 'rust-middle-in.rs') + # we are now outside of the dnsdistsettings namespace + + # generate the special global configuration Serde structure + for definition_name, keys in definitions.items(): + if definition_name == 'global': + generated_fp.write(get_rust_struct_from_definition(definition_name, keys, default_functions, special_serde_object=True) + '\n') + validation_functions.append(get_struct_validation_function_from_definition(definition_name, keys['parameters'] if 'parameters' in keys else [], True)) + + generate_rust_actions_enum(generated_fp, False) + generate_rust_actions_enum(generated_fp, True) + generate_rust_selectors_enum(generated_fp) + + # the generated functions for the default values and validation + for function_def in default_functions: + generated_fp.write(function_def + '\n') + + for function_def in validation_functions: + generated_fp.write(function_def + '\n') + + generate_rust_action_to_config(generated_fp, False) + generate_rust_action_to_config(generated_fp, True) + generate_rust_selector_to_config(generated_fp) + + include_file(generated_fp, src_dir + 'rust-post-in.rs') + + os.rename(generated_fp.name, src_dir + '/rust/src/lib.rs') + +if __name__ == '__main__': + main() diff --git a/pdns/dnsdistdist/dnsdist-rust-lib/rust-middle-in.rs b/pdns/dnsdistdist/dnsdist-rust-lib/rust-middle-in.rs new file mode 100644 index 000000000000..6dea5667323d --- /dev/null +++ b/pdns/dnsdistdist/dnsdist-rust-lib/rust-middle-in.rs @@ -0,0 +1,97 @@ + /* + * Functions callable from C++ + */ + extern "Rust" { + fn from_yaml_string(str: &str) -> Result; + } + /* + * Functions callable from Rust + */ + unsafe extern "C++" { + include!("dnsdist-rust-bridge.hh"); + type DNSSelector; + type DNSActionWrapper; + type DNSResponseActionWrapper; + fn registerProtobufLogger(config: &ProtobufLoggerConfiguration); + fn registerDnstapLogger(config: &DnstapLoggerConfiguration); + fn registerKVSObjects(config: &KeyValueStoresConfiguration); + } +} + +impl Default for dnsdistsettings::SharedDNSAction { + fn default() -> dnsdistsettings::SharedDNSAction { + dnsdistsettings::SharedDNSAction { + action: cxx::SharedPtr::null(), + } + } +} + +impl Default for dnsdistsettings::SharedDNSSelector { + fn default() -> dnsdistsettings::SharedDNSSelector { + dnsdistsettings::SharedDNSSelector { + selector: cxx::SharedPtr::null(), + } + } +} + +#[derive(Default, Deserialize, Serialize, Debug, PartialEq)] +#[serde(deny_unknown_fields)] +struct AndSelectorConfigurationSerde { + #[serde(default, skip_serializing_if = "crate::is_default")] + selectors: Vec, +} + +#[derive(Default, Deserialize, Serialize, Debug, PartialEq)] +#[serde(deny_unknown_fields)] +struct OrSelectorConfigurationSerde { + #[serde(default, skip_serializing_if = "crate::is_default")] + selectors: Vec, +} + +#[derive(Default, Deserialize, Serialize, Debug, PartialEq)] +#[serde(deny_unknown_fields)] +struct NotSelectorConfigurationSerde { + #[serde(default, skip_serializing_if = "crate::is_default")] + selector: Box, +} + +#[derive(Default, Deserialize, Serialize, Debug, PartialEq)] +#[serde(deny_unknown_fields)] +struct ContinueActionConfigurationSerde { + #[serde(default, skip_serializing_if = "crate::is_default")] + action: Box, +} + +#[derive(Default, Deserialize, Serialize, Debug, PartialEq)] +#[serde(deny_unknown_fields)] +struct QueryRuleConfigurationSerde { + #[serde(default, skip_serializing_if = "crate::is_default")] + name: String, + #[serde(default, skip_serializing_if = "crate::is_default")] + uuid: String, + selector: Selector, + action: Action, +} + +impl QueryRuleConfigurationSerde { + fn validate(&self) -> Result<(), ValidationError> { + Ok(()) + } +} + +#[derive(Default, Deserialize, Serialize, Debug, PartialEq)] +#[serde(deny_unknown_fields)] +struct ResponseRuleConfigurationSerde { + #[serde(default, skip_serializing_if = "crate::is_default")] + name: String, + #[serde(default, skip_serializing_if = "crate::is_default")] + uuid: String, + selector: Selector, + action: ResponseAction, +} + +impl ResponseRuleConfigurationSerde { + fn validate(&self) -> Result<(), ValidationError> { + Ok(()) + } +} diff --git a/pdns/dnsdistdist/dnsdist-rust-lib/rust-post-in.rs b/pdns/dnsdistdist/dnsdist-rust-lib/rust-post-in.rs new file mode 100644 index 000000000000..1209fe8bb7ca --- /dev/null +++ b/pdns/dnsdistdist/dnsdist-rust-lib/rust-post-in.rs @@ -0,0 +1,124 @@ +fn get_selectors_from_serde( + selectors_from_serde: &Vec, +) -> Vec { + let mut results: Vec = Vec::new(); + + for rule in selectors_from_serde { + let selector = get_one_selector_from_serde(&rule); + if selector.is_some() { + results.push(selector.unwrap()); + } + } + results +} + +fn get_query_rules_from_serde( + rules_from_serde: &Vec, +) -> Vec { + let mut results: Vec = Vec::new(); + + for rule in rules_from_serde { + let selector = get_one_selector_from_serde(&rule.selector); + let action = get_one_action_from_serde(&rule.action); + if selector.is_some() && action.is_some() { + results.push(dnsdistsettings::QueryRuleConfiguration { + name: rule.name.clone(), + uuid: rule.uuid.clone(), + selector: selector.unwrap(), + action: action.unwrap(), + }); + } + } + results +} + +fn get_response_rules_from_serde( + rules_from_serde: &Vec, +) -> Vec { + let mut results: Vec = Vec::new(); + + for rule in rules_from_serde { + let selector = get_one_selector_from_serde(&rule.selector); + let action = get_one_response_action_from_serde(&rule.action); + if selector.is_some() && action.is_some() { + results.push(dnsdistsettings::ResponseRuleConfiguration { + name: rule.name.clone(), + uuid: rule.uuid.clone(), + selector: selector.unwrap(), + action: action.unwrap(), + }); + } + } + results +} + +fn register_remote_loggers( + config: &dnsdistsettings::RemoteLoggingConfiguration, +) { + for logger in &config.protobuf_loggers { + dnsdistsettings::registerProtobufLogger(&logger); + } + for logger in &config.dnstap_loggers { + dnsdistsettings::registerDnstapLogger(&logger); + } +} + +fn get_global_configuration_from_serde( + serde: GlobalConfigurationSerde, +) -> dnsdistsettings::GlobalConfiguration { + let mut config: dnsdistsettings::GlobalConfiguration = Default::default(); + config.key_value_stores = serde.key_value_stores; + config.webserver = serde.webserver; + config.console = serde.console; + config.edns_client_subnet = serde.edns_client_subnet; + config.dynamic_rules_settings = serde.dynamic_rules_settings; + config.dynamic_rules = serde.dynamic_rules; + config.acl = serde.acl; + config.ring_buffers = serde.ring_buffers; + config.binds = serde.binds; + config.backends = serde.backends; + config.cache_settings = serde.cache_settings; + config.security_polling = serde.security_polling; + config.general = serde.general; + config.packet_caches = serde.packet_caches; + config.proxy_protocol = serde.proxy_protocol; + config.snmp = serde.snmp; + config.query_count = serde.query_count; + config.load_balancing_policies = serde.load_balancing_policies; + config.pools = serde.pools; + config.metrics = serde.metrics; + config.remote_logging = serde.remote_logging; + config.tuning = serde.tuning; + // this needs to be done before the rules so that they can refer to the loggers + register_remote_loggers(&config.remote_logging); + // this needs to be done before the rules so that they can refer to the KVS objects + dnsdistsettings::registerKVSObjects(&config.key_value_stores); + // this needs to be done BEFORE the rules so that they can refer to the selectors + // by name + config.selectors = get_selectors_from_serde(&serde.selectors); + config.query_rules = get_query_rules_from_serde(&serde.query_rules); + config.cache_miss_rules = get_query_rules_from_serde(&serde.cache_miss_rules); + config.response_rules = get_response_rules_from_serde(&serde.response_rules); + config.cache_hit_response_rules = get_response_rules_from_serde(&serde.cache_hit_response_rules); + config.cache_inserted_response_rules = get_response_rules_from_serde(&serde.cache_inserted_response_rules); + config.self_answered_response_rules = get_response_rules_from_serde(&serde.self_answered_response_rules); + config.xfr_response_rules = get_response_rules_from_serde(&serde.xfr_response_rules); + config +} + +pub fn from_yaml_string( + str: &str, +) -> Result { + let serde_config: Result = + serde_yaml::from_str(str); + + if !serde_config.is_err() { + let validation_result = serde_config.as_ref().unwrap().validate(); + if let Err(e) = validation_result { + println!("Error validating the configuration loaded from {}: {}", str, e); + } + } + let config: dnsdistsettings::GlobalConfiguration = + get_global_configuration_from_serde(serde_config?); + return Ok(config); +} diff --git a/pdns/dnsdistdist/dnsdist-rust-lib/rust-pre-in.rs b/pdns/dnsdistdist/dnsdist-rust-lib/rust-pre-in.rs new file mode 100644 index 000000000000..7860963c48f6 --- /dev/null +++ b/pdns/dnsdistdist/dnsdist-rust-lib/rust-pre-in.rs @@ -0,0 +1,82 @@ +use serde::{Deserialize, Serialize}; + +mod helpers; +use helpers::*; + +// Suppresses "Deserialize unused" warning +#[derive(Deserialize, Serialize)] +struct UnusedStruct {} + +#[derive(Debug)] +pub struct ValidationError { + msg: String, +} + +#[cxx::bridge(namespace = dnsdist::rust::settings)] +mod dnsdistsettings { + #[derive(Default, Deserialize, Serialize, Debug, PartialEq)] + #[serde(deny_unknown_fields)] + struct ResponseConfig { + #[serde(default, skip_serializing_if = "crate::is_default")] + set_aa: bool, + #[serde(default, skip_serializing_if = "crate::is_default")] + set_ad: bool, + #[serde(default, skip_serializing_if = "crate::is_default")] + set_ra: bool, + #[serde(default, skip_serializing_if = "crate::is_default")] + ttl: u32, + } + + #[derive(Default, Deserialize, Serialize, Debug, PartialEq)] + #[serde(deny_unknown_fields)] + struct SOAParams { + serial: u32, + refresh: u32, + retry: u32, + expire: u32, + minimum: u32, + } + + #[derive(Default, Deserialize, Serialize, Debug, PartialEq)] + #[serde(deny_unknown_fields)] + struct SVCRecordAdditionalParams { + #[serde(default, skip_serializing_if = "crate::is_default")] + key: u16, + #[serde(default, skip_serializing_if = "crate::is_default")] + value: String, + } + + #[derive(Default, Deserialize, Serialize, Debug, PartialEq)] + #[serde(deny_unknown_fields)] + struct SVCRecordParameters { + #[serde(default, skip_serializing_if = "crate::is_default")] + mandatory_params: Vec, + #[serde(default, skip_serializing_if = "crate::is_default")] + alpns: Vec, + #[serde(default, skip_serializing_if = "crate::is_default")] + ipv4_hints: Vec, + #[serde(default, skip_serializing_if = "crate::is_default")] + ipv6_hints: Vec, + #[serde(default, skip_serializing_if = "crate::is_default")] + additional_params: Vec, + #[serde(default, skip_serializing_if = "crate::is_default")] + target: String, + #[serde(default, skip_serializing_if = "crate::is_default")] + port: u16, + #[serde(default, skip_serializing_if = "crate::is_default")] + priority: u16, + #[serde(default, skip_serializing_if = "crate::is_default")] + no_default_alpn: bool, + } + + struct SharedDNSSelector { + selector: SharedPtr, + } + + struct SharedDNSAction { + action: SharedPtr, + } + + struct SharedDNSResponseAction { + action: SharedPtr, + } diff --git a/pdns/dnsdistdist/dnsdist-rust-lib/rust/.gitignore b/pdns/dnsdistdist/dnsdist-rust-lib/rust/.gitignore new file mode 100644 index 000000000000..e9a64ebe1de4 --- /dev/null +++ b/pdns/dnsdistdist/dnsdist-rust-lib/rust/.gitignore @@ -0,0 +1,5 @@ +/target +/Makefile +/Makefile.in +/cxx.h +/lib.rs.h diff --git a/pdns/dnsdistdist/dnsdist-rust-lib/rust/Cargo.lock b/pdns/dnsdistdist/dnsdist-rust-lib/rust/Cargo.lock new file mode 100644 index 000000000000..58b8d6f03aad --- /dev/null +++ b/pdns/dnsdistdist/dnsdist-rust-lib/rust/Cargo.lock @@ -0,0 +1,332 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "anyhow" +version = "1.0.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86fdf8605db99b54d3cd748a44c6d04df638eb5dafb219b135d0149bd0db01f6" + +[[package]] +name = "cc" +version = "1.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50d2eb3cd3d1bf4529e31c215ee6f93ec5a3d536d9f578f93d9d33ee19562932" +dependencies = [ + "shlex", +] + +[[package]] +name = "codespan-reporting" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e" +dependencies = [ + "termcolor", + "unicode-width", +] + +[[package]] +name = "cxx" +version = "1.0.126" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c4eae4b7fc8dcb0032eb3b1beee46b38d371cdeaf2d0c64b9944f6f69ad7755" +dependencies = [ + "cc", + "cxxbridge-flags", + "cxxbridge-macro", + "link-cplusplus", +] + +[[package]] +name = "cxx-build" +version = "1.0.126" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c822bf7fb755d97328d6c337120b6f843678178751cba33c9da25cf522272e0" +dependencies = [ + "cc", + "codespan-reporting", + "once_cell", + "proc-macro2", + "quote", + "scratch", + "syn", +] + +[[package]] +name = "cxxbridge-flags" +version = "1.0.126" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "719d6197dc016c88744aff3c0d0340a01ecce12e8939fc282e7c8f583ee64bc6" + +[[package]] +name = "cxxbridge-macro" +version = "1.0.126" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35de3b547387863c8f82013c4f79f1c2162edee956383e4089e1d04c18c4f16c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "dnsdist-rust" +version = "2.0.0" +dependencies = [ + "cxx", + "cxx-build", + "serde", + "serde_yml", +] + +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + +[[package]] +name = "hashbrown" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" + +[[package]] +name = "indexmap" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93ead53efc7ea8ed3cfb0c79fc8023fbb782a5432b52830b6518941cebe6505c" +dependencies = [ + "equivalent", + "hashbrown", +] + +[[package]] +name = "itoa" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" + +[[package]] +name = "libyml" +version = "0.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3302702afa434ffa30847a83305f0a69d6abd74293b6554c18ec85c7ef30c980" +dependencies = [ + "anyhow", + "version_check", +] + +[[package]] +name = "link-cplusplus" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d240c6f7e1ba3a28b0249f774e6a9dd0175054b52dfbb61b16eb8505c3785c9" +dependencies = [ + "cc", +] + +[[package]] +name = "memchr" +version = "2.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" + +[[package]] +name = "once_cell" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" + +[[package]] +name = "proc-macro2" +version = "1.0.86" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "ryu" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" + +[[package]] +name = "scratch" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3cf7c11c38cb994f3d40e8a8cde3bbd1f72a435e4c49e85d6553d8312306152" + +[[package]] +name = "serde" +version = "1.0.208" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cff085d2cb684faa248efb494c39b68e522822ac0de72ccf08109abde717cfb2" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.208" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24008e81ff7613ed8e5ba0cfaf24e2c2f1e5b8a0495711e44fcd4882fca62bcf" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_yml" +version = "0.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59e2dd588bf1597a252c3b920e0143eb99b0f76e4e082f4c92ce34fbc9e71ddd" +dependencies = [ + "indexmap", + "itoa", + "libyml", + "memchr", + "ryu", + "serde", + "version_check", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "syn" +version = "2.0.75" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6af063034fc1935ede7be0122941bafa9bacb949334d090b77ca98b5817c7d9" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "termcolor" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" + +[[package]] +name = "unicode-width" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0336d538f7abc86d282a4189614dfaa90810dfc2c6f6427eaf88e16311dd225d" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "winapi-util" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" diff --git a/pdns/dnsdistdist/dnsdist-rust-lib/rust/Cargo.toml b/pdns/dnsdistdist/dnsdist-rust-lib/rust/Cargo.toml new file mode 100644 index 000000000000..d3537feb5c88 --- /dev/null +++ b/pdns/dnsdistdist/dnsdist-rust-lib/rust/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "dnsdist-rust" +# Convention: major/minor is equal to DNSdist's major/minor +version = "2.0.0" +edition = "2021" + +[lib] +name = "dnsdist_rust" +crate-type = ["staticlib"] + +[dependencies] +cxx = "1.0" +serde = { version = "1.0", features = ["derive"] } +serde_yaml = { package = "serde_yml", version = "0.0.12" } + +[build-dependencies] +cxx-build = "1.0" diff --git a/pdns/dnsdistdist/dnsdist-rust-lib/rust/Makefile.am b/pdns/dnsdistdist/dnsdist-rust-lib/rust/Makefile.am new file mode 100644 index 000000000000..c30191a2c569 --- /dev/null +++ b/pdns/dnsdistdist/dnsdist-rust-lib/rust/Makefile.am @@ -0,0 +1,21 @@ +CARGO ?= cargo + +EXTRA_DIST = \ + Cargo.toml \ + Cargo.lock \ + build.rs \ + src/helpers.rs + +if HAVE_YAML_CONFIGURATION +all install: libdnsdist_rust.a + +libdnsdist_rust.a: src/lib.rs src/helpers.rs Cargo.toml Cargo.lock + SYSCONFDIR=$(sysconfdir) $(CARGO) build --release $(RUST_TARGET) --target-dir=$(builddir)/target --manifest-path ${srcdir}/Cargo.toml + cp target/release/libdnsdist_rust.a libdnsdist_rust.a + cp target/cxxbridge/dnsdist-rust/src/lib.rs.h lib.rs.h + cp target/cxxbridge/rust/cxx.h cxx.h + +clean-local: + rm -rf libdnsdist_rust.a lib.rs.h cxx.h target + +endif diff --git a/pdns/dnsdistdist/dnsdist-rust-lib/rust/build.rs b/pdns/dnsdistdist/dnsdist-rust-lib/rust/build.rs new file mode 100644 index 000000000000..18d00f906a58 --- /dev/null +++ b/pdns/dnsdistdist/dnsdist-rust-lib/rust/build.rs @@ -0,0 +1,12 @@ +fn main() { + cxx_build::bridge("src/lib.rs") + .flag_if_supported("-std=c++17") + .flag("-Isrc") + .flag("-I.") + .flag("-I..") + .flag("-I../..") + .compile("dnsdist_rust"); + + println!("cargo:rerun-if-changed=src/lib.rs"); + println!("cargo:rerun-if-changed=src/helpers.rs"); +} \ No newline at end of file diff --git a/pdns/dnsdistdist/dnsdist-rust-lib/rust/src/helpers.rs b/pdns/dnsdistdist/dnsdist-rust-lib/rust/src/helpers.rs new file mode 100644 index 000000000000..289dfe640725 --- /dev/null +++ b/pdns/dnsdistdist/dnsdist-rust-lib/rust/src/helpers.rs @@ -0,0 +1,93 @@ +/* + * 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. + */ + +use crate::ValidationError; +use std::{error::Error, fmt}; + +/* Helper code for validation */ +impl Error for ValidationError {} +impl fmt::Display for ValidationError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}", self.msg) + } +} + +// Generic helpers + +// A helper to define a function returning a constant value and an equal function as a Rust path */ +pub struct U64; +impl U64 { + pub const fn value() -> u64 { + U + } + pub fn is_equal(v: &u64) -> bool { + v == &U + } +} + +pub struct U32; +impl U32 { + pub const fn value() -> u32 { + U + } + pub fn is_equal(v: &u32) -> bool { + v == &U + } +} + +pub struct U16; +impl U16 { + pub const fn value() -> u16 { + U + } + pub fn is_equal(v: &u16) -> bool { + v == &U + } +} + +pub struct U8; +impl U8 { + pub const fn value() -> u8 { + U + } + pub fn is_equal(v: &u8) -> bool { + v == &U + } +} + +// A helper to define constant value as a Rust path */ +pub struct Bool; +impl Bool { + pub const fn value() -> bool { + U + } +} + +// A helper used to decide if a bool value should be skipped +pub fn if_true(v: &bool) -> bool { + *v +} + +/* Helper to decide if a value has a default value, as defined by Default trait */ +pub fn is_default(t: &T) -> bool { + t == &T::default() +} diff --git a/pdns/dnsdistdist/dnsdist-rust-lib/rust/src/lib.rs b/pdns/dnsdistdist/dnsdist-rust-lib/rust/src/lib.rs new file mode 100644 index 000000000000..f6c3fba075e6 --- /dev/null +++ b/pdns/dnsdistdist/dnsdist-rust-lib/rust/src/lib.rs @@ -0,0 +1,4518 @@ +// !! This file has been generated by dnsdist-settings-generator.py, do not edit by hand!! +// START INCLUDE ./rust-pre-in.rs +use serde::{Deserialize, Serialize}; + +mod helpers; +use helpers::*; + +// Suppresses "Deserialize unused" warning +#[derive(Deserialize, Serialize)] +struct UnusedStruct {} + +#[derive(Debug)] +pub struct ValidationError { + msg: String, +} + +#[cxx::bridge(namespace = dnsdist::rust::settings)] +mod dnsdistsettings { + #[derive(Default, Deserialize, Serialize, Debug, PartialEq)] + #[serde(deny_unknown_fields)] + struct ResponseConfig { + #[serde(default, skip_serializing_if = "crate::is_default")] + set_aa: bool, + #[serde(default, skip_serializing_if = "crate::is_default")] + set_ad: bool, + #[serde(default, skip_serializing_if = "crate::is_default")] + set_ra: bool, + #[serde(default, skip_serializing_if = "crate::is_default")] + ttl: u32, + } + + #[derive(Default, Deserialize, Serialize, Debug, PartialEq)] + #[serde(deny_unknown_fields)] + struct SOAParams { + serial: u32, + refresh: u32, + retry: u32, + expire: u32, + minimum: u32, + } + + #[derive(Default, Deserialize, Serialize, Debug, PartialEq)] + #[serde(deny_unknown_fields)] + struct SVCRecordAdditionalParams { + #[serde(default, skip_serializing_if = "crate::is_default")] + key: u16, + #[serde(default, skip_serializing_if = "crate::is_default")] + value: String, + } + + #[derive(Default, Deserialize, Serialize, Debug, PartialEq)] + #[serde(deny_unknown_fields)] + struct SVCRecordParameters { + #[serde(default, skip_serializing_if = "crate::is_default")] + mandatory_params: Vec, + #[serde(default, skip_serializing_if = "crate::is_default")] + alpns: Vec, + #[serde(default, skip_serializing_if = "crate::is_default")] + ipv4_hints: Vec, + #[serde(default, skip_serializing_if = "crate::is_default")] + ipv6_hints: Vec, + #[serde(default, skip_serializing_if = "crate::is_default")] + additional_params: Vec, + #[serde(default, skip_serializing_if = "crate::is_default")] + target: String, + #[serde(default, skip_serializing_if = "crate::is_default")] + port: u16, + #[serde(default, skip_serializing_if = "crate::is_default")] + priority: u16, + #[serde(default, skip_serializing_if = "crate::is_default")] + no_default_alpn: bool, + } + + struct SharedDNSSelector { + selector: SharedPtr, + } + + struct SharedDNSAction { + action: SharedPtr, + } + + struct SharedDNSResponseAction { + action: SharedPtr, + } +// END INCLUDE ./rust-pre-in.rs + #[derive(Deserialize, Serialize, Debug, PartialEq)] + #[serde(deny_unknown_fields)] + struct AllowActionConfiguration { + #[serde(default, skip_serializing_if = "crate::is_default")] + name: String, + } + + #[derive(Default)] + struct ContinueActionConfiguration { + name: String, + action: SharedDNSAction, + } + + #[derive(Deserialize, Serialize, Debug, PartialEq)] + #[serde(deny_unknown_fields)] + struct DelayActionConfiguration { + #[serde(default, skip_serializing_if = "crate::is_default")] + name: String, + msec: u32, + } + + #[derive(Deserialize, Serialize, Debug, PartialEq)] + #[serde(deny_unknown_fields)] + struct DnstapLogActionConfiguration { + #[serde(default, skip_serializing_if = "crate::is_default")] + name: String, + identity: String, + logger_name: String, + #[serde(default, skip_serializing_if = "crate::is_default")] + alter_function_name: String, + #[serde(default, skip_serializing_if = "crate::is_default")] + alter_function_code: String, + #[serde(default, skip_serializing_if = "crate::is_default")] + alter_function_file: String, + } + + #[derive(Deserialize, Serialize, Debug, PartialEq)] + #[serde(deny_unknown_fields)] + struct DropActionConfiguration { + #[serde(default, skip_serializing_if = "crate::is_default")] + name: String, + } + + #[derive(Deserialize, Serialize, Debug, PartialEq)] + #[serde(deny_unknown_fields)] + struct SetEDNSOptionActionConfiguration { + #[serde(default, skip_serializing_if = "crate::is_default")] + name: String, + code: u32, + data: String, + } + + #[derive(Deserialize, Serialize, Debug, PartialEq)] + #[serde(deny_unknown_fields)] + struct ERCodeActionConfiguration { + #[serde(default, skip_serializing_if = "crate::is_default")] + name: String, + rcode: u8, + #[serde(default, skip_serializing_if = "crate::is_default")] + vars: ResponseConfig, + } + + #[derive(Deserialize, Serialize, Debug, PartialEq)] + #[serde(deny_unknown_fields)] + struct HTTPStatusActionConfiguration { + #[serde(default, skip_serializing_if = "crate::is_default")] + name: String, + status: u16, + body: String, + #[serde(default, skip_serializing_if = "crate::is_default")] + content_type: String, + #[serde(default, skip_serializing_if = "crate::is_default")] + vars: ResponseConfig, + } + + #[derive(Deserialize, Serialize, Debug, PartialEq)] + #[serde(deny_unknown_fields)] + struct KeyValueStoreLookupActionConfiguration { + #[serde(default, skip_serializing_if = "crate::is_default")] + name: String, + kvs_name: String, + lookup_key_name: String, + destination_tag: String, + } + + #[derive(Deserialize, Serialize, Debug, PartialEq)] + #[serde(deny_unknown_fields)] + struct KeyValueStoreRangeLookupActionConfiguration { + #[serde(default, skip_serializing_if = "crate::is_default")] + name: String, + kvs_name: String, + lookup_key_name: String, + destination_tag: String, + } + + #[derive(Deserialize, Serialize, Debug, PartialEq)] + #[serde(deny_unknown_fields)] + struct LogActionConfiguration { + #[serde(default, skip_serializing_if = "crate::is_default")] + name: String, + #[serde(default, skip_serializing_if = "crate::is_default")] + file_name: String, + #[serde(default = "crate::Bool::::value", skip_serializing_if = "crate::if_true")] + binary: bool, + #[serde(default, skip_serializing_if = "crate::is_default")] + append: bool, + #[serde(default, skip_serializing_if = "crate::is_default")] + buffered: bool, + #[serde(default = "crate::Bool::::value", skip_serializing_if = "crate::if_true")] + verbose_only: bool, + #[serde(default, skip_serializing_if = "crate::is_default")] + include_timestamp: bool, + } + + #[derive(Deserialize, Serialize, Debug, PartialEq)] + #[serde(deny_unknown_fields)] + struct LuaActionConfiguration { + #[serde(default, skip_serializing_if = "crate::is_default")] + name: String, + #[serde(default, skip_serializing_if = "crate::is_default")] + function_name: String, + #[serde(default, skip_serializing_if = "crate::is_default")] + function_code: String, + #[serde(default, skip_serializing_if = "crate::is_default")] + function_file: String, + } + + #[derive(Deserialize, Serialize, Debug, PartialEq)] + #[serde(deny_unknown_fields)] + struct LuaFFIActionConfiguration { + #[serde(default, skip_serializing_if = "crate::is_default")] + name: String, + #[serde(default, skip_serializing_if = "crate::is_default")] + function_name: String, + #[serde(default, skip_serializing_if = "crate::is_default")] + function_code: String, + #[serde(default, skip_serializing_if = "crate::is_default")] + function_file: String, + } + + #[derive(Deserialize, Serialize, Debug, PartialEq)] + #[serde(deny_unknown_fields)] + struct LuaFFIPerThreadActionConfiguration { + #[serde(default, skip_serializing_if = "crate::is_default")] + name: String, + code: String, + } + + #[derive(Deserialize, Serialize, Debug, PartialEq)] + #[serde(deny_unknown_fields)] + struct NegativeAndSOAActionConfiguration { + #[serde(default, skip_serializing_if = "crate::is_default")] + name: String, + nxd: bool, + zone: String, + ttl: u32, + mname: String, + rname: String, + soa_parameters: SOAParams, + #[serde(default, skip_serializing_if = "crate::is_default")] + soa_in_authority: bool, + #[serde(default, skip_serializing_if = "crate::is_default")] + vars: ResponseConfig, + } + + #[derive(Deserialize, Serialize, Debug, PartialEq)] + #[serde(deny_unknown_fields)] + struct NoneActionConfiguration { + #[serde(default, skip_serializing_if = "crate::is_default")] + name: String, + } + + #[derive(Deserialize, Serialize, Debug, PartialEq)] + #[serde(deny_unknown_fields)] + struct PoolActionConfiguration { + #[serde(default, skip_serializing_if = "crate::is_default")] + name: String, + pool_name: String, + #[serde(default = "crate::Bool::::value", skip_serializing_if = "crate::if_true")] + stop_processing: bool, + } + + #[derive(Deserialize, Serialize, Debug, PartialEq)] + #[serde(deny_unknown_fields)] + struct QPSActionConfiguration { + #[serde(default, skip_serializing_if = "crate::is_default")] + name: String, + limit: u32, + } + + #[derive(Deserialize, Serialize, Debug, PartialEq)] + #[serde(deny_unknown_fields)] + struct QPSPoolActionConfiguration { + #[serde(default, skip_serializing_if = "crate::is_default")] + name: String, + limit: u32, + pool_name: String, + #[serde(default = "crate::Bool::::value", skip_serializing_if = "crate::if_true")] + stop_processing: bool, + } + + #[derive(Deserialize, Serialize, Debug, PartialEq)] + #[serde(deny_unknown_fields)] + struct RCodeActionConfiguration { + #[serde(default, skip_serializing_if = "crate::is_default")] + name: String, + rcode: u8, + #[serde(default, skip_serializing_if = "crate::is_default")] + vars: ResponseConfig, + } + + #[derive(Deserialize, Serialize, Debug, PartialEq)] + #[serde(deny_unknown_fields)] + struct RemoteLogActionConfiguration { + #[serde(default, skip_serializing_if = "crate::is_default")] + name: String, + logger_name: String, + #[serde(default, skip_serializing_if = "crate::is_default")] + alter_function_name: String, + #[serde(default, skip_serializing_if = "crate::is_default")] + alter_function_code: String, + #[serde(default, skip_serializing_if = "crate::is_default")] + alter_function_file: String, + #[serde(default, skip_serializing_if = "crate::is_default")] + server_id: String, + #[serde(default, skip_serializing_if = "crate::is_default")] + ip_encrypt_key: String, + #[serde(default, skip_serializing_if = "crate::is_default")] + export_tags: Vec, + #[serde(default, skip_serializing_if = "crate::is_default")] + metas: Vec, + } + + #[derive(Deserialize, Serialize, Debug, PartialEq)] + #[serde(deny_unknown_fields)] + struct SetAdditionalProxyProtocolValueActionConfiguration { + #[serde(default, skip_serializing_if = "crate::is_default")] + name: String, + proxy_type: u8, + value: String, + } + + #[derive(Deserialize, Serialize, Debug, PartialEq)] + #[serde(deny_unknown_fields)] + struct SetDisableECSActionConfiguration { + #[serde(default, skip_serializing_if = "crate::is_default")] + name: String, + } + + #[derive(Deserialize, Serialize, Debug, PartialEq)] + #[serde(deny_unknown_fields)] + struct SetDisableValidationActionConfiguration { + #[serde(default, skip_serializing_if = "crate::is_default")] + name: String, + } + + #[derive(Deserialize, Serialize, Debug, PartialEq)] + #[serde(deny_unknown_fields)] + struct SetECSActionConfiguration { + #[serde(default, skip_serializing_if = "crate::is_default")] + name: String, + ipv4: String, + #[serde(default, skip_serializing_if = "crate::is_default")] + ipv6: String, + } + + #[derive(Deserialize, Serialize, Debug, PartialEq)] + #[serde(deny_unknown_fields)] + struct SetECSOverrideActionConfiguration { + #[serde(default, skip_serializing_if = "crate::is_default")] + name: String, + override_existing: bool, + } + + #[derive(Deserialize, Serialize, Debug, PartialEq)] + #[serde(deny_unknown_fields)] + struct SetECSPrefixLengthActionConfiguration { + #[serde(default, skip_serializing_if = "crate::is_default")] + name: String, + ipv4: u16, + ipv6: u16, + } + + #[derive(Deserialize, Serialize, Debug, PartialEq)] + #[serde(deny_unknown_fields)] + struct SetExtendedDNSErrorActionConfiguration { + #[serde(default, skip_serializing_if = "crate::is_default")] + name: String, + info_code: u16, + #[serde(default, skip_serializing_if = "crate::is_default")] + extra_text: String, + } + + #[derive(Deserialize, Serialize, Debug, PartialEq)] + #[serde(deny_unknown_fields)] + struct SetMacAddrActionConfiguration { + #[serde(default, skip_serializing_if = "crate::is_default")] + name: String, + code: u32, + } + + #[derive(Deserialize, Serialize, Debug, PartialEq)] + #[serde(deny_unknown_fields)] + struct SetMaxReturnedTTLActionConfiguration { + #[serde(default, skip_serializing_if = "crate::is_default")] + name: String, + max: u32, + } + + #[derive(Deserialize, Serialize, Debug, PartialEq)] + #[serde(deny_unknown_fields)] + struct SetNoRecurseActionConfiguration { + #[serde(default, skip_serializing_if = "crate::is_default")] + name: String, + } + + #[derive(Deserialize, Serialize, Debug, PartialEq)] + #[serde(deny_unknown_fields)] + struct SetProxyProtocolValuesActionConfiguration { + #[serde(default, skip_serializing_if = "crate::is_default")] + name: String, + #[serde(default, skip_serializing_if = "crate::is_default")] + values: Vec, + } + + #[derive(Deserialize, Serialize, Debug, PartialEq)] + #[serde(deny_unknown_fields)] + struct SetSkipCacheActionConfiguration { + #[serde(default, skip_serializing_if = "crate::is_default")] + name: String, + } + + #[derive(Deserialize, Serialize, Debug, PartialEq)] + #[serde(deny_unknown_fields)] + struct SetTagActionConfiguration { + #[serde(default, skip_serializing_if = "crate::is_default")] + name: String, + tag: String, + value: String, + } + + #[derive(Deserialize, Serialize, Debug, PartialEq)] + #[serde(deny_unknown_fields)] + struct SetTempFailureCacheTTLActionConfiguration { + #[serde(default, skip_serializing_if = "crate::is_default")] + name: String, + ttl: u32, + } + + #[derive(Deserialize, Serialize, Debug, PartialEq)] + #[serde(deny_unknown_fields)] + struct SNMPTrapActionConfiguration { + #[serde(default, skip_serializing_if = "crate::is_default")] + name: String, + #[serde(default, skip_serializing_if = "crate::is_default")] + reason: String, + } + + #[derive(Deserialize, Serialize, Debug, PartialEq)] + #[serde(deny_unknown_fields)] + struct SpoofActionConfiguration { + #[serde(default, skip_serializing_if = "crate::is_default")] + name: String, + ips: Vec, + #[serde(default, skip_serializing_if = "crate::is_default")] + vars: ResponseConfig, + } + + #[derive(Deserialize, Serialize, Debug, PartialEq)] + #[serde(deny_unknown_fields)] + struct SpoofCNAMEActionConfiguration { + #[serde(default, skip_serializing_if = "crate::is_default")] + name: String, + cname: String, + #[serde(default, skip_serializing_if = "crate::is_default")] + vars: ResponseConfig, + } + + #[derive(Deserialize, Serialize, Debug, PartialEq)] + #[serde(deny_unknown_fields)] + struct SpoofPacketActionConfiguration { + #[serde(default, skip_serializing_if = "crate::is_default")] + name: String, + response: String, + len: u32, + } + + #[derive(Deserialize, Serialize, Debug, PartialEq)] + #[serde(deny_unknown_fields)] + struct SpoofRawActionConfiguration { + #[serde(default, skip_serializing_if = "crate::is_default")] + name: String, + answers: Vec, + #[serde(default, skip_serializing_if = "crate::is_default")] + qtype_for_any: String, + #[serde(default, skip_serializing_if = "crate::is_default")] + vars: ResponseConfig, + } + + #[derive(Deserialize, Serialize, Debug, PartialEq)] + #[serde(deny_unknown_fields)] + struct SpoofSVCActionConfiguration { + #[serde(default, skip_serializing_if = "crate::is_default")] + name: String, + parameters: Vec, + #[serde(default, skip_serializing_if = "crate::is_default")] + vars: ResponseConfig, + } + + #[derive(Deserialize, Serialize, Debug, PartialEq)] + #[serde(deny_unknown_fields)] + struct TCActionConfiguration { + #[serde(default, skip_serializing_if = "crate::is_default")] + name: String, + } + + #[derive(Deserialize, Serialize, Debug, PartialEq)] + #[serde(deny_unknown_fields)] + struct TeeActionConfiguration { + #[serde(default, skip_serializing_if = "crate::is_default")] + name: String, + rca: String, + #[serde(default, skip_serializing_if = "crate::is_default")] + lca: String, + #[serde(default, skip_serializing_if = "crate::is_default")] + add_ecs: bool, + #[serde(default, skip_serializing_if = "crate::is_default")] + add_proxy_protocol: bool, + } + + #[derive(Deserialize, Serialize, Debug, PartialEq)] + #[serde(deny_unknown_fields)] + struct AllowResponseActionConfiguration { + #[serde(default, skip_serializing_if = "crate::is_default")] + name: String, + } + + #[derive(Deserialize, Serialize, Debug, PartialEq)] + #[serde(deny_unknown_fields)] + struct ClearRecordTypesResponseActionConfiguration { + #[serde(default, skip_serializing_if = "crate::is_default")] + name: String, + #[serde(default, skip_serializing_if = "crate::is_default")] + types: Vec, + } + + #[derive(Deserialize, Serialize, Debug, PartialEq)] + #[serde(deny_unknown_fields)] + struct DelayResponseActionConfiguration { + #[serde(default, skip_serializing_if = "crate::is_default")] + name: String, + msec: u32, + } + + #[derive(Deserialize, Serialize, Debug, PartialEq)] + #[serde(deny_unknown_fields)] + struct DnstapLogResponseActionConfiguration { + #[serde(default, skip_serializing_if = "crate::is_default")] + name: String, + identity: String, + logger_name: String, + #[serde(default, skip_serializing_if = "crate::is_default")] + alter_function_name: String, + #[serde(default, skip_serializing_if = "crate::is_default")] + alter_function_code: String, + #[serde(default, skip_serializing_if = "crate::is_default")] + alter_function_file: String, + } + + #[derive(Deserialize, Serialize, Debug, PartialEq)] + #[serde(deny_unknown_fields)] + struct DropResponseActionConfiguration { + #[serde(default, skip_serializing_if = "crate::is_default")] + name: String, + } + + #[derive(Deserialize, Serialize, Debug, PartialEq)] + #[serde(deny_unknown_fields)] + struct LimitTTLResponseActionConfiguration { + #[serde(default, skip_serializing_if = "crate::is_default")] + name: String, + min: u32, + max: u32, + #[serde(default, skip_serializing_if = "crate::is_default")] + types: Vec, + } + + #[derive(Deserialize, Serialize, Debug, PartialEq)] + #[serde(deny_unknown_fields)] + struct LogResponseActionConfiguration { + #[serde(default, skip_serializing_if = "crate::is_default")] + name: String, + #[serde(default, skip_serializing_if = "crate::is_default")] + file_name: String, + #[serde(default, skip_serializing_if = "crate::is_default")] + append: bool, + #[serde(default, skip_serializing_if = "crate::is_default")] + buffered: bool, + #[serde(default = "crate::Bool::::value", skip_serializing_if = "crate::if_true")] + verbose_only: bool, + #[serde(default, skip_serializing_if = "crate::is_default")] + include_timestamp: bool, + } + + #[derive(Deserialize, Serialize, Debug, PartialEq)] + #[serde(deny_unknown_fields)] + struct LuaResponseActionConfiguration { + #[serde(default, skip_serializing_if = "crate::is_default")] + name: String, + #[serde(default, skip_serializing_if = "crate::is_default")] + function_name: String, + #[serde(default, skip_serializing_if = "crate::is_default")] + function_code: String, + #[serde(default, skip_serializing_if = "crate::is_default")] + function_file: String, + } + + #[derive(Deserialize, Serialize, Debug, PartialEq)] + #[serde(deny_unknown_fields)] + struct LuaFFIResponseActionConfiguration { + #[serde(default, skip_serializing_if = "crate::is_default")] + name: String, + #[serde(default, skip_serializing_if = "crate::is_default")] + function_name: String, + #[serde(default, skip_serializing_if = "crate::is_default")] + function_code: String, + #[serde(default, skip_serializing_if = "crate::is_default")] + function_file: String, + } + + #[derive(Deserialize, Serialize, Debug, PartialEq)] + #[serde(deny_unknown_fields)] + struct LuaFFIPerThreadResponseActionConfiguration { + #[serde(default, skip_serializing_if = "crate::is_default")] + name: String, + code: String, + } + + #[derive(Deserialize, Serialize, Debug, PartialEq)] + #[serde(deny_unknown_fields)] + struct RemoteLogResponseActionConfiguration { + #[serde(default, skip_serializing_if = "crate::is_default")] + name: String, + logger_name: String, + #[serde(default, skip_serializing_if = "crate::is_default")] + alter_function_name: String, + #[serde(default, skip_serializing_if = "crate::is_default")] + alter_function_code: String, + #[serde(default, skip_serializing_if = "crate::is_default")] + alter_function_file: String, + #[serde(default, skip_serializing_if = "crate::is_default")] + server_id: String, + #[serde(default, skip_serializing_if = "crate::is_default")] + ip_encrypt_key: String, + #[serde(default, skip_serializing_if = "crate::is_default")] + include_cname: bool, + #[serde(default, skip_serializing_if = "crate::is_default")] + export_tags: Vec, + #[serde(default, skip_serializing_if = "crate::is_default")] + export_extended_errors_to_meta: String, + #[serde(default, skip_serializing_if = "crate::is_default")] + metas: Vec, + } + + #[derive(Deserialize, Serialize, Debug, PartialEq)] + #[serde(deny_unknown_fields)] + struct SetExtendedDNSErrorResponseActionConfiguration { + #[serde(default, skip_serializing_if = "crate::is_default")] + name: String, + info_code: u16, + #[serde(default, skip_serializing_if = "crate::is_default")] + extra_text: String, + } + + #[derive(Deserialize, Serialize, Debug, PartialEq)] + #[serde(deny_unknown_fields)] + struct SetMaxReturnedTTLResponseActionConfiguration { + #[serde(default, skip_serializing_if = "crate::is_default")] + name: String, + max: u32, + } + + #[derive(Deserialize, Serialize, Debug, PartialEq)] + #[serde(deny_unknown_fields)] + struct SetMaxTTLResponseActionConfiguration { + #[serde(default, skip_serializing_if = "crate::is_default")] + name: String, + max: u32, + } + + #[derive(Deserialize, Serialize, Debug, PartialEq)] + #[serde(deny_unknown_fields)] + struct SetMinTTLResponseActionConfiguration { + #[serde(default, skip_serializing_if = "crate::is_default")] + name: String, + min: u32, + } + + #[derive(Deserialize, Serialize, Debug, PartialEq)] + #[serde(deny_unknown_fields)] + struct SetReducedTTLResponseActionConfiguration { + #[serde(default, skip_serializing_if = "crate::is_default")] + name: String, + percentage: u8, + } + + #[derive(Deserialize, Serialize, Debug, PartialEq)] + #[serde(deny_unknown_fields)] + struct SetSkipCacheResponseActionConfiguration { + #[serde(default, skip_serializing_if = "crate::is_default")] + name: String, + } + + #[derive(Deserialize, Serialize, Debug, PartialEq)] + #[serde(deny_unknown_fields)] + struct SetTagResponseActionConfiguration { + #[serde(default, skip_serializing_if = "crate::is_default")] + name: String, + tag: String, + value: String, + } + + #[derive(Deserialize, Serialize, Debug, PartialEq)] + #[serde(deny_unknown_fields)] + struct SNMPTrapResponseActionConfiguration { + #[serde(default, skip_serializing_if = "crate::is_default")] + name: String, + #[serde(default, skip_serializing_if = "crate::is_default")] + reason: String, + } + + #[derive(Deserialize, Serialize, Debug, PartialEq)] + #[serde(deny_unknown_fields)] + struct TCResponseActionConfiguration { + #[serde(default, skip_serializing_if = "crate::is_default")] + name: String, + } + + #[derive(Deserialize, Serialize, Debug, PartialEq)] + #[serde(deny_unknown_fields)] + struct AllSelectorConfiguration { + #[serde(default, skip_serializing_if = "crate::is_default")] + name: String, + } + + #[derive(Default)] + struct AndSelectorConfiguration { + name: String, + selectors: Vec, + } + + #[derive(Deserialize, Serialize, Debug, PartialEq)] + #[serde(deny_unknown_fields)] + struct ByNameSelectorConfiguration { + #[serde(default, skip_serializing_if = "crate::is_default")] + name: String, + selector_name: String, + } + + #[derive(Deserialize, Serialize, Debug, PartialEq)] + #[serde(deny_unknown_fields)] + struct DNSSECSelectorConfiguration { + #[serde(default, skip_serializing_if = "crate::is_default")] + name: String, + } + + #[derive(Deserialize, Serialize, Debug, PartialEq)] + #[serde(deny_unknown_fields)] + struct DSTPortSelectorConfiguration { + #[serde(default, skip_serializing_if = "crate::is_default")] + name: String, + port: u16, + } + + #[derive(Deserialize, Serialize, Debug, PartialEq)] + #[serde(deny_unknown_fields)] + struct EDNSOptionSelectorConfiguration { + #[serde(default, skip_serializing_if = "crate::is_default")] + name: String, + option_code: u16, + } + + #[derive(Deserialize, Serialize, Debug, PartialEq)] + #[serde(deny_unknown_fields)] + struct EDNSVersionSelectorConfiguration { + #[serde(default, skip_serializing_if = "crate::is_default")] + name: String, + version: u8, + } + + #[derive(Deserialize, Serialize, Debug, PartialEq)] + #[serde(deny_unknown_fields)] + struct ERCodeSelectorConfiguration { + #[serde(default, skip_serializing_if = "crate::is_default")] + name: String, + rcode: u64, + } + + #[derive(Deserialize, Serialize, Debug, PartialEq)] + #[serde(deny_unknown_fields)] + struct HTTPHeaderSelectorConfiguration { + #[serde(default, skip_serializing_if = "crate::is_default")] + name: String, + header: String, + expression: String, + } + + #[derive(Deserialize, Serialize, Debug, PartialEq)] + #[serde(deny_unknown_fields)] + struct HTTPPathSelectorConfiguration { + #[serde(default, skip_serializing_if = "crate::is_default")] + name: String, + path: String, + } + + #[derive(Deserialize, Serialize, Debug, PartialEq)] + #[serde(deny_unknown_fields)] + struct HTTPPathRegexSelectorConfiguration { + #[serde(default, skip_serializing_if = "crate::is_default")] + name: String, + expression: String, + } + + #[derive(Deserialize, Serialize, Debug, PartialEq)] + #[serde(deny_unknown_fields)] + struct KeyValueStoreLookupSelectorConfiguration { + #[serde(default, skip_serializing_if = "crate::is_default")] + name: String, + kvs_name: String, + lookup_key_name: String, + } + + #[derive(Deserialize, Serialize, Debug, PartialEq)] + #[serde(deny_unknown_fields)] + struct KeyValueStoreRangeLookupSelectorConfiguration { + #[serde(default, skip_serializing_if = "crate::is_default")] + name: String, + kvs_name: String, + lookup_key_name: String, + } + + #[derive(Deserialize, Serialize, Debug, PartialEq)] + #[serde(deny_unknown_fields)] + struct LuaSelectorConfiguration { + #[serde(default, skip_serializing_if = "crate::is_default")] + name: String, + #[serde(default, skip_serializing_if = "crate::is_default")] + function_name: String, + #[serde(default, skip_serializing_if = "crate::is_default")] + function_code: String, + #[serde(default, skip_serializing_if = "crate::is_default")] + function_file: String, + } + + #[derive(Deserialize, Serialize, Debug, PartialEq)] + #[serde(deny_unknown_fields)] + struct LuaFFISelectorConfiguration { + #[serde(default, skip_serializing_if = "crate::is_default")] + name: String, + #[serde(default, skip_serializing_if = "crate::is_default")] + function_name: String, + #[serde(default, skip_serializing_if = "crate::is_default")] + function_code: String, + #[serde(default, skip_serializing_if = "crate::is_default")] + function_file: String, + } + + #[derive(Deserialize, Serialize, Debug, PartialEq)] + #[serde(deny_unknown_fields)] + struct LuaFFIPerThreadSelectorConfiguration { + #[serde(default, skip_serializing_if = "crate::is_default")] + name: String, + code: String, + } + + #[derive(Deserialize, Serialize, Debug, PartialEq)] + #[serde(deny_unknown_fields)] + struct MaxQPSSelectorConfiguration { + #[serde(default, skip_serializing_if = "crate::is_default")] + name: String, + qps: u32, + #[serde(default, skip_serializing_if = "crate::is_default")] + burst: u32, + } + + #[derive(Deserialize, Serialize, Debug, PartialEq)] + #[serde(deny_unknown_fields)] + struct MaxQPSIPSelectorConfiguration { + #[serde(default, skip_serializing_if = "crate::is_default")] + name: String, + qps: u32, + #[serde(default = "crate::U8::<32>::value", skip_serializing_if = "crate::U8::<32>::is_equal")] + ipv4_mask: u8, + #[serde(default = "crate::U8::<64>::value", skip_serializing_if = "crate::U8::<64>::is_equal")] + ipv6_mask: u8, + #[serde(default, skip_serializing_if = "crate::is_default")] + burst: u32, + #[serde(default = "crate::U32::<300>::value", skip_serializing_if = "crate::U32::<300>::is_equal")] + expiration: u32, + #[serde(default = "crate::U32::<60>::value", skip_serializing_if = "crate::U32::<60>::is_equal")] + cleanup_delay: u32, + #[serde(default = "crate::U32::<10>::value", skip_serializing_if = "crate::U32::<10>::is_equal")] + scan_fraction: u32, + #[serde(default = "crate::U32::<10>::value", skip_serializing_if = "crate::U32::<10>::is_equal")] + shards: u32, + } + + #[derive(Deserialize, Serialize, Debug, PartialEq)] + #[serde(deny_unknown_fields)] + struct NetmaskGroupSelectorConfiguration { + #[serde(default, skip_serializing_if = "crate::is_default")] + name: String, + #[serde(default, skip_serializing_if = "crate::is_default")] + netmask_group_name: String, + #[serde(default, skip_serializing_if = "crate::is_default")] + netmasks: Vec, + #[serde(default = "crate::Bool::::value", skip_serializing_if = "crate::if_true")] + source: bool, + #[serde(default, skip_serializing_if = "crate::is_default")] + quiet: bool, + } + + #[derive(Default)] + struct NotSelectorConfiguration { + name: String, + selector: SharedDNSSelector, + } + + #[derive(Deserialize, Serialize, Debug, PartialEq)] + #[serde(deny_unknown_fields)] + struct OpcodeSelectorConfiguration { + #[serde(default, skip_serializing_if = "crate::is_default")] + name: String, + code: u8, + } + + #[derive(Default)] + struct OrSelectorConfiguration { + name: String, + selectors: Vec, + } + + #[derive(Deserialize, Serialize, Debug, PartialEq)] + #[serde(deny_unknown_fields)] + struct PayloadSizeSelectorConfiguration { + #[serde(default, skip_serializing_if = "crate::is_default")] + name: String, + comparison: String, + size: u16, + } + + #[derive(Deserialize, Serialize, Debug, PartialEq)] + #[serde(deny_unknown_fields)] + struct PoolAvailableSelectorConfiguration { + #[serde(default, skip_serializing_if = "crate::is_default")] + name: String, + pool: String, + } + + #[derive(Deserialize, Serialize, Debug, PartialEq)] + #[serde(deny_unknown_fields)] + struct PoolOutstandingSelectorConfiguration { + #[serde(default, skip_serializing_if = "crate::is_default")] + name: String, + pool: String, + max_outstanding: u64, + } + + #[derive(Deserialize, Serialize, Debug, PartialEq)] + #[serde(deny_unknown_fields)] + struct ProbaSelectorConfiguration { + #[serde(default, skip_serializing_if = "crate::is_default")] + name: String, + probability: f64, + } + + #[derive(Deserialize, Serialize, Debug, PartialEq)] + #[serde(deny_unknown_fields)] + struct ProxyProtocolValueSelectorConfiguration { + #[serde(default, skip_serializing_if = "crate::is_default")] + name: String, + option_type: u8, + #[serde(default, skip_serializing_if = "crate::is_default")] + option_value: String, + } + + #[derive(Deserialize, Serialize, Debug, PartialEq)] + #[serde(deny_unknown_fields)] + struct QClassSelectorConfiguration { + #[serde(default, skip_serializing_if = "crate::is_default")] + name: String, + #[serde(default, skip_serializing_if = "crate::is_default")] + qclass: String, + #[serde(default, skip_serializing_if = "crate::is_default")] + numeric_value: u16, + } + + #[derive(Deserialize, Serialize, Debug, PartialEq)] + #[serde(deny_unknown_fields)] + struct QNameSelectorConfiguration { + #[serde(default, skip_serializing_if = "crate::is_default")] + name: String, + qname: String, + } + + #[derive(Deserialize, Serialize, Debug, PartialEq)] + #[serde(deny_unknown_fields)] + struct QNameLabelsCountSelectorConfiguration { + #[serde(default, skip_serializing_if = "crate::is_default")] + name: String, + min_labels_count: u16, + max_labels_count: u16, + } + + #[derive(Deserialize, Serialize, Debug, PartialEq)] + #[serde(deny_unknown_fields)] + struct QNameSetSelectorConfiguration { + #[serde(default, skip_serializing_if = "crate::is_default")] + name: String, + qnames: Vec, + } + + #[derive(Deserialize, Serialize, Debug, PartialEq)] + #[serde(deny_unknown_fields)] + struct QNameSuffixSelectorConfiguration { + #[serde(default, skip_serializing_if = "crate::is_default")] + name: String, + suffixes: Vec, + #[serde(default, skip_serializing_if = "crate::is_default")] + quiet: bool, + } + + #[derive(Deserialize, Serialize, Debug, PartialEq)] + #[serde(deny_unknown_fields)] + struct QNameWireLengthSelectorConfiguration { + #[serde(default, skip_serializing_if = "crate::is_default")] + name: String, + min: u16, + max: u16, + } + + #[derive(Deserialize, Serialize, Debug, PartialEq)] + #[serde(deny_unknown_fields)] + struct QTypeSelectorConfiguration { + #[serde(default, skip_serializing_if = "crate::is_default")] + name: String, + qtype: String, + #[serde(default, skip_serializing_if = "crate::is_default")] + numeric_value: u16, + } + + #[derive(Deserialize, Serialize, Debug, PartialEq)] + #[serde(deny_unknown_fields)] + struct RCodeSelectorConfiguration { + #[serde(default, skip_serializing_if = "crate::is_default")] + name: String, + rcode: u64, + } + + #[derive(Deserialize, Serialize, Debug, PartialEq)] + #[serde(deny_unknown_fields)] + struct RDSelectorConfiguration { + #[serde(default, skip_serializing_if = "crate::is_default")] + name: String, + } + + #[derive(Deserialize, Serialize, Debug, PartialEq)] + #[serde(deny_unknown_fields)] + struct RE2SelectorConfiguration { + #[serde(default, skip_serializing_if = "crate::is_default")] + name: String, + expression: String, + } + + #[derive(Deserialize, Serialize, Debug, PartialEq)] + #[serde(deny_unknown_fields)] + struct RecordsCountSelectorConfiguration { + #[serde(default, skip_serializing_if = "crate::is_default")] + name: String, + section: u8, + minimum: u16, + maximum: u16, + } + + #[derive(Deserialize, Serialize, Debug, PartialEq)] + #[serde(deny_unknown_fields)] + struct RecordsTypeCountSelectorConfiguration { + #[serde(default, skip_serializing_if = "crate::is_default")] + name: String, + section: u8, + record_type: u16, + minimum: u16, + maximum: u16, + } + + #[derive(Deserialize, Serialize, Debug, PartialEq)] + #[serde(deny_unknown_fields)] + struct RegexSelectorConfiguration { + #[serde(default, skip_serializing_if = "crate::is_default")] + name: String, + expression: String, + } + + #[derive(Deserialize, Serialize, Debug, PartialEq)] + #[serde(deny_unknown_fields)] + struct SNISelectorConfiguration { + #[serde(default, skip_serializing_if = "crate::is_default")] + name: String, + server_name: String, + } + + #[derive(Deserialize, Serialize, Debug, PartialEq)] + #[serde(deny_unknown_fields)] + struct TagSelectorConfiguration { + #[serde(default, skip_serializing_if = "crate::is_default")] + name: String, + tag: String, + #[serde(default, skip_serializing_if = "crate::is_default")] + value: String, + } + + #[derive(Deserialize, Serialize, Debug, PartialEq)] + #[serde(deny_unknown_fields)] + struct TCPSelectorConfiguration { + #[serde(default, skip_serializing_if = "crate::is_default")] + name: String, + tcp: bool, + } + + #[derive(Deserialize, Serialize, Debug, PartialEq)] + #[serde(deny_unknown_fields)] + struct TrailingDataSelectorConfiguration { + #[serde(default, skip_serializing_if = "crate::is_default")] + name: String, + } + + #[derive(Default)] + struct GlobalConfiguration { + acl: Vec, + backends: Vec, + binds: Vec, + cache_hit_response_rules: Vec, + cache_inserted_response_rules: Vec, + cache_miss_rules: Vec, + cache_settings: CacheSettingsConfiguration, + console: ConsoleConfiguration, + dynamic_rules: Vec, + dynamic_rules_settings: DynamicRulesSettingsConfiguration, + ebpf: EbpfConfiguration, + edns_client_subnet: EdnsClientSubnetConfiguration, + general: GeneralConfiguration, + key_value_stores: KeyValueStoresConfiguration, + load_balancing_policies: LoadBalancingPoliciesConfiguration, + logging: LoggingConfiguration, + metrics: MetricsConfiguration, + packet_caches: Vec, + pools: Vec, + proxy_protocol: ProxyProtocolConfiguration, + query_count: QueryCountConfiguration, + query_rules: Vec, + remote_logging: RemoteLoggingConfiguration, + response_rules: Vec, + ring_buffers: RingBuffersConfiguration, + security_polling: SecurityPollingConfiguration, + selectors: Vec, + self_answered_response_rules: Vec, + snmp: SnmpConfiguration, + tuning: TuningConfiguration, + webserver: WebserverConfiguration, + xfr_response_rules: Vec, + xsk: Vec, + } + + #[derive(Deserialize, Serialize, Debug, PartialEq)] + #[serde(deny_unknown_fields)] + struct MetricsConfiguration { + #[serde(default, skip_serializing_if = "crate::is_default")] + carbon: Vec, + } + + #[derive(Deserialize, Serialize, Debug, PartialEq)] + #[serde(deny_unknown_fields)] + struct CarbonConfiguration { + address: String, + #[serde(default, skip_serializing_if = "crate::is_default")] + name: String, + #[serde(default = "crate::U32::<30>::value", skip_serializing_if = "crate::U32::<30>::is_equal")] + interval: u32, + #[serde(rename = "namespace", default, skip_serializing_if = "crate::is_default")] + name_space: String, + #[serde(default, skip_serializing_if = "crate::is_default")] + instance: String, + } + + #[derive(Deserialize, Serialize, Debug, PartialEq)] + #[serde(deny_unknown_fields)] + struct RemoteLoggingConfiguration { + #[serde(default, skip_serializing_if = "crate::is_default")] + protobuf_loggers: Vec, + #[serde(default, skip_serializing_if = "crate::is_default")] + dnstap_loggers: Vec, + } + + #[derive(Deserialize, Serialize, Debug, PartialEq)] + #[serde(deny_unknown_fields)] + struct ProtobufLoggerConfiguration { + name: String, + address: String, + #[serde(default = "crate::U16::<2>::value", skip_serializing_if = "crate::U16::<2>::is_equal")] + timeout: u16, + #[serde(default = "crate::U64::<100>::value", skip_serializing_if = "crate::U64::<100>::is_equal")] + max_queued_entries: u64, + #[serde(default = "crate::U8::<1>::value", skip_serializing_if = "crate::U8::<1>::is_equal")] + reconnect_wait_time: u8, + } + + #[derive(Deserialize, Serialize, Debug, PartialEq)] + #[serde(deny_unknown_fields)] + struct DnstapLoggerConfiguration { + name: String, + transport: String, + address: String, + #[serde(default, skip_serializing_if = "crate::is_default")] + buffer_hint: u64, + #[serde(default, skip_serializing_if = "crate::is_default")] + flush_timeout: u64, + #[serde(default, skip_serializing_if = "crate::is_default")] + input_queue_size: u64, + #[serde(default, skip_serializing_if = "crate::is_default")] + output_queue_size: u64, + #[serde(default, skip_serializing_if = "crate::is_default")] + queue_notify_threshold: u64, + #[serde(default, skip_serializing_if = "crate::is_default")] + reopen_interval: u64, + } + + #[derive(Deserialize, Serialize, Debug, PartialEq)] + #[serde(deny_unknown_fields)] + struct ProtoBufMetaConfiguration { + key: String, + value: String, + } + + #[derive(Deserialize, Serialize, Debug, PartialEq)] + #[serde(deny_unknown_fields)] + struct LmdbKvStoreConfiguration { + name: String, + file_name: String, + database_name: String, + #[serde(default, skip_serializing_if = "crate::is_default")] + no_lock: bool, + } + + #[derive(Deserialize, Serialize, Debug, PartialEq)] + #[serde(deny_unknown_fields)] + struct CdbKvStoreConfiguration { + name: String, + file_name: String, + refresh_delay: u32, + } + + #[derive(Deserialize, Serialize, Debug, PartialEq)] + #[serde(deny_unknown_fields)] + struct KvsLookupKeySourceIpConfiguration { + name: String, + #[serde(default = "crate::U8::<32>::value", skip_serializing_if = "crate::U8::<32>::is_equal")] + v4_mask: u8, + #[serde(default = "crate::U8::<128>::value", skip_serializing_if = "crate::U8::<128>::is_equal")] + v6_mask: u8, + #[serde(default, skip_serializing_if = "crate::is_default")] + include_port: bool, + } + + #[derive(Deserialize, Serialize, Debug, PartialEq)] + #[serde(deny_unknown_fields)] + struct KvsLookupKeyQnameConfiguration { + name: String, + #[serde(default = "crate::Bool::::value", skip_serializing_if = "crate::if_true")] + wire_format: bool, + } + + #[derive(Deserialize, Serialize, Debug, PartialEq)] + #[serde(deny_unknown_fields)] + struct KvsLookupKeySuffixConfiguration { + name: String, + #[serde(default, skip_serializing_if = "crate::is_default")] + minimum_labels: u16, + #[serde(default = "crate::Bool::::value", skip_serializing_if = "crate::if_true")] + wire_format: bool, + } + + #[derive(Deserialize, Serialize, Debug, PartialEq)] + #[serde(deny_unknown_fields)] + struct KvsLookupKeyTagConfiguration { + name: String, + tag: String, + } + + #[derive(Deserialize, Serialize, Debug, PartialEq)] + #[serde(deny_unknown_fields)] + struct KvsLookupKeysConfiguration { + #[serde(default, skip_serializing_if = "crate::is_default")] + source_ip_keys: Vec, + #[serde(default, skip_serializing_if = "crate::is_default")] + qname_keys: Vec, + #[serde(default, skip_serializing_if = "crate::is_default")] + suffix_keys: Vec, + #[serde(default, skip_serializing_if = "crate::is_default")] + tag_keys: Vec, + } + + #[derive(Deserialize, Serialize, Debug, PartialEq)] + #[serde(deny_unknown_fields)] + struct KeyValueStoresConfiguration { + #[serde(default, skip_serializing_if = "crate::is_default")] + lmdb: Vec, + #[serde(default, skip_serializing_if = "crate::is_default")] + cdb: Vec, + #[serde(default, skip_serializing_if = "crate::is_default")] + lookup_keys: KvsLookupKeysConfiguration, + } + + #[derive(Deserialize, Serialize, Debug, PartialEq)] + #[serde(deny_unknown_fields)] + struct WebserverConfiguration { + #[serde(default, skip_serializing_if = "crate::is_default")] + listen_address: String, + #[serde(default, skip_serializing_if = "crate::is_default")] + password: String, + #[serde(default, skip_serializing_if = "crate::is_default")] + api_key: String, + #[serde(default = "crate::default_value_webserver_acl", skip_serializing_if = "crate::default_value_equal_webserver_acl")] + acl: Vec, + #[serde(default = "crate::Bool::::value", skip_serializing_if = "crate::if_true")] + api_requires_authentication: bool, + #[serde(default = "crate::Bool::::value", skip_serializing_if = "crate::if_true")] + stats_require_authentication: bool, + #[serde(default = "crate::Bool::::value", skip_serializing_if = "crate::if_true")] + dashboard_requires_authentication: bool, + #[serde(default = "crate::U32::<100>::value", skip_serializing_if = "crate::U32::<100>::is_equal")] + max_concurrent_connections: u32, + #[serde(default, skip_serializing_if = "crate::is_default")] + hash_plaintext_credentials: bool, + #[serde(default, skip_serializing_if = "crate::is_default")] + custom_headers: Vec, + #[serde(default, skip_serializing_if = "crate::is_default")] + api_configuration_directory: String, + #[serde(default, skip_serializing_if = "crate::is_default")] + api_read_write: bool, + } + + #[derive(Deserialize, Serialize, Debug, PartialEq)] + #[serde(deny_unknown_fields)] + struct ConsoleConfiguration { + #[serde(default, skip_serializing_if = "crate::is_default")] + listen_address: String, + #[serde(default, skip_serializing_if = "crate::is_default")] + key: String, + #[serde(default = "crate::default_value_console_acl", skip_serializing_if = "crate::default_value_equal_console_acl")] + acl: Vec, + #[serde(default = "crate::U32::<10000000>::value", skip_serializing_if = "crate::U32::<10000000>::is_equal")] + maximum_output_size: u32, + #[serde(default = "crate::Bool::::value", skip_serializing_if = "crate::if_true")] + log_connections: bool, + #[serde(default, skip_serializing_if = "crate::is_default")] + max_concurrent_connections: u64, + } + + #[derive(Deserialize, Serialize, Debug, PartialEq)] + #[serde(deny_unknown_fields)] + struct EbpfMapConfiguration { + #[serde(default, skip_serializing_if = "crate::is_default")] + max_entries: u32, + #[serde(default, skip_serializing_if = "crate::is_default")] + pinned_path: String, + } + + #[derive(Deserialize, Serialize, Debug, PartialEq)] + #[serde(deny_unknown_fields)] + struct EbpfConfiguration { + #[serde(default, skip_serializing_if = "crate::is_default")] + ipv4: EbpfMapConfiguration, + #[serde(default, skip_serializing_if = "crate::is_default")] + ipv6: EbpfMapConfiguration, + #[serde(default, skip_serializing_if = "crate::is_default")] + cidr_ipv4: EbpfMapConfiguration, + #[serde(default, skip_serializing_if = "crate::is_default")] + cidr_ipv6: EbpfMapConfiguration, + #[serde(default, skip_serializing_if = "crate::is_default")] + qnames: EbpfMapConfiguration, + #[serde(default, skip_serializing_if = "crate::is_default")] + external: bool, + } + + #[derive(Deserialize, Serialize, Debug, PartialEq)] + #[serde(deny_unknown_fields)] + struct EdnsClientSubnetConfiguration { + #[serde(default, skip_serializing_if = "crate::is_default")] + override_existing: bool, + #[serde(default = "crate::U8::<32>::value", skip_serializing_if = "crate::U8::<32>::is_equal")] + source_prefix_v4: u8, + #[serde(default = "crate::U8::<56>::value", skip_serializing_if = "crate::U8::<56>::is_equal")] + source_prefix_v6: u8, + } + + #[derive(Deserialize, Serialize, Debug, PartialEq)] + #[serde(deny_unknown_fields)] + struct DynamicRulesSettingsConfiguration { + #[serde(default = "crate::U64::<60>::value", skip_serializing_if = "crate::U64::<60>::is_equal")] + purge_interval: u64, + #[serde(default = "crate::default_value_dynamic_rules_settings_default_action", skip_serializing_if = "crate::default_value_equal_dynamic_rules_settings_default_action")] + default_action: String, + } + + #[derive(Deserialize, Serialize, Debug, PartialEq)] + #[serde(deny_unknown_fields)] + struct DynamicRuleConfiguration { + #[serde(rename = "type", )] + rule_type: String, + seconds: u32, + action_duration: u32, + comment: String, + #[serde(default, skip_serializing_if = "crate::is_default")] + rate: u32, + #[serde(default, skip_serializing_if = "crate::is_default")] + ratio: f64, + #[serde(default = "crate::default_value_dynamic_rule_action", skip_serializing_if = "crate::default_value_equal_dynamic_rule_action")] + action: String, + #[serde(default, skip_serializing_if = "crate::is_default")] + warning_rate: u32, + #[serde(default, skip_serializing_if = "crate::is_default")] + warning_ratio: f64, + #[serde(default, skip_serializing_if = "crate::is_default")] + tag_name: String, + #[serde(default = "crate::default_value_dynamic_rule_tag_value", skip_serializing_if = "crate::default_value_equal_dynamic_rule_tag_value")] + tag_value: String, + #[serde(default, skip_serializing_if = "crate::is_default")] + visitor_function_name: String, + #[serde(default, skip_serializing_if = "crate::is_default")] + visitor_function_code: String, + #[serde(default, skip_serializing_if = "crate::is_default")] + visitor_function_file: String, + #[serde(default, skip_serializing_if = "crate::is_default")] + rcode: String, + #[serde(default, skip_serializing_if = "crate::is_default")] + qtype: String, + #[serde(default, skip_serializing_if = "crate::is_default")] + minimum_number_of_responses: u32, + #[serde(default, skip_serializing_if = "crate::is_default")] + minimum_global_cache_hit_ratio: f64, + } + + #[derive(Deserialize, Serialize, Debug, PartialEq)] + #[serde(deny_unknown_fields)] + struct DynamicRulesConfiguration { + name: String, + #[serde(default = "crate::U8::<32>::value", skip_serializing_if = "crate::U8::<32>::is_equal")] + mask_ipv4: u8, + #[serde(default = "crate::U8::<64>::value", skip_serializing_if = "crate::U8::<64>::is_equal")] + mask_ipv6: u8, + #[serde(default, skip_serializing_if = "crate::is_default")] + mask_port: u8, + #[serde(default, skip_serializing_if = "crate::is_default")] + exclude_ranges: Vec, + #[serde(default, skip_serializing_if = "crate::is_default")] + include_ranges: Vec, + #[serde(default, skip_serializing_if = "crate::is_default")] + exclude_domains: Vec, + rules: Vec, + } + + #[derive(Deserialize, Serialize, Debug, PartialEq)] + #[serde(deny_unknown_fields)] + struct RingBuffersConfiguration { + #[serde(default = "crate::U64::<10000>::value", skip_serializing_if = "crate::U64::<10000>::is_equal")] + size: u64, + #[serde(default = "crate::U64::<10>::value", skip_serializing_if = "crate::U64::<10>::is_equal")] + shards: u64, + #[serde(default = "crate::U64::<5>::value", skip_serializing_if = "crate::U64::<5>::is_equal")] + lock_retries: u64, + #[serde(default = "crate::Bool::::value", skip_serializing_if = "crate::if_true")] + record_queries: bool, + #[serde(default = "crate::Bool::::value", skip_serializing_if = "crate::if_true")] + record_responses: bool, + } + + #[derive(Deserialize, Serialize, Debug, PartialEq)] + #[serde(deny_unknown_fields)] + struct IncomingTlsCertificateKeyPairConfiguration { + certificate: String, + #[serde(default, skip_serializing_if = "crate::is_default")] + key: String, + #[serde(default, skip_serializing_if = "crate::is_default")] + password: String, + } + + #[derive(Deserialize, Serialize, Debug, PartialEq)] + #[serde(deny_unknown_fields)] + struct IncomingTlsConfiguration { + #[serde(default = "crate::default_value_incoming_tls_provider", skip_serializing_if = "crate::default_value_equal_incoming_tls_provider")] + provider: String, + #[serde(default, skip_serializing_if = "crate::is_default")] + certificates: Vec, + #[serde(default, skip_serializing_if = "crate::is_default")] + ciphers: String, + #[serde(default, skip_serializing_if = "crate::is_default")] + ciphers_tls_13: String, + #[serde(default = "crate::default_value_incoming_tls_minimum_version", skip_serializing_if = "crate::default_value_equal_incoming_tls_minimum_version")] + minimum_version: String, + #[serde(default, skip_serializing_if = "crate::is_default")] + ticket_key_file: String, + #[serde(default = "crate::U32::<43200>::value", skip_serializing_if = "crate::U32::<43200>::is_equal")] + tickets_keys_rotation_delay: u32, + #[serde(default = "crate::U32::<5>::value", skip_serializing_if = "crate::U32::<5>::is_equal")] + number_of_tickets_keys: u32, + #[serde(default = "crate::Bool::::value", skip_serializing_if = "crate::if_true")] + prefer_server_ciphers: bool, + #[serde(default, skip_serializing_if = "crate::is_default")] + session_timeout: u32, + #[serde(default = "crate::Bool::::value", skip_serializing_if = "crate::if_true")] + session_tickets: bool, + #[serde(default = "crate::U32::<20480>::value", skip_serializing_if = "crate::U32::<20480>::is_equal")] + number_of_stored_sessions: u32, + #[serde(default, skip_serializing_if = "crate::is_default")] + ocsp_response_files: Vec, + #[serde(default, skip_serializing_if = "crate::is_default")] + key_log_file: String, + #[serde(default = "crate::Bool::::value", skip_serializing_if = "crate::if_true")] + release_buffers: bool, + #[serde(default, skip_serializing_if = "crate::is_default")] + enable_renegotiation: bool, + #[serde(default, skip_serializing_if = "crate::is_default")] + async_mode: bool, + #[serde(default, skip_serializing_if = "crate::is_default")] + ktls: bool, + #[serde(default = "crate::Bool::::value", skip_serializing_if = "crate::if_true")] + read_ahead: bool, + #[serde(default, skip_serializing_if = "crate::is_default")] + proxy_protocol_outside_tls: bool, + #[serde(default, skip_serializing_if = "crate::is_default")] + ignore_configuration_errors: bool, + } + + #[derive(Deserialize, Serialize, Debug, PartialEq)] + #[serde(deny_unknown_fields)] + struct OutgoingTlsConfiguration { + #[serde(default = "crate::default_value_outgoing_tls_provider", skip_serializing_if = "crate::default_value_equal_outgoing_tls_provider")] + provider: String, + #[serde(default, skip_serializing_if = "crate::is_default")] + subject_name: String, + #[serde(default, skip_serializing_if = "crate::is_default")] + subject_address: String, + #[serde(default = "crate::Bool::::value", skip_serializing_if = "crate::if_true")] + validate_certificate: bool, + #[serde(default, skip_serializing_if = "crate::is_default")] + ca_store: String, + #[serde(default, skip_serializing_if = "crate::is_default")] + ciphers: String, + #[serde(default, skip_serializing_if = "crate::is_default")] + ciphers_tls_13: String, + #[serde(default, skip_serializing_if = "crate::is_default")] + key_log_file: String, + #[serde(default = "crate::Bool::::value", skip_serializing_if = "crate::if_true")] + release_buffers: bool, + #[serde(default, skip_serializing_if = "crate::is_default")] + enable_renegotiation: bool, + #[serde(default, skip_serializing_if = "crate::is_default")] + ktls: bool, + } + + #[derive(Deserialize, Serialize, Debug, PartialEq)] + #[serde(deny_unknown_fields)] + struct HttpCustomResponseHeaderConfiguration { + key: String, + value: String, + } + + #[derive(Deserialize, Serialize, Debug, PartialEq)] + #[serde(deny_unknown_fields)] + struct HttpResponsesMapConfiguration { + expression: String, + status: u16, + content: String, + #[serde(default, skip_serializing_if = "crate::is_default")] + headers: Vec, + } + + #[derive(Deserialize, Serialize, Debug, PartialEq)] + #[serde(deny_unknown_fields)] + struct IncomingDohConfiguration { + #[serde(default = "crate::default_value_incoming_doh_provider", skip_serializing_if = "crate::default_value_equal_incoming_doh_provider")] + provider: String, + #[serde(default = "crate::default_value_incoming_doh_paths", skip_serializing_if = "crate::default_value_equal_incoming_doh_paths")] + paths: Vec, + #[serde(default = "crate::U64::<30>::value", skip_serializing_if = "crate::U64::<30>::is_equal")] + idle_timeout: u64, + #[serde(default, skip_serializing_if = "crate::is_default")] + server_tokens: String, + #[serde(default = "crate::Bool::::value", skip_serializing_if = "crate::if_true")] + send_cache_control_headers: bool, + #[serde(default, skip_serializing_if = "crate::is_default")] + keep_incoming_headers: bool, + #[serde(default, skip_serializing_if = "crate::is_default")] + trust_forwarded_for_header: bool, + #[serde(default = "crate::Bool::::value", skip_serializing_if = "crate::if_true")] + early_acl_drop: bool, + #[serde(default = "crate::Bool::::value", skip_serializing_if = "crate::if_true")] + exact_path_matching: bool, + #[serde(default = "crate::U32::<1048576>::value", skip_serializing_if = "crate::U32::<1048576>::is_equal")] + internal_pipe_buffer_size: u32, + #[serde(default, skip_serializing_if = "crate::is_default")] + custom_response_headers: Vec, + #[serde(default, skip_serializing_if = "crate::is_default")] + responses_map: Vec, + } + + #[derive(Deserialize, Serialize, Debug, PartialEq)] + #[serde(deny_unknown_fields)] + struct IncomingDoqConfiguration { + #[serde(default = "crate::U64::<65535>::value", skip_serializing_if = "crate::U64::<65535>::is_equal")] + max_concurrent_queries_per_connection: u64, + } + + #[derive(Deserialize, Serialize, Debug, PartialEq)] + #[serde(deny_unknown_fields)] + struct IncomingQuicConfiguration { + #[serde(default = "crate::U64::<5>::value", skip_serializing_if = "crate::U64::<5>::is_equal")] + idle_timeout: u64, + #[serde(default = "crate::default_value_incoming_quic_congestion_control_algorithm", skip_serializing_if = "crate::default_value_equal_incoming_quic_congestion_control_algorithm")] + congestion_control_algorithm: String, + #[serde(default = "crate::U32::<1048576>::value", skip_serializing_if = "crate::U32::<1048576>::is_equal")] + internal_pipe_buffer_size: u32, + } + + #[derive(Deserialize, Serialize, Debug, PartialEq)] + #[serde(deny_unknown_fields)] + struct IncomingDnscryptCertificateKeyPairConfiguration { + certificate: String, + key: String, + } + + #[derive(Deserialize, Serialize, Debug, PartialEq)] + #[serde(deny_unknown_fields)] + struct IncomingDnscryptConfiguration { + #[serde(default, skip_serializing_if = "crate::is_default")] + provider_name: String, + #[serde(default, skip_serializing_if = "crate::is_default")] + certificates: Vec, + } + + #[derive(Deserialize, Serialize, Debug, PartialEq)] + #[serde(deny_unknown_fields)] + struct OutgoingDohConfiguration { + #[serde(default = "crate::default_value_outgoing_doh_path", skip_serializing_if = "crate::default_value_equal_outgoing_doh_path")] + path: String, + #[serde(default, skip_serializing_if = "crate::is_default")] + add_x_forwarded_headers: bool, + } + + #[derive(Deserialize, Serialize, Debug, PartialEq)] + #[serde(deny_unknown_fields)] + struct IncomingTcpConfiguration { + #[serde(default, skip_serializing_if = "crate::is_default")] + max_in_flight_queries: u32, + #[serde(default, skip_serializing_if = "crate::is_default")] + listen_queue_size: u32, + #[serde(default, skip_serializing_if = "crate::is_default")] + fast_open_queue_size: u32, + #[serde(default, skip_serializing_if = "crate::is_default")] + max_concurrent_connections: u32, + } + + #[derive(Deserialize, Serialize, Debug, PartialEq)] + #[serde(deny_unknown_fields)] + struct BindConfiguration { + listen_address: String, + #[serde(default, skip_serializing_if = "crate::is_default")] + reuseport: bool, + #[serde(default = "crate::default_value_bind_protocol", skip_serializing_if = "crate::default_value_equal_bind_protocol")] + protocol: String, + #[serde(default = "crate::U32::<1>::value", skip_serializing_if = "crate::U32::<1>::is_equal")] + threads: u32, + #[serde(default, skip_serializing_if = "crate::is_default")] + interface: String, + #[serde(default, skip_serializing_if = "crate::is_default")] + cpus: String, + #[serde(default, skip_serializing_if = "crate::is_default")] + enable_proxy_protocol: bool, + #[serde(default, skip_serializing_if = "crate::is_default")] + tcp: IncomingTcpConfiguration, + #[serde(default, skip_serializing_if = "crate::is_default")] + tls: IncomingTlsConfiguration, + #[serde(default, skip_serializing_if = "crate::is_default")] + doh: IncomingDohConfiguration, + #[serde(default, skip_serializing_if = "crate::is_default")] + doq: IncomingDoqConfiguration, + #[serde(default, skip_serializing_if = "crate::is_default")] + quic: IncomingQuicConfiguration, + #[serde(default, skip_serializing_if = "crate::is_default")] + dnscrypt: IncomingDnscryptConfiguration, + #[serde(default, skip_serializing_if = "crate::is_default")] + additional_addresses: Vec, + #[serde(default, skip_serializing_if = "crate::is_default")] + xsk: String, + } + + #[derive(Deserialize, Serialize, Debug, PartialEq)] + #[serde(deny_unknown_fields)] + struct OutgoingTcpConfiguration { + #[serde(default = "crate::U16::<5>::value", skip_serializing_if = "crate::U16::<5>::is_equal")] + retries: u16, + #[serde(default = "crate::U16::<5>::value", skip_serializing_if = "crate::U16::<5>::is_equal")] + connect_timeout: u16, + #[serde(default = "crate::U16::<30>::value", skip_serializing_if = "crate::U16::<30>::is_equal")] + send_timeout: u16, + #[serde(default = "crate::U16::<30>::value", skip_serializing_if = "crate::U16::<30>::is_equal")] + receive_timeout: u16, + #[serde(default, skip_serializing_if = "crate::is_default")] + fast_open: bool, + } + + #[derive(Deserialize, Serialize, Debug, PartialEq)] + #[serde(deny_unknown_fields)] + struct ProxyProtocolValueConfiguration { + key: u8, + value: String, + } + + #[derive(Deserialize, Serialize, Debug, PartialEq)] + #[serde(deny_unknown_fields)] + struct LazyHealthCheckConfiguration { + #[serde(default = "crate::U16::<30>::value", skip_serializing_if = "crate::U16::<30>::is_equal")] + interval: u16, + #[serde(default = "crate::U16::<1>::value", skip_serializing_if = "crate::U16::<1>::is_equal")] + min_sample_count: u16, + #[serde(default = "crate::default_value_lazy_health_check_mode", skip_serializing_if = "crate::default_value_equal_lazy_health_check_mode")] + mode: String, + #[serde(default = "crate::U16::<100>::value", skip_serializing_if = "crate::U16::<100>::is_equal")] + sample_size: u16, + #[serde(default = "crate::U16::<20>::value", skip_serializing_if = "crate::U16::<20>::is_equal")] + threshold: u16, + #[serde(default, skip_serializing_if = "crate::is_default")] + use_exponential_back_off: bool, + #[serde(default = "crate::U16::<3600>::value", skip_serializing_if = "crate::U16::<3600>::is_equal")] + max_back_off: u16, + } + + #[derive(Deserialize, Serialize, Debug, PartialEq)] + #[serde(deny_unknown_fields)] + struct HealthCheckConfiguration { + #[serde(default = "crate::default_value_health_check_mode", skip_serializing_if = "crate::default_value_equal_health_check_mode")] + mode: String, + #[serde(default, skip_serializing_if = "crate::is_default")] + qname: String, + #[serde(default = "crate::default_value_health_check_qclass", skip_serializing_if = "crate::default_value_equal_health_check_qclass")] + qclass: String, + #[serde(default = "crate::default_value_health_check_qtype", skip_serializing_if = "crate::default_value_equal_health_check_qtype")] + qtype: String, + #[serde(default, skip_serializing_if = "crate::is_default")] + function: String, + #[serde(default, skip_serializing_if = "crate::is_default")] + lua: String, + #[serde(default, skip_serializing_if = "crate::is_default")] + lua_file: String, + #[serde(default = "crate::U16::<1000>::value", skip_serializing_if = "crate::U16::<1000>::is_equal")] + timeout: u16, + #[serde(default, skip_serializing_if = "crate::is_default")] + set_cd: bool, + #[serde(default = "crate::U8::<1>::value", skip_serializing_if = "crate::U8::<1>::is_equal")] + max_failures: u8, + #[serde(default = "crate::U8::<1>::value", skip_serializing_if = "crate::U8::<1>::is_equal")] + rise: u8, + #[serde(default = "crate::U32::<1>::value", skip_serializing_if = "crate::U32::<1>::is_equal")] + interval: u32, + #[serde(default, skip_serializing_if = "crate::is_default")] + must_resolve: bool, + #[serde(default, skip_serializing_if = "crate::is_default")] + use_tcp: bool, + #[serde(default, skip_serializing_if = "crate::is_default")] + lazy: LazyHealthCheckConfiguration, + } + + #[derive(Deserialize, Serialize, Debug, PartialEq)] + #[serde(deny_unknown_fields)] + struct OutgoingAutoUpgradeConfiguration { + #[serde(default, skip_serializing_if = "crate::is_default")] + enabled: bool, + #[serde(default = "crate::U32::<3600>::value", skip_serializing_if = "crate::U32::<3600>::is_equal")] + interval: u32, + #[serde(default, skip_serializing_if = "crate::is_default")] + keep: bool, + #[serde(default, skip_serializing_if = "crate::is_default")] + pool: String, + #[serde(default = "crate::U8::<7>::value", skip_serializing_if = "crate::U8::<7>::is_equal")] + doh_key: u8, + #[serde(default, skip_serializing_if = "crate::is_default")] + use_lazy_health_check: bool, + } + + #[derive(Deserialize, Serialize, Debug, PartialEq)] + #[serde(deny_unknown_fields)] + struct BackendConfiguration { + address: String, + #[serde(default, skip_serializing_if = "crate::is_default")] + id: String, + #[serde(default, skip_serializing_if = "crate::is_default")] + name: String, + protocol: String, + #[serde(default, skip_serializing_if = "crate::is_default")] + tls: OutgoingTlsConfiguration, + #[serde(default, skip_serializing_if = "crate::is_default")] + doh: OutgoingDohConfiguration, + #[serde(default, skip_serializing_if = "crate::is_default")] + use_client_subnet: bool, + #[serde(default, skip_serializing_if = "crate::is_default")] + use_proxy_protocol: bool, + #[serde(default, skip_serializing_if = "crate::is_default")] + queries_per_second: u32, + #[serde(default = "crate::U32::<1>::value", skip_serializing_if = "crate::U32::<1>::is_equal")] + order: u32, + #[serde(default = "crate::U32::<1>::value", skip_serializing_if = "crate::U32::<1>::is_equal")] + weight: u32, + #[serde(default, skip_serializing_if = "crate::is_default")] + pools: Vec, + #[serde(default, skip_serializing_if = "crate::is_default")] + tcp: OutgoingTcpConfiguration, + #[serde(default = "crate::Bool::::value", skip_serializing_if = "crate::if_true")] + ip_bind_addr_no_port: bool, + #[serde(default, skip_serializing_if = "crate::is_default")] + health_checks: HealthCheckConfiguration, + #[serde(default, skip_serializing_if = "crate::is_default")] + source: String, + #[serde(default = "crate::U32::<1>::value", skip_serializing_if = "crate::U32::<1>::is_equal")] + sockets: u32, + #[serde(default, skip_serializing_if = "crate::is_default")] + disable_zero_scope: bool, + #[serde(default, skip_serializing_if = "crate::is_default")] + reconnect_on_up: bool, + #[serde(default = "crate::U32::<1>::value", skip_serializing_if = "crate::U32::<1>::is_equal")] + max_in_flight: u32, + #[serde(default, skip_serializing_if = "crate::is_default")] + tcp_only: bool, + #[serde(default, skip_serializing_if = "crate::is_default")] + auto_upgrade: OutgoingAutoUpgradeConfiguration, + #[serde(default, skip_serializing_if = "crate::is_default")] + max_concurrent_tcp_connections: u32, + #[serde(default, skip_serializing_if = "crate::is_default")] + proxy_protocol_advertise_tls: bool, + #[serde(default, skip_serializing_if = "crate::is_default")] + mac_address: String, + #[serde(default, skip_serializing_if = "crate::is_default")] + cpus: String, + #[serde(default, skip_serializing_if = "crate::is_default")] + xsk: String, + } + + #[derive(Deserialize, Serialize, Debug, PartialEq)] + #[serde(deny_unknown_fields)] + struct TuningConfiguration { + #[serde(default, skip_serializing_if = "crate::is_default")] + doh: DohTuningConfiguration, + #[serde(default, skip_serializing_if = "crate::is_default")] + tcp: TcpTuningConfiguration, + #[serde(default, skip_serializing_if = "crate::is_default")] + tls: TlsTuningConfiguration, + #[serde(default, skip_serializing_if = "crate::is_default")] + udp: UdpTuningConfiguration, + } + + #[derive(Deserialize, Serialize, Debug, PartialEq)] + #[serde(deny_unknown_fields)] + struct TcpTuningConfiguration { + #[serde(default = "crate::U32::<10>::value", skip_serializing_if = "crate::U32::<10>::is_equal")] + worker_threads: u32, + #[serde(default = "crate::U32::<2>::value", skip_serializing_if = "crate::U32::<2>::is_equal")] + receive_timeout: u32, + #[serde(default = "crate::U32::<2>::value", skip_serializing_if = "crate::U32::<2>::is_equal")] + send_timeout: u32, + #[serde(default, skip_serializing_if = "crate::is_default")] + max_queries_per_connection: u64, + #[serde(default, skip_serializing_if = "crate::is_default")] + max_connection_duration: u64, + #[serde(default = "crate::U64::<10000>::value", skip_serializing_if = "crate::U64::<10000>::is_equal")] + max_queued_connections: u64, + #[serde(default = "crate::U32::<1048576>::value", skip_serializing_if = "crate::U32::<1048576>::is_equal")] + internal_pipe_buffer_size: u32, + #[serde(default = "crate::U64::<300>::value", skip_serializing_if = "crate::U64::<300>::is_equal")] + outgoing_max_idle_time: u64, + #[serde(default = "crate::U64::<60>::value", skip_serializing_if = "crate::U64::<60>::is_equal")] + outgoing_cleanup_interval: u64, + #[serde(default = "crate::U64::<10>::value", skip_serializing_if = "crate::U64::<10>::is_equal")] + outgoing_max_idle_connection_per_backend: u64, + #[serde(default, skip_serializing_if = "crate::is_default")] + max_connections_per_client: u32, + #[serde(default, skip_serializing_if = "crate::is_default")] + fast_open_key: String, + } + + #[derive(Deserialize, Serialize, Debug, PartialEq)] + #[serde(deny_unknown_fields)] + struct UdpTuningConfiguration { + #[serde(default = "crate::U32::<1>::value", skip_serializing_if = "crate::U32::<1>::is_equal")] + messages_per_round: u32, + #[serde(default, skip_serializing_if = "crate::is_default")] + send_buffer_size: u32, + #[serde(default, skip_serializing_if = "crate::is_default")] + receive_buffer_size: u32, + #[serde(default = "crate::U32::<65535>::value", skip_serializing_if = "crate::U32::<65535>::is_equal")] + max_outstanding_per_backend: u32, + #[serde(default = "crate::U8::<2>::value", skip_serializing_if = "crate::U8::<2>::is_equal")] + timeout: u8, + #[serde(default, skip_serializing_if = "crate::is_default")] + randomize_outgoing_sockets_to_backend: bool, + #[serde(default, skip_serializing_if = "crate::is_default")] + randomize_ids_to_backend: bool, + } + + #[derive(Deserialize, Serialize, Debug, PartialEq)] + #[serde(deny_unknown_fields)] + struct TlsEngineConfiguration { + name: String, + #[serde(default, skip_serializing_if = "crate::is_default")] + default_string: String, + } + + #[derive(Deserialize, Serialize, Debug, PartialEq)] + #[serde(deny_unknown_fields)] + struct TlsTuningConfiguration { + #[serde(default = "crate::U16::<60>::value", skip_serializing_if = "crate::U16::<60>::is_equal")] + outgoing_tickets_cache_cleanup_delay: u16, + #[serde(default = "crate::U16::<600>::value", skip_serializing_if = "crate::U16::<600>::is_equal")] + outgoing_tickets_cache_validity: u16, + #[serde(default = "crate::U16::<20>::value", skip_serializing_if = "crate::U16::<20>::is_equal")] + max_outgoing_tickets_per_backend: u16, + #[serde(default, skip_serializing_if = "crate::is_default")] + providers: Vec, + #[serde(default, skip_serializing_if = "crate::is_default")] + engines: Vec, + } + + #[derive(Deserialize, Serialize, Debug, PartialEq)] + #[serde(deny_unknown_fields)] + struct DohTuningConfiguration { + #[serde(default = "crate::U32::<10>::value", skip_serializing_if = "crate::U32::<10>::is_equal")] + outgoing_worker_threads: u32, + #[serde(default = "crate::U64::<300>::value", skip_serializing_if = "crate::U64::<300>::is_equal")] + outgoing_max_idle_time: u64, + #[serde(default = "crate::U64::<60>::value", skip_serializing_if = "crate::U64::<60>::is_equal")] + outgoing_cleanup_interval: u64, + #[serde(default = "crate::U64::<10>::value", skip_serializing_if = "crate::U64::<10>::is_equal")] + outgoing_max_idle_connection_per_backend: u64, + } + + #[derive(Deserialize, Serialize, Debug, PartialEq)] + #[serde(deny_unknown_fields)] + struct CacheSettingsConfiguration { + #[serde(default, skip_serializing_if = "crate::is_default")] + stale_entries_ttl: u32, + #[serde(default = "crate::U16::<60>::value", skip_serializing_if = "crate::U16::<60>::is_equal")] + cleaning_delay: u16, + #[serde(default = "crate::U16::<100>::value", skip_serializing_if = "crate::U16::<100>::is_equal")] + cleaning_percentage: u16, + } + + #[derive(Deserialize, Serialize, Debug, PartialEq)] + #[serde(deny_unknown_fields)] + struct SecurityPollingConfiguration { + #[serde(default = "crate::U32::<3600>::value", skip_serializing_if = "crate::U32::<3600>::is_equal")] + polling_interval: u32, + #[serde(default = "crate::default_value_security_polling_suffix", skip_serializing_if = "crate::default_value_equal_security_polling_suffix")] + suffix: String, + } + + #[derive(Deserialize, Serialize, Debug, PartialEq)] + #[serde(deny_unknown_fields)] + struct StructuredLoggingConfiguration { + #[serde(default, skip_serializing_if = "crate::is_default")] + enabled: bool, + #[serde(default = "crate::default_value_structured_logging_level_prefix", skip_serializing_if = "crate::default_value_equal_structured_logging_level_prefix")] + level_prefix: String, + #[serde(default = "crate::default_value_structured_logging_time_format", skip_serializing_if = "crate::default_value_equal_structured_logging_time_format")] + time_format: String, + } + + #[derive(Deserialize, Serialize, Debug, PartialEq)] + #[serde(deny_unknown_fields)] + struct LoggingConfiguration { + #[serde(default, skip_serializing_if = "crate::is_default")] + verbose: bool, + #[serde(default, skip_serializing_if = "crate::is_default")] + verbose_health_checks: bool, + #[serde(default, skip_serializing_if = "crate::is_default")] + verbose_log_destination: String, + #[serde(default, skip_serializing_if = "crate::is_default")] + syslog_facility: String, + #[serde(default, skip_serializing_if = "crate::is_default")] + structured: StructuredLoggingConfiguration, + } + + #[derive(Deserialize, Serialize, Debug, PartialEq)] + #[serde(deny_unknown_fields)] + struct GeneralConfiguration { + #[serde(default = "crate::U16::<1232>::value", skip_serializing_if = "crate::U16::<1232>::is_equal")] + edns_udp_payload_size_self_generated_answers: u16, + #[serde(default = "crate::Bool::::value", skip_serializing_if = "crate::if_true")] + add_edns_to_self_generated_answers: bool, + #[serde(default, skip_serializing_if = "crate::is_default")] + truncate_tc_answers: bool, + #[serde(default, skip_serializing_if = "crate::is_default")] + fixup_case: bool, + #[serde(default, skip_serializing_if = "crate::is_default")] + allow_empty_responses: bool, + #[serde(default, skip_serializing_if = "crate::is_default")] + drop_empty_queries: bool, + #[serde(default, skip_serializing_if = "crate::is_default")] + capabilities_to_retain: Vec, + } + + #[derive(Deserialize, Serialize, Debug, PartialEq)] + #[serde(deny_unknown_fields)] + struct PacketCacheConfiguration { + name: String, + size: u64, + #[serde(default = "crate::Bool::::value", skip_serializing_if = "crate::if_true")] + deferrable_insert_lock: bool, + #[serde(default, skip_serializing_if = "crate::is_default")] + dont_age: bool, + #[serde(default, skip_serializing_if = "crate::is_default")] + keep_stale_data: bool, + #[serde(default = "crate::U32::<3600>::value", skip_serializing_if = "crate::U32::<3600>::is_equal")] + max_negative_ttl: u32, + #[serde(default = "crate::U32::<86400>::value", skip_serializing_if = "crate::U32::<86400>::is_equal")] + max_ttl: u32, + #[serde(default, skip_serializing_if = "crate::is_default")] + min_ttl: u32, + #[serde(default = "crate::U32::<20>::value", skip_serializing_if = "crate::U32::<20>::is_equal")] + shards: u32, + #[serde(default, skip_serializing_if = "crate::is_default")] + parse_ecs: bool, + #[serde(default = "crate::U32::<60>::value", skip_serializing_if = "crate::U32::<60>::is_equal")] + stale_ttl: u32, + #[serde(default = "crate::U32::<60>::value", skip_serializing_if = "crate::U32::<60>::is_equal")] + temporary_failure_ttl: u32, + #[serde(default, skip_serializing_if = "crate::is_default")] + cookie_hashing: bool, + #[serde(default = "crate::U32::<4096>::value", skip_serializing_if = "crate::U32::<4096>::is_equal")] + maximum_entry_size: u32, + #[serde(default, skip_serializing_if = "crate::is_default")] + options_to_skip: Vec, + } + + #[derive(Deserialize, Serialize, Debug, PartialEq)] + #[serde(deny_unknown_fields)] + struct ProxyProtocolConfiguration { + #[serde(default, skip_serializing_if = "crate::is_default")] + acl: Vec, + #[serde(default = "crate::U32::<512>::value", skip_serializing_if = "crate::U32::<512>::is_equal")] + maximum_payload_size: u32, + #[serde(default, skip_serializing_if = "crate::is_default")] + apply_acl_to_proxied_clients: bool, + } + + #[derive(Deserialize, Serialize, Debug, PartialEq)] + #[serde(deny_unknown_fields)] + struct SnmpConfiguration { + #[serde(default, skip_serializing_if = "crate::is_default")] + enabled: bool, + #[serde(default, skip_serializing_if = "crate::is_default")] + traps_enabled: bool, + #[serde(default, skip_serializing_if = "crate::is_default")] + daemon_socket: String, + } + + #[derive(Deserialize, Serialize, Debug, PartialEq)] + #[serde(deny_unknown_fields)] + struct QueryCountConfiguration { + #[serde(default, skip_serializing_if = "crate::is_default")] + enabled: bool, + #[serde(default, skip_serializing_if = "crate::is_default")] + filter_function_name: String, + #[serde(default, skip_serializing_if = "crate::is_default")] + filter_function_code: String, + #[serde(default, skip_serializing_if = "crate::is_default")] + filter_function_file: String, + } + + #[derive(Deserialize, Serialize, Debug, PartialEq)] + #[serde(deny_unknown_fields)] + struct PoolConfiguration { + name: String, + #[serde(default, skip_serializing_if = "crate::is_default")] + packet_cache: String, + #[serde(default, skip_serializing_if = "crate::is_default")] + policy: String, + } + + #[derive(Deserialize, Serialize, Debug, PartialEq)] + #[serde(deny_unknown_fields)] + struct CustomLoadBalancingPolicyConfiguration { + name: String, + #[serde(default, skip_serializing_if = "crate::is_default")] + function_name: String, + #[serde(default, skip_serializing_if = "crate::is_default")] + function_code: String, + #[serde(default, skip_serializing_if = "crate::is_default")] + function_file: String, + #[serde(default, skip_serializing_if = "crate::is_default")] + ffi: bool, + #[serde(default, skip_serializing_if = "crate::is_default")] + per_thread: bool, + } + + #[derive(Deserialize, Serialize, Debug, PartialEq)] + #[serde(deny_unknown_fields)] + struct LoadBalancingPoliciesConfiguration { + #[serde(default = "crate::default_value_load_balancing_policies_default_policy", skip_serializing_if = "crate::default_value_equal_load_balancing_policies_default_policy")] + default_policy: String, + #[serde(default, skip_serializing_if = "crate::is_default")] + servfail_on_no_server: bool, + #[serde(default, skip_serializing_if = "crate::is_default")] + round_robin_servfail_on_no_server: bool, + #[serde(default, skip_serializing_if = "crate::is_default")] + weighted_balancing_factor: f64, + #[serde(default, skip_serializing_if = "crate::is_default")] + consistent_hashing_balancing_factor: f64, + #[serde(default, skip_serializing_if = "crate::is_default")] + custom_policies: Vec, + #[serde(default, skip_serializing_if = "crate::is_default")] + hash_perturbation: u32, + } + + struct QueryRuleConfiguration { + name: String, + uuid: String, + selector: SharedDNSSelector, + action: SharedDNSAction, + } + + struct ResponseRuleConfiguration { + name: String, + uuid: String, + selector: SharedDNSSelector, + action: SharedDNSResponseAction, + } + + #[derive(Deserialize, Serialize, Debug, PartialEq)] + #[serde(deny_unknown_fields)] + struct XskConfiguration { + name: String, + interface: String, + queues: u16, + #[serde(default = "crate::U32::<65536>::value", skip_serializing_if = "crate::U32::<65536>::is_equal")] + frames: u32, + #[serde(default = "crate::default_value_xsk_map_path", skip_serializing_if = "crate::default_value_equal_xsk_map_path")] + map_path: String, + } + + + /* + * Functions callable from Rust (actions and selectors) + */ + unsafe extern "C++" { + fn getAllowAction(config: &AllowActionConfiguration) -> SharedPtr; + fn getContinueAction(config: &ContinueActionConfiguration) -> SharedPtr; + fn getDelayAction(config: &DelayActionConfiguration) -> SharedPtr; + fn getDnstapLogAction(config: &DnstapLogActionConfiguration) -> SharedPtr; + fn getDropAction(config: &DropActionConfiguration) -> SharedPtr; + fn getSetEDNSOptionAction(config: &SetEDNSOptionActionConfiguration) -> SharedPtr; + fn getERCodeAction(config: &ERCodeActionConfiguration) -> SharedPtr; + fn getHTTPStatusAction(config: &HTTPStatusActionConfiguration) -> SharedPtr; + fn getKeyValueStoreLookupAction(config: &KeyValueStoreLookupActionConfiguration) -> SharedPtr; + fn getKeyValueStoreRangeLookupAction(config: &KeyValueStoreRangeLookupActionConfiguration) -> SharedPtr; + fn getLogAction(config: &LogActionConfiguration) -> SharedPtr; + fn getLuaAction(config: &LuaActionConfiguration) -> SharedPtr; + fn getLuaFFIAction(config: &LuaFFIActionConfiguration) -> SharedPtr; + fn getLuaFFIPerThreadAction(config: &LuaFFIPerThreadActionConfiguration) -> SharedPtr; + fn getNegativeAndSOAAction(config: &NegativeAndSOAActionConfiguration) -> SharedPtr; + fn getNoneAction(config: &NoneActionConfiguration) -> SharedPtr; + fn getPoolAction(config: &PoolActionConfiguration) -> SharedPtr; + fn getQPSAction(config: &QPSActionConfiguration) -> SharedPtr; + fn getQPSPoolAction(config: &QPSPoolActionConfiguration) -> SharedPtr; + fn getRCodeAction(config: &RCodeActionConfiguration) -> SharedPtr; + fn getRemoteLogAction(config: &RemoteLogActionConfiguration) -> SharedPtr; + fn getSetAdditionalProxyProtocolValueAction(config: &SetAdditionalProxyProtocolValueActionConfiguration) -> SharedPtr; + fn getSetDisableECSAction(config: &SetDisableECSActionConfiguration) -> SharedPtr; + fn getSetDisableValidationAction(config: &SetDisableValidationActionConfiguration) -> SharedPtr; + fn getSetECSAction(config: &SetECSActionConfiguration) -> SharedPtr; + fn getSetECSOverrideAction(config: &SetECSOverrideActionConfiguration) -> SharedPtr; + fn getSetECSPrefixLengthAction(config: &SetECSPrefixLengthActionConfiguration) -> SharedPtr; + fn getSetExtendedDNSErrorAction(config: &SetExtendedDNSErrorActionConfiguration) -> SharedPtr; + fn getSetMacAddrAction(config: &SetMacAddrActionConfiguration) -> SharedPtr; + fn getSetMaxReturnedTTLAction(config: &SetMaxReturnedTTLActionConfiguration) -> SharedPtr; + fn getSetNoRecurseAction(config: &SetNoRecurseActionConfiguration) -> SharedPtr; + fn getSetProxyProtocolValuesAction(config: &SetProxyProtocolValuesActionConfiguration) -> SharedPtr; + fn getSetSkipCacheAction(config: &SetSkipCacheActionConfiguration) -> SharedPtr; + fn getSetTagAction(config: &SetTagActionConfiguration) -> SharedPtr; + fn getSetTempFailureCacheTTLAction(config: &SetTempFailureCacheTTLActionConfiguration) -> SharedPtr; + fn getSNMPTrapAction(config: &SNMPTrapActionConfiguration) -> SharedPtr; + fn getSpoofAction(config: &SpoofActionConfiguration) -> SharedPtr; + fn getSpoofCNAMEAction(config: &SpoofCNAMEActionConfiguration) -> SharedPtr; + fn getSpoofPacketAction(config: &SpoofPacketActionConfiguration) -> SharedPtr; + fn getSpoofRawAction(config: &SpoofRawActionConfiguration) -> SharedPtr; + fn getSpoofSVCAction(config: &SpoofSVCActionConfiguration) -> SharedPtr; + fn getTCAction(config: &TCActionConfiguration) -> SharedPtr; + fn getTeeAction(config: &TeeActionConfiguration) -> SharedPtr; + fn getAllowResponseAction(config: &AllowResponseActionConfiguration) -> SharedPtr; + fn getClearRecordTypesResponseAction(config: &ClearRecordTypesResponseActionConfiguration) -> SharedPtr; + fn getDelayResponseAction(config: &DelayResponseActionConfiguration) -> SharedPtr; + fn getDnstapLogResponseAction(config: &DnstapLogResponseActionConfiguration) -> SharedPtr; + fn getDropResponseAction(config: &DropResponseActionConfiguration) -> SharedPtr; + fn getLimitTTLResponseAction(config: &LimitTTLResponseActionConfiguration) -> SharedPtr; + fn getLogResponseAction(config: &LogResponseActionConfiguration) -> SharedPtr; + fn getLuaResponseAction(config: &LuaResponseActionConfiguration) -> SharedPtr; + fn getLuaFFIResponseAction(config: &LuaFFIResponseActionConfiguration) -> SharedPtr; + fn getLuaFFIPerThreadResponseAction(config: &LuaFFIPerThreadResponseActionConfiguration) -> SharedPtr; + fn getRemoteLogResponseAction(config: &RemoteLogResponseActionConfiguration) -> SharedPtr; + fn getSetExtendedDNSErrorResponseAction(config: &SetExtendedDNSErrorResponseActionConfiguration) -> SharedPtr; + fn getSetMaxReturnedTTLResponseAction(config: &SetMaxReturnedTTLResponseActionConfiguration) -> SharedPtr; + fn getSetMaxTTLResponseAction(config: &SetMaxTTLResponseActionConfiguration) -> SharedPtr; + fn getSetMinTTLResponseAction(config: &SetMinTTLResponseActionConfiguration) -> SharedPtr; + fn getSetReducedTTLResponseAction(config: &SetReducedTTLResponseActionConfiguration) -> SharedPtr; + fn getSetSkipCacheResponseAction(config: &SetSkipCacheResponseActionConfiguration) -> SharedPtr; + fn getSetTagResponseAction(config: &SetTagResponseActionConfiguration) -> SharedPtr; + fn getSNMPTrapResponseAction(config: &SNMPTrapResponseActionConfiguration) -> SharedPtr; + fn getTCResponseAction(config: &TCResponseActionConfiguration) -> SharedPtr; + fn getAllSelector(config: &AllSelectorConfiguration) -> SharedPtr; + fn getAndSelector(config: &AndSelectorConfiguration) -> SharedPtr; + fn getByNameSelector(config: &ByNameSelectorConfiguration) -> SharedPtr; + fn getDNSSECSelector(config: &DNSSECSelectorConfiguration) -> SharedPtr; + fn getDSTPortSelector(config: &DSTPortSelectorConfiguration) -> SharedPtr; + fn getEDNSOptionSelector(config: &EDNSOptionSelectorConfiguration) -> SharedPtr; + fn getEDNSVersionSelector(config: &EDNSVersionSelectorConfiguration) -> SharedPtr; + fn getERCodeSelector(config: &ERCodeSelectorConfiguration) -> SharedPtr; + fn getHTTPHeaderSelector(config: &HTTPHeaderSelectorConfiguration) -> SharedPtr; + fn getHTTPPathSelector(config: &HTTPPathSelectorConfiguration) -> SharedPtr; + fn getHTTPPathRegexSelector(config: &HTTPPathRegexSelectorConfiguration) -> SharedPtr; + fn getKeyValueStoreLookupSelector(config: &KeyValueStoreLookupSelectorConfiguration) -> SharedPtr; + fn getKeyValueStoreRangeLookupSelector(config: &KeyValueStoreRangeLookupSelectorConfiguration) -> SharedPtr; + fn getLuaSelector(config: &LuaSelectorConfiguration) -> SharedPtr; + fn getLuaFFISelector(config: &LuaFFISelectorConfiguration) -> SharedPtr; + fn getLuaFFIPerThreadSelector(config: &LuaFFIPerThreadSelectorConfiguration) -> SharedPtr; + fn getMaxQPSSelector(config: &MaxQPSSelectorConfiguration) -> SharedPtr; + fn getMaxQPSIPSelector(config: &MaxQPSIPSelectorConfiguration) -> SharedPtr; + fn getNetmaskGroupSelector(config: &NetmaskGroupSelectorConfiguration) -> SharedPtr; + fn getNotSelector(config: &NotSelectorConfiguration) -> SharedPtr; + fn getOpcodeSelector(config: &OpcodeSelectorConfiguration) -> SharedPtr; + fn getOrSelector(config: &OrSelectorConfiguration) -> SharedPtr; + fn getPayloadSizeSelector(config: &PayloadSizeSelectorConfiguration) -> SharedPtr; + fn getPoolAvailableSelector(config: &PoolAvailableSelectorConfiguration) -> SharedPtr; + fn getPoolOutstandingSelector(config: &PoolOutstandingSelectorConfiguration) -> SharedPtr; + fn getProbaSelector(config: &ProbaSelectorConfiguration) -> SharedPtr; + fn getProxyProtocolValueSelector(config: &ProxyProtocolValueSelectorConfiguration) -> SharedPtr; + fn getQClassSelector(config: &QClassSelectorConfiguration) -> SharedPtr; + fn getQNameSelector(config: &QNameSelectorConfiguration) -> SharedPtr; + fn getQNameLabelsCountSelector(config: &QNameLabelsCountSelectorConfiguration) -> SharedPtr; + fn getQNameSetSelector(config: &QNameSetSelectorConfiguration) -> SharedPtr; + fn getQNameSuffixSelector(config: &QNameSuffixSelectorConfiguration) -> SharedPtr; + fn getQNameWireLengthSelector(config: &QNameWireLengthSelectorConfiguration) -> SharedPtr; + fn getQTypeSelector(config: &QTypeSelectorConfiguration) -> SharedPtr; + fn getRCodeSelector(config: &RCodeSelectorConfiguration) -> SharedPtr; + fn getRDSelector(config: &RDSelectorConfiguration) -> SharedPtr; + fn getRE2Selector(config: &RE2SelectorConfiguration) -> SharedPtr; + fn getRecordsCountSelector(config: &RecordsCountSelectorConfiguration) -> SharedPtr; + fn getRecordsTypeCountSelector(config: &RecordsTypeCountSelectorConfiguration) -> SharedPtr; + fn getRegexSelector(config: &RegexSelectorConfiguration) -> SharedPtr; + fn getSNISelector(config: &SNISelectorConfiguration) -> SharedPtr; + fn getTagSelector(config: &TagSelectorConfiguration) -> SharedPtr; + fn getTCPSelector(config: &TCPSelectorConfiguration) -> SharedPtr; + fn getTrailingDataSelector(config: &TrailingDataSelectorConfiguration) -> SharedPtr; + } +// START INCLUDE ./rust-middle-in.rs + /* + * Functions callable from C++ + */ + extern "Rust" { + fn from_yaml_string(str: &str) -> Result; + } + /* + * Functions callable from Rust + */ + unsafe extern "C++" { + include!("dnsdist-rust-bridge.hh"); + type DNSSelector; + type DNSActionWrapper; + type DNSResponseActionWrapper; + fn registerProtobufLogger(config: &ProtobufLoggerConfiguration); + fn registerDnstapLogger(config: &DnstapLoggerConfiguration); + fn registerKVSObjects(config: &KeyValueStoresConfiguration); + } +} + +impl Default for dnsdistsettings::SharedDNSAction { + fn default() -> dnsdistsettings::SharedDNSAction { + dnsdistsettings::SharedDNSAction { + action: cxx::SharedPtr::null(), + } + } +} + +impl Default for dnsdistsettings::SharedDNSSelector { + fn default() -> dnsdistsettings::SharedDNSSelector { + dnsdistsettings::SharedDNSSelector { + selector: cxx::SharedPtr::null(), + } + } +} + +#[derive(Default, Deserialize, Serialize, Debug, PartialEq)] +#[serde(deny_unknown_fields)] +struct AndSelectorConfigurationSerde { + #[serde(default, skip_serializing_if = "crate::is_default")] + selectors: Vec, +} + +#[derive(Default, Deserialize, Serialize, Debug, PartialEq)] +#[serde(deny_unknown_fields)] +struct OrSelectorConfigurationSerde { + #[serde(default, skip_serializing_if = "crate::is_default")] + selectors: Vec, +} + +#[derive(Default, Deserialize, Serialize, Debug, PartialEq)] +#[serde(deny_unknown_fields)] +struct NotSelectorConfigurationSerde { + #[serde(default, skip_serializing_if = "crate::is_default")] + selector: Box, +} + +#[derive(Default, Deserialize, Serialize, Debug, PartialEq)] +#[serde(deny_unknown_fields)] +struct ContinueActionConfigurationSerde { + #[serde(default, skip_serializing_if = "crate::is_default")] + action: Box, +} + +#[derive(Default, Deserialize, Serialize, Debug, PartialEq)] +#[serde(deny_unknown_fields)] +struct QueryRuleConfigurationSerde { + #[serde(default, skip_serializing_if = "crate::is_default")] + name: String, + #[serde(default, skip_serializing_if = "crate::is_default")] + uuid: String, + selector: Selector, + action: Action, +} + +impl QueryRuleConfigurationSerde { + fn validate(&self) -> Result<(), ValidationError> { + Ok(()) + } +} + +#[derive(Default, Deserialize, Serialize, Debug, PartialEq)] +#[serde(deny_unknown_fields)] +struct ResponseRuleConfigurationSerde { + #[serde(default, skip_serializing_if = "crate::is_default")] + name: String, + #[serde(default, skip_serializing_if = "crate::is_default")] + uuid: String, + selector: Selector, + action: ResponseAction, +} + +impl ResponseRuleConfigurationSerde { + fn validate(&self) -> Result<(), ValidationError> { + Ok(()) + } +} +// END INCLUDE ./rust-middle-in.rs + #[derive(Deserialize, Serialize, Debug, PartialEq)] + #[serde(deny_unknown_fields)] + struct GlobalConfigurationSerde { + #[serde(default = "crate::default_value_global_acl", skip_serializing_if = "crate::default_value_equal_global_acl")] + acl: Vec, + #[serde(default, skip_serializing_if = "crate::is_default")] + backends: Vec, + #[serde(default, skip_serializing_if = "crate::is_default")] + binds: Vec, + #[serde(default, skip_serializing_if = "crate::is_default")] + cache_hit_response_rules: Vec, + #[serde(default, skip_serializing_if = "crate::is_default")] + cache_inserted_response_rules: Vec, + #[serde(default, skip_serializing_if = "crate::is_default")] + cache_miss_rules: Vec, + #[serde(default, skip_serializing_if = "crate::is_default")] + cache_settings: dnsdistsettings::CacheSettingsConfiguration, + #[serde(default, skip_serializing_if = "crate::is_default")] + console: dnsdistsettings::ConsoleConfiguration, + #[serde(default, skip_serializing_if = "crate::is_default")] + dynamic_rules: Vec, + #[serde(default, skip_serializing_if = "crate::is_default")] + dynamic_rules_settings: dnsdistsettings::DynamicRulesSettingsConfiguration, + #[serde(default, skip_serializing_if = "crate::is_default")] + ebpf: dnsdistsettings::EbpfConfiguration, + #[serde(default, skip_serializing_if = "crate::is_default")] + edns_client_subnet: dnsdistsettings::EdnsClientSubnetConfiguration, + #[serde(default, skip_serializing_if = "crate::is_default")] + general: dnsdistsettings::GeneralConfiguration, + #[serde(default, skip_serializing_if = "crate::is_default")] + key_value_stores: dnsdistsettings::KeyValueStoresConfiguration, + #[serde(default, skip_serializing_if = "crate::is_default")] + load_balancing_policies: dnsdistsettings::LoadBalancingPoliciesConfiguration, + #[serde(default, skip_serializing_if = "crate::is_default")] + logging: dnsdistsettings::LoggingConfiguration, + #[serde(default, skip_serializing_if = "crate::is_default")] + metrics: dnsdistsettings::MetricsConfiguration, + #[serde(default, skip_serializing_if = "crate::is_default")] + packet_caches: Vec, + #[serde(default, skip_serializing_if = "crate::is_default")] + pools: Vec, + #[serde(default, skip_serializing_if = "crate::is_default")] + proxy_protocol: dnsdistsettings::ProxyProtocolConfiguration, + #[serde(default, skip_serializing_if = "crate::is_default")] + query_count: dnsdistsettings::QueryCountConfiguration, + #[serde(default, skip_serializing_if = "crate::is_default")] + query_rules: Vec, + #[serde(default, skip_serializing_if = "crate::is_default")] + remote_logging: dnsdistsettings::RemoteLoggingConfiguration, + #[serde(default, skip_serializing_if = "crate::is_default")] + response_rules: Vec, + #[serde(default, skip_serializing_if = "crate::is_default")] + ring_buffers: dnsdistsettings::RingBuffersConfiguration, + #[serde(default, skip_serializing_if = "crate::is_default")] + security_polling: dnsdistsettings::SecurityPollingConfiguration, + #[serde(default, skip_serializing_if = "crate::is_default")] + selectors: Vec, + #[serde(default, skip_serializing_if = "crate::is_default")] + self_answered_response_rules: Vec, + #[serde(default, skip_serializing_if = "crate::is_default")] + snmp: dnsdistsettings::SnmpConfiguration, + #[serde(default, skip_serializing_if = "crate::is_default")] + tuning: dnsdistsettings::TuningConfiguration, + #[serde(default, skip_serializing_if = "crate::is_default")] + webserver: dnsdistsettings::WebserverConfiguration, + #[serde(default, skip_serializing_if = "crate::is_default")] + xfr_response_rules: Vec, + #[serde(default, skip_serializing_if = "crate::is_default")] + xsk: Vec, + } + +#[derive(Default, Serialize, Deserialize, Debug, PartialEq)] +#[serde(tag = "type")] +enum Action { + #[default] + Default, + Allow(dnsdistsettings::AllowActionConfiguration), + Continue(ContinueActionConfigurationSerde), + Delay(dnsdistsettings::DelayActionConfiguration), + DnstapLog(dnsdistsettings::DnstapLogActionConfiguration), + Drop(dnsdistsettings::DropActionConfiguration), + SetEDNSOption(dnsdistsettings::SetEDNSOptionActionConfiguration), + ERCode(dnsdistsettings::ERCodeActionConfiguration), + HTTPStatus(dnsdistsettings::HTTPStatusActionConfiguration), + KeyValueStoreLookup(dnsdistsettings::KeyValueStoreLookupActionConfiguration), + KeyValueStoreRangeLookup(dnsdistsettings::KeyValueStoreRangeLookupActionConfiguration), + Log(dnsdistsettings::LogActionConfiguration), + Lua(dnsdistsettings::LuaActionConfiguration), + LuaFFI(dnsdistsettings::LuaFFIActionConfiguration), + LuaFFIPerThread(dnsdistsettings::LuaFFIPerThreadActionConfiguration), + NegativeAndSOA(dnsdistsettings::NegativeAndSOAActionConfiguration), + None(dnsdistsettings::NoneActionConfiguration), + Pool(dnsdistsettings::PoolActionConfiguration), + QPS(dnsdistsettings::QPSActionConfiguration), + QPSPool(dnsdistsettings::QPSPoolActionConfiguration), + RCode(dnsdistsettings::RCodeActionConfiguration), + RemoteLog(dnsdistsettings::RemoteLogActionConfiguration), + SetAdditionalProxyProtocolValue(dnsdistsettings::SetAdditionalProxyProtocolValueActionConfiguration), + SetDisableECS(dnsdistsettings::SetDisableECSActionConfiguration), + SetDisableValidation(dnsdistsettings::SetDisableValidationActionConfiguration), + SetECS(dnsdistsettings::SetECSActionConfiguration), + SetECSOverride(dnsdistsettings::SetECSOverrideActionConfiguration), + SetECSPrefixLength(dnsdistsettings::SetECSPrefixLengthActionConfiguration), + SetExtendedDNSError(dnsdistsettings::SetExtendedDNSErrorActionConfiguration), + SetMacAddr(dnsdistsettings::SetMacAddrActionConfiguration), + SetMaxReturnedTTL(dnsdistsettings::SetMaxReturnedTTLActionConfiguration), + SetNoRecurse(dnsdistsettings::SetNoRecurseActionConfiguration), + SetProxyProtocolValues(dnsdistsettings::SetProxyProtocolValuesActionConfiguration), + SetSkipCache(dnsdistsettings::SetSkipCacheActionConfiguration), + SetTag(dnsdistsettings::SetTagActionConfiguration), + SetTempFailureCacheTTL(dnsdistsettings::SetTempFailureCacheTTLActionConfiguration), + SNMPTrap(dnsdistsettings::SNMPTrapActionConfiguration), + Spoof(dnsdistsettings::SpoofActionConfiguration), + SpoofCNAME(dnsdistsettings::SpoofCNAMEActionConfiguration), + SpoofPacket(dnsdistsettings::SpoofPacketActionConfiguration), + SpoofRaw(dnsdistsettings::SpoofRawActionConfiguration), + SpoofSVC(dnsdistsettings::SpoofSVCActionConfiguration), + TC(dnsdistsettings::TCActionConfiguration), + Tee(dnsdistsettings::TeeActionConfiguration), +} + +#[derive(Default, Serialize, Deserialize, Debug, PartialEq)] +#[serde(tag = "type")] +enum ResponseAction { + #[default] + Default, + Allow(dnsdistsettings::AllowResponseActionConfiguration), + ClearRecordTypes(dnsdistsettings::ClearRecordTypesResponseActionConfiguration), + Delay(dnsdistsettings::DelayResponseActionConfiguration), + DnstapLog(dnsdistsettings::DnstapLogResponseActionConfiguration), + Drop(dnsdistsettings::DropResponseActionConfiguration), + LimitTTL(dnsdistsettings::LimitTTLResponseActionConfiguration), + Log(dnsdistsettings::LogResponseActionConfiguration), + Lua(dnsdistsettings::LuaResponseActionConfiguration), + LuaFFI(dnsdistsettings::LuaFFIResponseActionConfiguration), + LuaFFIPerThread(dnsdistsettings::LuaFFIPerThreadResponseActionConfiguration), + RemoteLog(dnsdistsettings::RemoteLogResponseActionConfiguration), + SetExtendedDNSError(dnsdistsettings::SetExtendedDNSErrorResponseActionConfiguration), + SetMaxReturnedTTL(dnsdistsettings::SetMaxReturnedTTLResponseActionConfiguration), + SetMaxTTL(dnsdistsettings::SetMaxTTLResponseActionConfiguration), + SetMinTTL(dnsdistsettings::SetMinTTLResponseActionConfiguration), + SetReducedTTL(dnsdistsettings::SetReducedTTLResponseActionConfiguration), + SetSkipCache(dnsdistsettings::SetSkipCacheResponseActionConfiguration), + SetTag(dnsdistsettings::SetTagResponseActionConfiguration), + SNMPTrap(dnsdistsettings::SNMPTrapResponseActionConfiguration), + TC(dnsdistsettings::TCResponseActionConfiguration), +} + +#[derive(Default, Serialize, Deserialize, Debug, PartialEq)] +#[serde(tag = "type")] +enum Selector { + #[default] + Default, + All(dnsdistsettings::AllSelectorConfiguration), + And(AndSelectorConfigurationSerde), + ByName(dnsdistsettings::ByNameSelectorConfiguration), + DNSSEC(dnsdistsettings::DNSSECSelectorConfiguration), + DSTPort(dnsdistsettings::DSTPortSelectorConfiguration), + EDNSOption(dnsdistsettings::EDNSOptionSelectorConfiguration), + EDNSVersion(dnsdistsettings::EDNSVersionSelectorConfiguration), + ERCode(dnsdistsettings::ERCodeSelectorConfiguration), + HTTPHeader(dnsdistsettings::HTTPHeaderSelectorConfiguration), + HTTPPath(dnsdistsettings::HTTPPathSelectorConfiguration), + HTTPPathRegex(dnsdistsettings::HTTPPathRegexSelectorConfiguration), + KeyValueStoreLookup(dnsdistsettings::KeyValueStoreLookupSelectorConfiguration), + KeyValueStoreRangeLookup(dnsdistsettings::KeyValueStoreRangeLookupSelectorConfiguration), + Lua(dnsdistsettings::LuaSelectorConfiguration), + LuaFFI(dnsdistsettings::LuaFFISelectorConfiguration), + LuaFFIPerThread(dnsdistsettings::LuaFFIPerThreadSelectorConfiguration), + MaxQPS(dnsdistsettings::MaxQPSSelectorConfiguration), + MaxQPSIP(dnsdistsettings::MaxQPSIPSelectorConfiguration), + NetmaskGroup(dnsdistsettings::NetmaskGroupSelectorConfiguration), + Not(NotSelectorConfigurationSerde), + Opcode(dnsdistsettings::OpcodeSelectorConfiguration), + Or(OrSelectorConfigurationSerde), + PayloadSize(dnsdistsettings::PayloadSizeSelectorConfiguration), + PoolAvailable(dnsdistsettings::PoolAvailableSelectorConfiguration), + PoolOutstanding(dnsdistsettings::PoolOutstandingSelectorConfiguration), + Proba(dnsdistsettings::ProbaSelectorConfiguration), + ProxyProtocolValue(dnsdistsettings::ProxyProtocolValueSelectorConfiguration), + QClass(dnsdistsettings::QClassSelectorConfiguration), + QName(dnsdistsettings::QNameSelectorConfiguration), + QNameLabelsCount(dnsdistsettings::QNameLabelsCountSelectorConfiguration), + QNameSet(dnsdistsettings::QNameSetSelectorConfiguration), + QNameSuffix(dnsdistsettings::QNameSuffixSelectorConfiguration), + QNameWireLength(dnsdistsettings::QNameWireLengthSelectorConfiguration), + QType(dnsdistsettings::QTypeSelectorConfiguration), + RCode(dnsdistsettings::RCodeSelectorConfiguration), + RD(dnsdistsettings::RDSelectorConfiguration), + RE2(dnsdistsettings::RE2SelectorConfiguration), + RecordsCount(dnsdistsettings::RecordsCountSelectorConfiguration), + RecordsTypeCount(dnsdistsettings::RecordsTypeCountSelectorConfiguration), + Regex(dnsdistsettings::RegexSelectorConfiguration), + SNI(dnsdistsettings::SNISelectorConfiguration), + Tag(dnsdistsettings::TagSelectorConfiguration), + TCP(dnsdistsettings::TCPSelectorConfiguration), + TrailingData(dnsdistsettings::TrailingDataSelectorConfiguration), +} + +impl Default for dnsdistsettings::MetricsConfiguration { + fn default() -> Self { + let deserialized: dnsdistsettings::MetricsConfiguration = serde_yaml::from_str("").unwrap(); + deserialized + } +} + + +impl Default for dnsdistsettings::CarbonConfiguration { + fn default() -> Self { + let deserialized: dnsdistsettings::CarbonConfiguration = serde_yaml::from_str("").unwrap(); + deserialized + } +} + + +impl Default for dnsdistsettings::RemoteLoggingConfiguration { + fn default() -> Self { + let deserialized: dnsdistsettings::RemoteLoggingConfiguration = serde_yaml::from_str("").unwrap(); + deserialized + } +} + + +impl Default for dnsdistsettings::ProtobufLoggerConfiguration { + fn default() -> Self { + let deserialized: dnsdistsettings::ProtobufLoggerConfiguration = serde_yaml::from_str("").unwrap(); + deserialized + } +} + + +impl Default for dnsdistsettings::DnstapLoggerConfiguration { + fn default() -> Self { + let deserialized: dnsdistsettings::DnstapLoggerConfiguration = serde_yaml::from_str("").unwrap(); + deserialized + } +} + + +impl Default for dnsdistsettings::ProtoBufMetaConfiguration { + fn default() -> Self { + let deserialized: dnsdistsettings::ProtoBufMetaConfiguration = serde_yaml::from_str("").unwrap(); + deserialized + } +} + + +impl Default for dnsdistsettings::LmdbKvStoreConfiguration { + fn default() -> Self { + let deserialized: dnsdistsettings::LmdbKvStoreConfiguration = serde_yaml::from_str("").unwrap(); + deserialized + } +} + + +impl Default for dnsdistsettings::CdbKvStoreConfiguration { + fn default() -> Self { + let deserialized: dnsdistsettings::CdbKvStoreConfiguration = serde_yaml::from_str("").unwrap(); + deserialized + } +} + + +impl Default for dnsdistsettings::KvsLookupKeySourceIpConfiguration { + fn default() -> Self { + let deserialized: dnsdistsettings::KvsLookupKeySourceIpConfiguration = serde_yaml::from_str("").unwrap(); + deserialized + } +} + + +impl Default for dnsdistsettings::KvsLookupKeyQnameConfiguration { + fn default() -> Self { + let deserialized: dnsdistsettings::KvsLookupKeyQnameConfiguration = serde_yaml::from_str("").unwrap(); + deserialized + } +} + + +impl Default for dnsdistsettings::KvsLookupKeySuffixConfiguration { + fn default() -> Self { + let deserialized: dnsdistsettings::KvsLookupKeySuffixConfiguration = serde_yaml::from_str("").unwrap(); + deserialized + } +} + + +impl Default for dnsdistsettings::KvsLookupKeyTagConfiguration { + fn default() -> Self { + let deserialized: dnsdistsettings::KvsLookupKeyTagConfiguration = serde_yaml::from_str("").unwrap(); + deserialized + } +} + + +impl Default for dnsdistsettings::KvsLookupKeysConfiguration { + fn default() -> Self { + let deserialized: dnsdistsettings::KvsLookupKeysConfiguration = serde_yaml::from_str("").unwrap(); + deserialized + } +} + + +impl Default for dnsdistsettings::KeyValueStoresConfiguration { + fn default() -> Self { + let deserialized: dnsdistsettings::KeyValueStoresConfiguration = serde_yaml::from_str("").unwrap(); + deserialized + } +} + + +// DEFAULT HANDLING for webserver_acl +fn default_value_webserver_acl() -> Vec { + vec![ + String::from("127.0.0.1"), + String::from("::1"), + ] +} +fn default_value_equal_webserver_acl(value: &Vec) -> bool { + let def = default_value_webserver_acl(); + &def == value +} + + +impl Default for dnsdistsettings::WebserverConfiguration { + fn default() -> Self { + let deserialized: dnsdistsettings::WebserverConfiguration = serde_yaml::from_str("").unwrap(); + deserialized + } +} + + +// DEFAULT HANDLING for console_acl +fn default_value_console_acl() -> Vec { + vec![ + String::from("127.0.0.1"), + String::from("::1"), + ] +} +fn default_value_equal_console_acl(value: &Vec) -> bool { + let def = default_value_console_acl(); + &def == value +} + + +impl Default for dnsdistsettings::ConsoleConfiguration { + fn default() -> Self { + let deserialized: dnsdistsettings::ConsoleConfiguration = serde_yaml::from_str("").unwrap(); + deserialized + } +} + + +impl Default for dnsdistsettings::EbpfMapConfiguration { + fn default() -> Self { + let deserialized: dnsdistsettings::EbpfMapConfiguration = serde_yaml::from_str("").unwrap(); + deserialized + } +} + + +impl Default for dnsdistsettings::EbpfConfiguration { + fn default() -> Self { + let deserialized: dnsdistsettings::EbpfConfiguration = serde_yaml::from_str("").unwrap(); + deserialized + } +} + + +impl Default for dnsdistsettings::EdnsClientSubnetConfiguration { + fn default() -> Self { + let deserialized: dnsdistsettings::EdnsClientSubnetConfiguration = serde_yaml::from_str("").unwrap(); + deserialized + } +} + + +// DEFAULT HANDLING for dynamic_rules_settings_default_action +fn default_value_dynamic_rules_settings_default_action() -> String { + String::from("Drop") +} +fn default_value_equal_dynamic_rules_settings_default_action(value: &str)-> bool { + value == default_value_dynamic_rules_settings_default_action() +} + + +impl Default for dnsdistsettings::DynamicRulesSettingsConfiguration { + fn default() -> Self { + let deserialized: dnsdistsettings::DynamicRulesSettingsConfiguration = serde_yaml::from_str("").unwrap(); + deserialized + } +} + + +// DEFAULT HANDLING for dynamic_rule_action +fn default_value_dynamic_rule_action() -> String { + String::from("drop") +} +fn default_value_equal_dynamic_rule_action(value: &str)-> bool { + value == default_value_dynamic_rule_action() +} + + +// DEFAULT HANDLING for dynamic_rule_tag_value +fn default_value_dynamic_rule_tag_value() -> String { + String::from("0") +} +fn default_value_equal_dynamic_rule_tag_value(value: &str)-> bool { + value == default_value_dynamic_rule_tag_value() +} + + +impl Default for dnsdistsettings::DynamicRuleConfiguration { + fn default() -> Self { + let deserialized: dnsdistsettings::DynamicRuleConfiguration = serde_yaml::from_str("").unwrap(); + deserialized + } +} + + +impl Default for dnsdistsettings::DynamicRulesConfiguration { + fn default() -> Self { + let deserialized: dnsdistsettings::DynamicRulesConfiguration = serde_yaml::from_str("").unwrap(); + deserialized + } +} + + +impl Default for dnsdistsettings::RingBuffersConfiguration { + fn default() -> Self { + let deserialized: dnsdistsettings::RingBuffersConfiguration = serde_yaml::from_str("").unwrap(); + deserialized + } +} + + +impl Default for dnsdistsettings::IncomingTlsCertificateKeyPairConfiguration { + fn default() -> Self { + let deserialized: dnsdistsettings::IncomingTlsCertificateKeyPairConfiguration = serde_yaml::from_str("").unwrap(); + deserialized + } +} + + +// DEFAULT HANDLING for incoming_tls_provider +fn default_value_incoming_tls_provider() -> String { + String::from("OpenSSL") +} +fn default_value_equal_incoming_tls_provider(value: &str)-> bool { + value == default_value_incoming_tls_provider() +} + + +// DEFAULT HANDLING for incoming_tls_minimum_version +fn default_value_incoming_tls_minimum_version() -> String { + String::from("tls1.0") +} +fn default_value_equal_incoming_tls_minimum_version(value: &str)-> bool { + value == default_value_incoming_tls_minimum_version() +} + + +impl Default for dnsdistsettings::IncomingTlsConfiguration { + fn default() -> Self { + let deserialized: dnsdistsettings::IncomingTlsConfiguration = serde_yaml::from_str("").unwrap(); + deserialized + } +} + + +// DEFAULT HANDLING for outgoing_tls_provider +fn default_value_outgoing_tls_provider() -> String { + String::from("OpenSSL") +} +fn default_value_equal_outgoing_tls_provider(value: &str)-> bool { + value == default_value_outgoing_tls_provider() +} + + +impl Default for dnsdistsettings::OutgoingTlsConfiguration { + fn default() -> Self { + let deserialized: dnsdistsettings::OutgoingTlsConfiguration = serde_yaml::from_str("").unwrap(); + deserialized + } +} + + +impl Default for dnsdistsettings::HttpCustomResponseHeaderConfiguration { + fn default() -> Self { + let deserialized: dnsdistsettings::HttpCustomResponseHeaderConfiguration = serde_yaml::from_str("").unwrap(); + deserialized + } +} + + +impl Default for dnsdistsettings::HttpResponsesMapConfiguration { + fn default() -> Self { + let deserialized: dnsdistsettings::HttpResponsesMapConfiguration = serde_yaml::from_str("").unwrap(); + deserialized + } +} + + +// DEFAULT HANDLING for incoming_doh_provider +fn default_value_incoming_doh_provider() -> String { + String::from("nghttp2") +} +fn default_value_equal_incoming_doh_provider(value: &str)-> bool { + value == default_value_incoming_doh_provider() +} + + +// DEFAULT HANDLING for incoming_doh_paths +fn default_value_incoming_doh_paths() -> Vec { + vec![ + String::from("/dns-query"), + ] +} +fn default_value_equal_incoming_doh_paths(value: &Vec) -> bool { + let def = default_value_incoming_doh_paths(); + &def == value +} + + +impl Default for dnsdistsettings::IncomingDohConfiguration { + fn default() -> Self { + let deserialized: dnsdistsettings::IncomingDohConfiguration = serde_yaml::from_str("").unwrap(); + deserialized + } +} + + +impl Default for dnsdistsettings::IncomingDoqConfiguration { + fn default() -> Self { + let deserialized: dnsdistsettings::IncomingDoqConfiguration = serde_yaml::from_str("").unwrap(); + deserialized + } +} + + +// DEFAULT HANDLING for incoming_quic_congestion_control_algorithm +fn default_value_incoming_quic_congestion_control_algorithm() -> String { + String::from("reno") +} +fn default_value_equal_incoming_quic_congestion_control_algorithm(value: &str)-> bool { + value == default_value_incoming_quic_congestion_control_algorithm() +} + + +impl Default for dnsdistsettings::IncomingQuicConfiguration { + fn default() -> Self { + let deserialized: dnsdistsettings::IncomingQuicConfiguration = serde_yaml::from_str("").unwrap(); + deserialized + } +} + + +impl Default for dnsdistsettings::IncomingDnscryptCertificateKeyPairConfiguration { + fn default() -> Self { + let deserialized: dnsdistsettings::IncomingDnscryptCertificateKeyPairConfiguration = serde_yaml::from_str("").unwrap(); + deserialized + } +} + + +impl Default for dnsdistsettings::IncomingDnscryptConfiguration { + fn default() -> Self { + let deserialized: dnsdistsettings::IncomingDnscryptConfiguration = serde_yaml::from_str("").unwrap(); + deserialized + } +} + + +// DEFAULT HANDLING for outgoing_doh_path +fn default_value_outgoing_doh_path() -> String { + String::from("/dns-query") +} +fn default_value_equal_outgoing_doh_path(value: &str)-> bool { + value == default_value_outgoing_doh_path() +} + + +impl Default for dnsdistsettings::OutgoingDohConfiguration { + fn default() -> Self { + let deserialized: dnsdistsettings::OutgoingDohConfiguration = serde_yaml::from_str("").unwrap(); + deserialized + } +} + + +impl Default for dnsdistsettings::IncomingTcpConfiguration { + fn default() -> Self { + let deserialized: dnsdistsettings::IncomingTcpConfiguration = serde_yaml::from_str("").unwrap(); + deserialized + } +} + + +// DEFAULT HANDLING for bind_protocol +fn default_value_bind_protocol() -> String { + String::from("Do53") +} +fn default_value_equal_bind_protocol(value: &str)-> bool { + value == default_value_bind_protocol() +} + + +impl Default for dnsdistsettings::BindConfiguration { + fn default() -> Self { + let deserialized: dnsdistsettings::BindConfiguration = serde_yaml::from_str("").unwrap(); + deserialized + } +} + + +impl Default for dnsdistsettings::OutgoingTcpConfiguration { + fn default() -> Self { + let deserialized: dnsdistsettings::OutgoingTcpConfiguration = serde_yaml::from_str("").unwrap(); + deserialized + } +} + + +impl Default for dnsdistsettings::ProxyProtocolValueConfiguration { + fn default() -> Self { + let deserialized: dnsdistsettings::ProxyProtocolValueConfiguration = serde_yaml::from_str("").unwrap(); + deserialized + } +} + + +// DEFAULT HANDLING for lazy_health_check_mode +fn default_value_lazy_health_check_mode() -> String { + String::from("TimeoutOrServFail") +} +fn default_value_equal_lazy_health_check_mode(value: &str)-> bool { + value == default_value_lazy_health_check_mode() +} + + +impl Default for dnsdistsettings::LazyHealthCheckConfiguration { + fn default() -> Self { + let deserialized: dnsdistsettings::LazyHealthCheckConfiguration = serde_yaml::from_str("").unwrap(); + deserialized + } +} + + +// DEFAULT HANDLING for health_check_mode +fn default_value_health_check_mode() -> String { + String::from("auto") +} +fn default_value_equal_health_check_mode(value: &str)-> bool { + value == default_value_health_check_mode() +} + + +// DEFAULT HANDLING for health_check_qclass +fn default_value_health_check_qclass() -> String { + String::from("IN") +} +fn default_value_equal_health_check_qclass(value: &str)-> bool { + value == default_value_health_check_qclass() +} + + +// DEFAULT HANDLING for health_check_qtype +fn default_value_health_check_qtype() -> String { + String::from("A") +} +fn default_value_equal_health_check_qtype(value: &str)-> bool { + value == default_value_health_check_qtype() +} + + +impl Default for dnsdistsettings::HealthCheckConfiguration { + fn default() -> Self { + let deserialized: dnsdistsettings::HealthCheckConfiguration = serde_yaml::from_str("").unwrap(); + deserialized + } +} + + +impl Default for dnsdistsettings::OutgoingAutoUpgradeConfiguration { + fn default() -> Self { + let deserialized: dnsdistsettings::OutgoingAutoUpgradeConfiguration = serde_yaml::from_str("").unwrap(); + deserialized + } +} + + +impl Default for dnsdistsettings::BackendConfiguration { + fn default() -> Self { + let deserialized: dnsdistsettings::BackendConfiguration = serde_yaml::from_str("").unwrap(); + deserialized + } +} + + +impl Default for dnsdistsettings::TuningConfiguration { + fn default() -> Self { + let deserialized: dnsdistsettings::TuningConfiguration = serde_yaml::from_str("").unwrap(); + deserialized + } +} + + +impl Default for dnsdistsettings::TcpTuningConfiguration { + fn default() -> Self { + let deserialized: dnsdistsettings::TcpTuningConfiguration = serde_yaml::from_str("").unwrap(); + deserialized + } +} + + +impl Default for dnsdistsettings::UdpTuningConfiguration { + fn default() -> Self { + let deserialized: dnsdistsettings::UdpTuningConfiguration = serde_yaml::from_str("").unwrap(); + deserialized + } +} + + +impl Default for dnsdistsettings::TlsEngineConfiguration { + fn default() -> Self { + let deserialized: dnsdistsettings::TlsEngineConfiguration = serde_yaml::from_str("").unwrap(); + deserialized + } +} + + +impl Default for dnsdistsettings::TlsTuningConfiguration { + fn default() -> Self { + let deserialized: dnsdistsettings::TlsTuningConfiguration = serde_yaml::from_str("").unwrap(); + deserialized + } +} + + +impl Default for dnsdistsettings::DohTuningConfiguration { + fn default() -> Self { + let deserialized: dnsdistsettings::DohTuningConfiguration = serde_yaml::from_str("").unwrap(); + deserialized + } +} + + +impl Default for dnsdistsettings::CacheSettingsConfiguration { + fn default() -> Self { + let deserialized: dnsdistsettings::CacheSettingsConfiguration = serde_yaml::from_str("").unwrap(); + deserialized + } +} + + +// DEFAULT HANDLING for security_polling_suffix +fn default_value_security_polling_suffix() -> String { + String::from("secpoll.powerdns.com.") +} +fn default_value_equal_security_polling_suffix(value: &str)-> bool { + value == default_value_security_polling_suffix() +} + + +impl Default for dnsdistsettings::SecurityPollingConfiguration { + fn default() -> Self { + let deserialized: dnsdistsettings::SecurityPollingConfiguration = serde_yaml::from_str("").unwrap(); + deserialized + } +} + + +// DEFAULT HANDLING for structured_logging_level_prefix +fn default_value_structured_logging_level_prefix() -> String { + String::from("prio") +} +fn default_value_equal_structured_logging_level_prefix(value: &str)-> bool { + value == default_value_structured_logging_level_prefix() +} + + +// DEFAULT HANDLING for structured_logging_time_format +fn default_value_structured_logging_time_format() -> String { + String::from("numeric") +} +fn default_value_equal_structured_logging_time_format(value: &str)-> bool { + value == default_value_structured_logging_time_format() +} + + +impl Default for dnsdistsettings::StructuredLoggingConfiguration { + fn default() -> Self { + let deserialized: dnsdistsettings::StructuredLoggingConfiguration = serde_yaml::from_str("").unwrap(); + deserialized + } +} + + +impl Default for dnsdistsettings::LoggingConfiguration { + fn default() -> Self { + let deserialized: dnsdistsettings::LoggingConfiguration = serde_yaml::from_str("").unwrap(); + deserialized + } +} + + +impl Default for dnsdistsettings::GeneralConfiguration { + fn default() -> Self { + let deserialized: dnsdistsettings::GeneralConfiguration = serde_yaml::from_str("").unwrap(); + deserialized + } +} + + +impl Default for dnsdistsettings::PacketCacheConfiguration { + fn default() -> Self { + let deserialized: dnsdistsettings::PacketCacheConfiguration = serde_yaml::from_str("").unwrap(); + deserialized + } +} + + +impl Default for dnsdistsettings::ProxyProtocolConfiguration { + fn default() -> Self { + let deserialized: dnsdistsettings::ProxyProtocolConfiguration = serde_yaml::from_str("").unwrap(); + deserialized + } +} + + +impl Default for dnsdistsettings::SnmpConfiguration { + fn default() -> Self { + let deserialized: dnsdistsettings::SnmpConfiguration = serde_yaml::from_str("").unwrap(); + deserialized + } +} + + +impl Default for dnsdistsettings::QueryCountConfiguration { + fn default() -> Self { + let deserialized: dnsdistsettings::QueryCountConfiguration = serde_yaml::from_str("").unwrap(); + deserialized + } +} + + +impl Default for dnsdistsettings::PoolConfiguration { + fn default() -> Self { + let deserialized: dnsdistsettings::PoolConfiguration = serde_yaml::from_str("").unwrap(); + deserialized + } +} + + +impl Default for dnsdistsettings::CustomLoadBalancingPolicyConfiguration { + fn default() -> Self { + let deserialized: dnsdistsettings::CustomLoadBalancingPolicyConfiguration = serde_yaml::from_str("").unwrap(); + deserialized + } +} + + +// DEFAULT HANDLING for load_balancing_policies_default_policy +fn default_value_load_balancing_policies_default_policy() -> String { + String::from("leastOutstanding") +} +fn default_value_equal_load_balancing_policies_default_policy(value: &str)-> bool { + value == default_value_load_balancing_policies_default_policy() +} + + +impl Default for dnsdistsettings::LoadBalancingPoliciesConfiguration { + fn default() -> Self { + let deserialized: dnsdistsettings::LoadBalancingPoliciesConfiguration = serde_yaml::from_str("").unwrap(); + deserialized + } +} + + +// DEFAULT HANDLING for xsk_map_path +fn default_value_xsk_map_path() -> String { + String::from("/sys/fs/bpf/dnsdist/xskmap") +} +fn default_value_equal_xsk_map_path(value: &str)-> bool { + value == default_value_xsk_map_path() +} + + +impl Default for dnsdistsettings::XskConfiguration { + fn default() -> Self { + let deserialized: dnsdistsettings::XskConfiguration = serde_yaml::from_str("").unwrap(); + deserialized + } +} + + +// DEFAULT HANDLING for global_acl +fn default_value_global_acl() -> Vec { + vec![ + String::from("127.0.0.0/8"), + String::from("10.0.0.0/8"), + String::from("100.64.0.0/10"), + String::from("169.254.0.0/16"), + String::from("192.168.0.0/16"), + String::from("172.16.0.0/12"), + String::from("::1/128"), + String::from("fc00::/7"), + String::from("fe80::/10"), + ] +} +fn default_value_equal_global_acl(value: &Vec) -> bool { + let def = default_value_global_acl(); + &def == value +} + + +impl Default for GlobalConfigurationSerde { + fn default() -> Self { + let deserialized: GlobalConfigurationSerde = serde_yaml::from_str("").unwrap(); + deserialized + } +} + + +impl dnsdistsettings::MetricsConfiguration { + fn validate(&self) -> Result<(), ValidationError> { + for sub_type in &self.carbon { + sub_type.validate()?; + } + Ok(()) + } +} +impl dnsdistsettings::CarbonConfiguration { + fn validate(&self) -> Result<(), ValidationError> { + Ok(()) + } +} +impl dnsdistsettings::RemoteLoggingConfiguration { + fn validate(&self) -> Result<(), ValidationError> { + for sub_type in &self.protobuf_loggers { + sub_type.validate()?; + } + for sub_type in &self.dnstap_loggers { + sub_type.validate()?; + } + Ok(()) + } +} +impl dnsdistsettings::ProtobufLoggerConfiguration { + fn validate(&self) -> Result<(), ValidationError> { + Ok(()) + } +} +impl dnsdistsettings::DnstapLoggerConfiguration { + fn validate(&self) -> Result<(), ValidationError> { + Ok(()) + } +} +impl dnsdistsettings::LmdbKvStoreConfiguration { + fn validate(&self) -> Result<(), ValidationError> { + Ok(()) + } +} +impl dnsdistsettings::CdbKvStoreConfiguration { + fn validate(&self) -> Result<(), ValidationError> { + Ok(()) + } +} +impl dnsdistsettings::KvsLookupKeySourceIpConfiguration { + fn validate(&self) -> Result<(), ValidationError> { + Ok(()) + } +} +impl dnsdistsettings::KvsLookupKeyQnameConfiguration { + fn validate(&self) -> Result<(), ValidationError> { + Ok(()) + } +} +impl dnsdistsettings::KvsLookupKeySuffixConfiguration { + fn validate(&self) -> Result<(), ValidationError> { + Ok(()) + } +} +impl dnsdistsettings::KvsLookupKeyTagConfiguration { + fn validate(&self) -> Result<(), ValidationError> { + Ok(()) + } +} +impl dnsdistsettings::KvsLookupKeysConfiguration { + fn validate(&self) -> Result<(), ValidationError> { + for sub_type in &self.source_ip_keys { + sub_type.validate()?; + } + for sub_type in &self.qname_keys { + sub_type.validate()?; + } + for sub_type in &self.suffix_keys { + sub_type.validate()?; + } + for sub_type in &self.tag_keys { + sub_type.validate()?; + } + Ok(()) + } +} +impl dnsdistsettings::KeyValueStoresConfiguration { + fn validate(&self) -> Result<(), ValidationError> { + for sub_type in &self.lmdb { + sub_type.validate()?; + } + for sub_type in &self.cdb { + sub_type.validate()?; + } + self.lookup_keys.validate()?; + Ok(()) + } +} +impl dnsdistsettings::WebserverConfiguration { + fn validate(&self) -> Result<(), ValidationError> { + for sub_type in &self.custom_headers { + sub_type.validate()?; + } + Ok(()) + } +} +impl dnsdistsettings::ConsoleConfiguration { + fn validate(&self) -> Result<(), ValidationError> { + Ok(()) + } +} +impl dnsdistsettings::EbpfMapConfiguration { + fn validate(&self) -> Result<(), ValidationError> { + Ok(()) + } +} +impl dnsdistsettings::EbpfConfiguration { + fn validate(&self) -> Result<(), ValidationError> { + self.ipv4.validate()?; + self.ipv6.validate()?; + self.cidr_ipv4.validate()?; + self.cidr_ipv6.validate()?; + self.qnames.validate()?; + Ok(()) + } +} +impl dnsdistsettings::EdnsClientSubnetConfiguration { + fn validate(&self) -> Result<(), ValidationError> { + Ok(()) + } +} +impl dnsdistsettings::DynamicRulesSettingsConfiguration { + fn validate(&self) -> Result<(), ValidationError> { + Ok(()) + } +} +impl dnsdistsettings::DynamicRuleConfiguration { + fn validate(&self) -> Result<(), ValidationError> { + Ok(()) + } +} +impl dnsdistsettings::DynamicRulesConfiguration { + fn validate(&self) -> Result<(), ValidationError> { + for sub_type in &self.rules { + sub_type.validate()?; + } + Ok(()) + } +} +impl dnsdistsettings::RingBuffersConfiguration { + fn validate(&self) -> Result<(), ValidationError> { + Ok(()) + } +} +impl dnsdistsettings::IncomingTlsCertificateKeyPairConfiguration { + fn validate(&self) -> Result<(), ValidationError> { + Ok(()) + } +} +impl dnsdistsettings::IncomingTlsConfiguration { + fn validate(&self) -> Result<(), ValidationError> { + for sub_type in &self.certificates { + sub_type.validate()?; + } + Ok(()) + } +} +impl dnsdistsettings::OutgoingTlsConfiguration { + fn validate(&self) -> Result<(), ValidationError> { + Ok(()) + } +} +impl dnsdistsettings::HttpCustomResponseHeaderConfiguration { + fn validate(&self) -> Result<(), ValidationError> { + Ok(()) + } +} +impl dnsdistsettings::HttpResponsesMapConfiguration { + fn validate(&self) -> Result<(), ValidationError> { + for sub_type in &self.headers { + sub_type.validate()?; + } + Ok(()) + } +} +impl dnsdistsettings::IncomingDohConfiguration { + fn validate(&self) -> Result<(), ValidationError> { + for sub_type in &self.custom_response_headers { + sub_type.validate()?; + } + for sub_type in &self.responses_map { + sub_type.validate()?; + } + Ok(()) + } +} +impl dnsdistsettings::IncomingDoqConfiguration { + fn validate(&self) -> Result<(), ValidationError> { + Ok(()) + } +} +impl dnsdistsettings::IncomingQuicConfiguration { + fn validate(&self) -> Result<(), ValidationError> { + Ok(()) + } +} +impl dnsdistsettings::IncomingDnscryptCertificateKeyPairConfiguration { + fn validate(&self) -> Result<(), ValidationError> { + Ok(()) + } +} +impl dnsdistsettings::IncomingDnscryptConfiguration { + fn validate(&self) -> Result<(), ValidationError> { + for sub_type in &self.certificates { + sub_type.validate()?; + } + Ok(()) + } +} +impl dnsdistsettings::OutgoingDohConfiguration { + fn validate(&self) -> Result<(), ValidationError> { + Ok(()) + } +} +impl dnsdistsettings::IncomingTcpConfiguration { + fn validate(&self) -> Result<(), ValidationError> { + Ok(()) + } +} +impl dnsdistsettings::BindConfiguration { + fn validate(&self) -> Result<(), ValidationError> { + self.tcp.validate()?; + self.tls.validate()?; + self.doh.validate()?; + self.doq.validate()?; + self.quic.validate()?; + self.dnscrypt.validate()?; + Ok(()) + } +} +impl dnsdistsettings::OutgoingTcpConfiguration { + fn validate(&self) -> Result<(), ValidationError> { + Ok(()) + } +} +impl dnsdistsettings::LazyHealthCheckConfiguration { + fn validate(&self) -> Result<(), ValidationError> { + Ok(()) + } +} +impl dnsdistsettings::HealthCheckConfiguration { + fn validate(&self) -> Result<(), ValidationError> { + self.lazy.validate()?; + Ok(()) + } +} +impl dnsdistsettings::OutgoingAutoUpgradeConfiguration { + fn validate(&self) -> Result<(), ValidationError> { + Ok(()) + } +} +impl dnsdistsettings::BackendConfiguration { + fn validate(&self) -> Result<(), ValidationError> { + self.tls.validate()?; + self.doh.validate()?; + self.tcp.validate()?; + self.health_checks.validate()?; + self.auto_upgrade.validate()?; + Ok(()) + } +} +impl dnsdistsettings::TuningConfiguration { + fn validate(&self) -> Result<(), ValidationError> { + self.doh.validate()?; + self.tcp.validate()?; + self.tls.validate()?; + self.udp.validate()?; + Ok(()) + } +} +impl dnsdistsettings::TcpTuningConfiguration { + fn validate(&self) -> Result<(), ValidationError> { + Ok(()) + } +} +impl dnsdistsettings::UdpTuningConfiguration { + fn validate(&self) -> Result<(), ValidationError> { + Ok(()) + } +} +impl dnsdistsettings::TlsEngineConfiguration { + fn validate(&self) -> Result<(), ValidationError> { + Ok(()) + } +} +impl dnsdistsettings::TlsTuningConfiguration { + fn validate(&self) -> Result<(), ValidationError> { + for sub_type in &self.engines { + sub_type.validate()?; + } + Ok(()) + } +} +impl dnsdistsettings::DohTuningConfiguration { + fn validate(&self) -> Result<(), ValidationError> { + Ok(()) + } +} +impl dnsdistsettings::CacheSettingsConfiguration { + fn validate(&self) -> Result<(), ValidationError> { + Ok(()) + } +} +impl dnsdistsettings::SecurityPollingConfiguration { + fn validate(&self) -> Result<(), ValidationError> { + Ok(()) + } +} +impl dnsdistsettings::StructuredLoggingConfiguration { + fn validate(&self) -> Result<(), ValidationError> { + Ok(()) + } +} +impl dnsdistsettings::LoggingConfiguration { + fn validate(&self) -> Result<(), ValidationError> { + self.structured.validate()?; + Ok(()) + } +} +impl dnsdistsettings::GeneralConfiguration { + fn validate(&self) -> Result<(), ValidationError> { + Ok(()) + } +} +impl dnsdistsettings::PacketCacheConfiguration { + fn validate(&self) -> Result<(), ValidationError> { + Ok(()) + } +} +impl dnsdistsettings::ProxyProtocolConfiguration { + fn validate(&self) -> Result<(), ValidationError> { + Ok(()) + } +} +impl dnsdistsettings::SnmpConfiguration { + fn validate(&self) -> Result<(), ValidationError> { + Ok(()) + } +} +impl dnsdistsettings::QueryCountConfiguration { + fn validate(&self) -> Result<(), ValidationError> { + Ok(()) + } +} +impl dnsdistsettings::PoolConfiguration { + fn validate(&self) -> Result<(), ValidationError> { + Ok(()) + } +} +impl dnsdistsettings::CustomLoadBalancingPolicyConfiguration { + fn validate(&self) -> Result<(), ValidationError> { + Ok(()) + } +} +impl dnsdistsettings::LoadBalancingPoliciesConfiguration { + fn validate(&self) -> Result<(), ValidationError> { + for sub_type in &self.custom_policies { + sub_type.validate()?; + } + Ok(()) + } +} +impl dnsdistsettings::XskConfiguration { + fn validate(&self) -> Result<(), ValidationError> { + Ok(()) + } +} +impl GlobalConfigurationSerde { + fn validate(&self) -> Result<(), ValidationError> { + for sub_type in &self.backends { + sub_type.validate()?; + } + for sub_type in &self.binds { + sub_type.validate()?; + } + for sub_type in &self.cache_hit_response_rules { + sub_type.validate()?; + } + for sub_type in &self.cache_inserted_response_rules { + sub_type.validate()?; + } + for sub_type in &self.cache_miss_rules { + sub_type.validate()?; + } + self.cache_settings.validate()?; + self.console.validate()?; + for sub_type in &self.dynamic_rules { + sub_type.validate()?; + } + self.dynamic_rules_settings.validate()?; + self.ebpf.validate()?; + self.edns_client_subnet.validate()?; + self.general.validate()?; + self.key_value_stores.validate()?; + self.load_balancing_policies.validate()?; + self.logging.validate()?; + self.metrics.validate()?; + for sub_type in &self.packet_caches { + sub_type.validate()?; + } + for sub_type in &self.pools { + sub_type.validate()?; + } + self.proxy_protocol.validate()?; + self.query_count.validate()?; + for sub_type in &self.query_rules { + sub_type.validate()?; + } + self.remote_logging.validate()?; + for sub_type in &self.response_rules { + sub_type.validate()?; + } + self.ring_buffers.validate()?; + self.security_polling.validate()?; + for sub_type in &self.self_answered_response_rules { + sub_type.validate()?; + } + self.snmp.validate()?; + self.tuning.validate()?; + self.webserver.validate()?; + for sub_type in &self.xfr_response_rules { + sub_type.validate()?; + } + for sub_type in &self.xsk { + sub_type.validate()?; + } + Ok(()) + } +} +fn get_one_action_from_serde(action: &Action) -> Option { + match action { + Action::Default => {} + Action::Allow(config) => { + let tmp_action = dnsdistsettings::getAllowAction(&config); + return Some(dnsdistsettings::SharedDNSAction { + action: tmp_action, + }); + } + Action::Continue(cont) => { + let mut config: dnsdistsettings::ContinueActionConfiguration = Default::default(); + let new_action = get_one_action_from_serde(&*cont.action); + if new_action.is_some() { + config.action = new_action.unwrap(); + } + return Some(dnsdistsettings::SharedDNSAction { + action: dnsdistsettings::getContinueAction(&config), + }); + } + Action::Delay(config) => { + let tmp_action = dnsdistsettings::getDelayAction(&config); + return Some(dnsdistsettings::SharedDNSAction { + action: tmp_action, + }); + } + Action::DnstapLog(config) => { + let tmp_action = dnsdistsettings::getDnstapLogAction(&config); + return Some(dnsdistsettings::SharedDNSAction { + action: tmp_action, + }); + } + Action::Drop(config) => { + let tmp_action = dnsdistsettings::getDropAction(&config); + return Some(dnsdistsettings::SharedDNSAction { + action: tmp_action, + }); + } + Action::SetEDNSOption(config) => { + let tmp_action = dnsdistsettings::getSetEDNSOptionAction(&config); + return Some(dnsdistsettings::SharedDNSAction { + action: tmp_action, + }); + } + Action::ERCode(config) => { + let tmp_action = dnsdistsettings::getERCodeAction(&config); + return Some(dnsdistsettings::SharedDNSAction { + action: tmp_action, + }); + } + Action::HTTPStatus(config) => { + let tmp_action = dnsdistsettings::getHTTPStatusAction(&config); + return Some(dnsdistsettings::SharedDNSAction { + action: tmp_action, + }); + } + Action::KeyValueStoreLookup(config) => { + let tmp_action = dnsdistsettings::getKeyValueStoreLookupAction(&config); + return Some(dnsdistsettings::SharedDNSAction { + action: tmp_action, + }); + } + Action::KeyValueStoreRangeLookup(config) => { + let tmp_action = dnsdistsettings::getKeyValueStoreRangeLookupAction(&config); + return Some(dnsdistsettings::SharedDNSAction { + action: tmp_action, + }); + } + Action::Log(config) => { + let tmp_action = dnsdistsettings::getLogAction(&config); + return Some(dnsdistsettings::SharedDNSAction { + action: tmp_action, + }); + } + Action::Lua(config) => { + let tmp_action = dnsdistsettings::getLuaAction(&config); + return Some(dnsdistsettings::SharedDNSAction { + action: tmp_action, + }); + } + Action::LuaFFI(config) => { + let tmp_action = dnsdistsettings::getLuaFFIAction(&config); + return Some(dnsdistsettings::SharedDNSAction { + action: tmp_action, + }); + } + Action::LuaFFIPerThread(config) => { + let tmp_action = dnsdistsettings::getLuaFFIPerThreadAction(&config); + return Some(dnsdistsettings::SharedDNSAction { + action: tmp_action, + }); + } + Action::NegativeAndSOA(config) => { + let tmp_action = dnsdistsettings::getNegativeAndSOAAction(&config); + return Some(dnsdistsettings::SharedDNSAction { + action: tmp_action, + }); + } + Action::None(config) => { + let tmp_action = dnsdistsettings::getNoneAction(&config); + return Some(dnsdistsettings::SharedDNSAction { + action: tmp_action, + }); + } + Action::Pool(config) => { + let tmp_action = dnsdistsettings::getPoolAction(&config); + return Some(dnsdistsettings::SharedDNSAction { + action: tmp_action, + }); + } + Action::QPS(config) => { + let tmp_action = dnsdistsettings::getQPSAction(&config); + return Some(dnsdistsettings::SharedDNSAction { + action: tmp_action, + }); + } + Action::QPSPool(config) => { + let tmp_action = dnsdistsettings::getQPSPoolAction(&config); + return Some(dnsdistsettings::SharedDNSAction { + action: tmp_action, + }); + } + Action::RCode(config) => { + let tmp_action = dnsdistsettings::getRCodeAction(&config); + return Some(dnsdistsettings::SharedDNSAction { + action: tmp_action, + }); + } + Action::RemoteLog(config) => { + let tmp_action = dnsdistsettings::getRemoteLogAction(&config); + return Some(dnsdistsettings::SharedDNSAction { + action: tmp_action, + }); + } + Action::SetAdditionalProxyProtocolValue(config) => { + let tmp_action = dnsdistsettings::getSetAdditionalProxyProtocolValueAction(&config); + return Some(dnsdistsettings::SharedDNSAction { + action: tmp_action, + }); + } + Action::SetDisableECS(config) => { + let tmp_action = dnsdistsettings::getSetDisableECSAction(&config); + return Some(dnsdistsettings::SharedDNSAction { + action: tmp_action, + }); + } + Action::SetDisableValidation(config) => { + let tmp_action = dnsdistsettings::getSetDisableValidationAction(&config); + return Some(dnsdistsettings::SharedDNSAction { + action: tmp_action, + }); + } + Action::SetECS(config) => { + let tmp_action = dnsdistsettings::getSetECSAction(&config); + return Some(dnsdistsettings::SharedDNSAction { + action: tmp_action, + }); + } + Action::SetECSOverride(config) => { + let tmp_action = dnsdistsettings::getSetECSOverrideAction(&config); + return Some(dnsdistsettings::SharedDNSAction { + action: tmp_action, + }); + } + Action::SetECSPrefixLength(config) => { + let tmp_action = dnsdistsettings::getSetECSPrefixLengthAction(&config); + return Some(dnsdistsettings::SharedDNSAction { + action: tmp_action, + }); + } + Action::SetExtendedDNSError(config) => { + let tmp_action = dnsdistsettings::getSetExtendedDNSErrorAction(&config); + return Some(dnsdistsettings::SharedDNSAction { + action: tmp_action, + }); + } + Action::SetMacAddr(config) => { + let tmp_action = dnsdistsettings::getSetMacAddrAction(&config); + return Some(dnsdistsettings::SharedDNSAction { + action: tmp_action, + }); + } + Action::SetMaxReturnedTTL(config) => { + let tmp_action = dnsdistsettings::getSetMaxReturnedTTLAction(&config); + return Some(dnsdistsettings::SharedDNSAction { + action: tmp_action, + }); + } + Action::SetNoRecurse(config) => { + let tmp_action = dnsdistsettings::getSetNoRecurseAction(&config); + return Some(dnsdistsettings::SharedDNSAction { + action: tmp_action, + }); + } + Action::SetProxyProtocolValues(config) => { + let tmp_action = dnsdistsettings::getSetProxyProtocolValuesAction(&config); + return Some(dnsdistsettings::SharedDNSAction { + action: tmp_action, + }); + } + Action::SetSkipCache(config) => { + let tmp_action = dnsdistsettings::getSetSkipCacheAction(&config); + return Some(dnsdistsettings::SharedDNSAction { + action: tmp_action, + }); + } + Action::SetTag(config) => { + let tmp_action = dnsdistsettings::getSetTagAction(&config); + return Some(dnsdistsettings::SharedDNSAction { + action: tmp_action, + }); + } + Action::SetTempFailureCacheTTL(config) => { + let tmp_action = dnsdistsettings::getSetTempFailureCacheTTLAction(&config); + return Some(dnsdistsettings::SharedDNSAction { + action: tmp_action, + }); + } + Action::SNMPTrap(config) => { + let tmp_action = dnsdistsettings::getSNMPTrapAction(&config); + return Some(dnsdistsettings::SharedDNSAction { + action: tmp_action, + }); + } + Action::Spoof(config) => { + let tmp_action = dnsdistsettings::getSpoofAction(&config); + return Some(dnsdistsettings::SharedDNSAction { + action: tmp_action, + }); + } + Action::SpoofCNAME(config) => { + let tmp_action = dnsdistsettings::getSpoofCNAMEAction(&config); + return Some(dnsdistsettings::SharedDNSAction { + action: tmp_action, + }); + } + Action::SpoofPacket(config) => { + let tmp_action = dnsdistsettings::getSpoofPacketAction(&config); + return Some(dnsdistsettings::SharedDNSAction { + action: tmp_action, + }); + } + Action::SpoofRaw(config) => { + let tmp_action = dnsdistsettings::getSpoofRawAction(&config); + return Some(dnsdistsettings::SharedDNSAction { + action: tmp_action, + }); + } + Action::SpoofSVC(config) => { + let tmp_action = dnsdistsettings::getSpoofSVCAction(&config); + return Some(dnsdistsettings::SharedDNSAction { + action: tmp_action, + }); + } + Action::TC(config) => { + let tmp_action = dnsdistsettings::getTCAction(&config); + return Some(dnsdistsettings::SharedDNSAction { + action: tmp_action, + }); + } + Action::Tee(config) => { + let tmp_action = dnsdistsettings::getTeeAction(&config); + return Some(dnsdistsettings::SharedDNSAction { + action: tmp_action, + }); + } + } + None +} +fn get_one_response_action_from_serde(action: &ResponseAction) -> Option { + match action { + ResponseAction::Default => {} + ResponseAction::Allow(config) => { + let tmp_action = dnsdistsettings::getAllowResponseAction(&config); + return Some(dnsdistsettings::SharedDNSResponseAction { + action: tmp_action, + }); + } + ResponseAction::ClearRecordTypes(config) => { + let tmp_action = dnsdistsettings::getClearRecordTypesResponseAction(&config); + return Some(dnsdistsettings::SharedDNSResponseAction { + action: tmp_action, + }); + } + ResponseAction::Delay(config) => { + let tmp_action = dnsdistsettings::getDelayResponseAction(&config); + return Some(dnsdistsettings::SharedDNSResponseAction { + action: tmp_action, + }); + } + ResponseAction::DnstapLog(config) => { + let tmp_action = dnsdistsettings::getDnstapLogResponseAction(&config); + return Some(dnsdistsettings::SharedDNSResponseAction { + action: tmp_action, + }); + } + ResponseAction::Drop(config) => { + let tmp_action = dnsdistsettings::getDropResponseAction(&config); + return Some(dnsdistsettings::SharedDNSResponseAction { + action: tmp_action, + }); + } + ResponseAction::LimitTTL(config) => { + let tmp_action = dnsdistsettings::getLimitTTLResponseAction(&config); + return Some(dnsdistsettings::SharedDNSResponseAction { + action: tmp_action, + }); + } + ResponseAction::Log(config) => { + let tmp_action = dnsdistsettings::getLogResponseAction(&config); + return Some(dnsdistsettings::SharedDNSResponseAction { + action: tmp_action, + }); + } + ResponseAction::Lua(config) => { + let tmp_action = dnsdistsettings::getLuaResponseAction(&config); + return Some(dnsdistsettings::SharedDNSResponseAction { + action: tmp_action, + }); + } + ResponseAction::LuaFFI(config) => { + let tmp_action = dnsdistsettings::getLuaFFIResponseAction(&config); + return Some(dnsdistsettings::SharedDNSResponseAction { + action: tmp_action, + }); + } + ResponseAction::LuaFFIPerThread(config) => { + let tmp_action = dnsdistsettings::getLuaFFIPerThreadResponseAction(&config); + return Some(dnsdistsettings::SharedDNSResponseAction { + action: tmp_action, + }); + } + ResponseAction::RemoteLog(config) => { + let tmp_action = dnsdistsettings::getRemoteLogResponseAction(&config); + return Some(dnsdistsettings::SharedDNSResponseAction { + action: tmp_action, + }); + } + ResponseAction::SetExtendedDNSError(config) => { + let tmp_action = dnsdistsettings::getSetExtendedDNSErrorResponseAction(&config); + return Some(dnsdistsettings::SharedDNSResponseAction { + action: tmp_action, + }); + } + ResponseAction::SetMaxReturnedTTL(config) => { + let tmp_action = dnsdistsettings::getSetMaxReturnedTTLResponseAction(&config); + return Some(dnsdistsettings::SharedDNSResponseAction { + action: tmp_action, + }); + } + ResponseAction::SetMaxTTL(config) => { + let tmp_action = dnsdistsettings::getSetMaxTTLResponseAction(&config); + return Some(dnsdistsettings::SharedDNSResponseAction { + action: tmp_action, + }); + } + ResponseAction::SetMinTTL(config) => { + let tmp_action = dnsdistsettings::getSetMinTTLResponseAction(&config); + return Some(dnsdistsettings::SharedDNSResponseAction { + action: tmp_action, + }); + } + ResponseAction::SetReducedTTL(config) => { + let tmp_action = dnsdistsettings::getSetReducedTTLResponseAction(&config); + return Some(dnsdistsettings::SharedDNSResponseAction { + action: tmp_action, + }); + } + ResponseAction::SetSkipCache(config) => { + let tmp_action = dnsdistsettings::getSetSkipCacheResponseAction(&config); + return Some(dnsdistsettings::SharedDNSResponseAction { + action: tmp_action, + }); + } + ResponseAction::SetTag(config) => { + let tmp_action = dnsdistsettings::getSetTagResponseAction(&config); + return Some(dnsdistsettings::SharedDNSResponseAction { + action: tmp_action, + }); + } + ResponseAction::SNMPTrap(config) => { + let tmp_action = dnsdistsettings::getSNMPTrapResponseAction(&config); + return Some(dnsdistsettings::SharedDNSResponseAction { + action: tmp_action, + }); + } + ResponseAction::TC(config) => { + let tmp_action = dnsdistsettings::getTCResponseAction(&config); + return Some(dnsdistsettings::SharedDNSResponseAction { + action: tmp_action, + }); + } + } + None +} +fn get_one_selector_from_serde(selector: &Selector) -> Option { + match selector { + Selector::Default => {} + Selector::All(all) => { + let tmp_selector = dnsdistsettings::getAllSelector(&all); + return Some(dnsdistsettings::SharedDNSSelector { + selector: tmp_selector, + }); + } + Selector::And(and) => { + let mut config: dnsdistsettings::AndSelectorConfiguration = Default::default(); + for sub_selector in &and.selectors { + let new_selector = get_one_selector_from_serde(&sub_selector); + if new_selector.is_some() { + config.selectors.push(new_selector.unwrap()); + } + } + return Some(dnsdistsettings::SharedDNSSelector { + selector: dnsdistsettings::getAndSelector(&config), + }); + } + Selector::ByName(byname) => { + let tmp_selector = dnsdistsettings::getByNameSelector(&byname); + return Some(dnsdistsettings::SharedDNSSelector { + selector: tmp_selector, + }); + } + Selector::DNSSEC(dnssec) => { + let tmp_selector = dnsdistsettings::getDNSSECSelector(&dnssec); + return Some(dnsdistsettings::SharedDNSSelector { + selector: tmp_selector, + }); + } + Selector::DSTPort(dstport) => { + let tmp_selector = dnsdistsettings::getDSTPortSelector(&dstport); + return Some(dnsdistsettings::SharedDNSSelector { + selector: tmp_selector, + }); + } + Selector::EDNSOption(ednsoption) => { + let tmp_selector = dnsdistsettings::getEDNSOptionSelector(&ednsoption); + return Some(dnsdistsettings::SharedDNSSelector { + selector: tmp_selector, + }); + } + Selector::EDNSVersion(ednsversion) => { + let tmp_selector = dnsdistsettings::getEDNSVersionSelector(&ednsversion); + return Some(dnsdistsettings::SharedDNSSelector { + selector: tmp_selector, + }); + } + Selector::ERCode(ercode) => { + let tmp_selector = dnsdistsettings::getERCodeSelector(&ercode); + return Some(dnsdistsettings::SharedDNSSelector { + selector: tmp_selector, + }); + } + Selector::HTTPHeader(httpheader) => { + let tmp_selector = dnsdistsettings::getHTTPHeaderSelector(&httpheader); + return Some(dnsdistsettings::SharedDNSSelector { + selector: tmp_selector, + }); + } + Selector::HTTPPath(httppath) => { + let tmp_selector = dnsdistsettings::getHTTPPathSelector(&httppath); + return Some(dnsdistsettings::SharedDNSSelector { + selector: tmp_selector, + }); + } + Selector::HTTPPathRegex(httppathregex) => { + let tmp_selector = dnsdistsettings::getHTTPPathRegexSelector(&httppathregex); + return Some(dnsdistsettings::SharedDNSSelector { + selector: tmp_selector, + }); + } + Selector::KeyValueStoreLookup(keyvaluestorelookup) => { + let tmp_selector = dnsdistsettings::getKeyValueStoreLookupSelector(&keyvaluestorelookup); + return Some(dnsdistsettings::SharedDNSSelector { + selector: tmp_selector, + }); + } + Selector::KeyValueStoreRangeLookup(keyvaluestorerangelookup) => { + let tmp_selector = dnsdistsettings::getKeyValueStoreRangeLookupSelector(&keyvaluestorerangelookup); + return Some(dnsdistsettings::SharedDNSSelector { + selector: tmp_selector, + }); + } + Selector::Lua(lua) => { + let tmp_selector = dnsdistsettings::getLuaSelector(&lua); + return Some(dnsdistsettings::SharedDNSSelector { + selector: tmp_selector, + }); + } + Selector::LuaFFI(luaffi) => { + let tmp_selector = dnsdistsettings::getLuaFFISelector(&luaffi); + return Some(dnsdistsettings::SharedDNSSelector { + selector: tmp_selector, + }); + } + Selector::LuaFFIPerThread(luaffiperthread) => { + let tmp_selector = dnsdistsettings::getLuaFFIPerThreadSelector(&luaffiperthread); + return Some(dnsdistsettings::SharedDNSSelector { + selector: tmp_selector, + }); + } + Selector::MaxQPS(maxqps) => { + let tmp_selector = dnsdistsettings::getMaxQPSSelector(&maxqps); + return Some(dnsdistsettings::SharedDNSSelector { + selector: tmp_selector, + }); + } + Selector::MaxQPSIP(maxqpsip) => { + let tmp_selector = dnsdistsettings::getMaxQPSIPSelector(&maxqpsip); + return Some(dnsdistsettings::SharedDNSSelector { + selector: tmp_selector, + }); + } + Selector::NetmaskGroup(netmaskgroup) => { + let tmp_selector = dnsdistsettings::getNetmaskGroupSelector(&netmaskgroup); + return Some(dnsdistsettings::SharedDNSSelector { + selector: tmp_selector, + }); + } + Selector::Not(not) => { + let mut config: dnsdistsettings::NotSelectorConfiguration = Default::default(); + let new_selector = get_one_selector_from_serde(&*not.selector); + if new_selector.is_some() { + config.selector = new_selector.unwrap(); + } + return Some(dnsdistsettings::SharedDNSSelector { + selector: dnsdistsettings::getNotSelector(&config), + }); + } + Selector::Opcode(opcode) => { + let tmp_selector = dnsdistsettings::getOpcodeSelector(&opcode); + return Some(dnsdistsettings::SharedDNSSelector { + selector: tmp_selector, + }); + } + Selector::Or(or) => { + let mut config: dnsdistsettings::OrSelectorConfiguration = Default::default(); + for sub_selector in &or.selectors { + let new_selector = get_one_selector_from_serde(&sub_selector); + if new_selector.is_some() { + config.selectors.push(new_selector.unwrap()); + } + } + return Some(dnsdistsettings::SharedDNSSelector { + selector: dnsdistsettings::getOrSelector(&config), + }); + } + Selector::PayloadSize(payloadsize) => { + let tmp_selector = dnsdistsettings::getPayloadSizeSelector(&payloadsize); + return Some(dnsdistsettings::SharedDNSSelector { + selector: tmp_selector, + }); + } + Selector::PoolAvailable(poolavailable) => { + let tmp_selector = dnsdistsettings::getPoolAvailableSelector(&poolavailable); + return Some(dnsdistsettings::SharedDNSSelector { + selector: tmp_selector, + }); + } + Selector::PoolOutstanding(pooloutstanding) => { + let tmp_selector = dnsdistsettings::getPoolOutstandingSelector(&pooloutstanding); + return Some(dnsdistsettings::SharedDNSSelector { + selector: tmp_selector, + }); + } + Selector::Proba(proba) => { + let tmp_selector = dnsdistsettings::getProbaSelector(&proba); + return Some(dnsdistsettings::SharedDNSSelector { + selector: tmp_selector, + }); + } + Selector::ProxyProtocolValue(proxyprotocolvalue) => { + let tmp_selector = dnsdistsettings::getProxyProtocolValueSelector(&proxyprotocolvalue); + return Some(dnsdistsettings::SharedDNSSelector { + selector: tmp_selector, + }); + } + Selector::QClass(qclass) => { + let tmp_selector = dnsdistsettings::getQClassSelector(&qclass); + return Some(dnsdistsettings::SharedDNSSelector { + selector: tmp_selector, + }); + } + Selector::QName(qname) => { + let tmp_selector = dnsdistsettings::getQNameSelector(&qname); + return Some(dnsdistsettings::SharedDNSSelector { + selector: tmp_selector, + }); + } + Selector::QNameLabelsCount(qnamelabelscount) => { + let tmp_selector = dnsdistsettings::getQNameLabelsCountSelector(&qnamelabelscount); + return Some(dnsdistsettings::SharedDNSSelector { + selector: tmp_selector, + }); + } + Selector::QNameSet(qnameset) => { + let tmp_selector = dnsdistsettings::getQNameSetSelector(&qnameset); + return Some(dnsdistsettings::SharedDNSSelector { + selector: tmp_selector, + }); + } + Selector::QNameSuffix(qnamesuffix) => { + let tmp_selector = dnsdistsettings::getQNameSuffixSelector(&qnamesuffix); + return Some(dnsdistsettings::SharedDNSSelector { + selector: tmp_selector, + }); + } + Selector::QNameWireLength(qnamewirelength) => { + let tmp_selector = dnsdistsettings::getQNameWireLengthSelector(&qnamewirelength); + return Some(dnsdistsettings::SharedDNSSelector { + selector: tmp_selector, + }); + } + Selector::QType(qtype) => { + let tmp_selector = dnsdistsettings::getQTypeSelector(&qtype); + return Some(dnsdistsettings::SharedDNSSelector { + selector: tmp_selector, + }); + } + Selector::RCode(rcode) => { + let tmp_selector = dnsdistsettings::getRCodeSelector(&rcode); + return Some(dnsdistsettings::SharedDNSSelector { + selector: tmp_selector, + }); + } + Selector::RD(rd) => { + let tmp_selector = dnsdistsettings::getRDSelector(&rd); + return Some(dnsdistsettings::SharedDNSSelector { + selector: tmp_selector, + }); + } + Selector::RE2(re2) => { + let tmp_selector = dnsdistsettings::getRE2Selector(&re2); + return Some(dnsdistsettings::SharedDNSSelector { + selector: tmp_selector, + }); + } + Selector::RecordsCount(recordscount) => { + let tmp_selector = dnsdistsettings::getRecordsCountSelector(&recordscount); + return Some(dnsdistsettings::SharedDNSSelector { + selector: tmp_selector, + }); + } + Selector::RecordsTypeCount(recordstypecount) => { + let tmp_selector = dnsdistsettings::getRecordsTypeCountSelector(&recordstypecount); + return Some(dnsdistsettings::SharedDNSSelector { + selector: tmp_selector, + }); + } + Selector::Regex(regex) => { + let tmp_selector = dnsdistsettings::getRegexSelector(®ex); + return Some(dnsdistsettings::SharedDNSSelector { + selector: tmp_selector, + }); + } + Selector::SNI(sni) => { + let tmp_selector = dnsdistsettings::getSNISelector(&sni); + return Some(dnsdistsettings::SharedDNSSelector { + selector: tmp_selector, + }); + } + Selector::Tag(tag) => { + let tmp_selector = dnsdistsettings::getTagSelector(&tag); + return Some(dnsdistsettings::SharedDNSSelector { + selector: tmp_selector, + }); + } + Selector::TCP(tcp) => { + let tmp_selector = dnsdistsettings::getTCPSelector(&tcp); + return Some(dnsdistsettings::SharedDNSSelector { + selector: tmp_selector, + }); + } + Selector::TrailingData(trailingdata) => { + let tmp_selector = dnsdistsettings::getTrailingDataSelector(&trailingdata); + return Some(dnsdistsettings::SharedDNSSelector { + selector: tmp_selector, + }); + } + } + None +} +// START INCLUDE ./rust-post-in.rs +fn get_selectors_from_serde( + selectors_from_serde: &Vec, +) -> Vec { + let mut results: Vec = Vec::new(); + + for rule in selectors_from_serde { + let selector = get_one_selector_from_serde(&rule); + if selector.is_some() { + results.push(selector.unwrap()); + } + } + results +} + +fn get_query_rules_from_serde( + rules_from_serde: &Vec, +) -> Vec { + let mut results: Vec = Vec::new(); + + for rule in rules_from_serde { + let selector = get_one_selector_from_serde(&rule.selector); + let action = get_one_action_from_serde(&rule.action); + if selector.is_some() && action.is_some() { + results.push(dnsdistsettings::QueryRuleConfiguration { + name: rule.name.clone(), + uuid: rule.uuid.clone(), + selector: selector.unwrap(), + action: action.unwrap(), + }); + } + } + results +} + +fn get_response_rules_from_serde( + rules_from_serde: &Vec, +) -> Vec { + let mut results: Vec = Vec::new(); + + for rule in rules_from_serde { + let selector = get_one_selector_from_serde(&rule.selector); + let action = get_one_response_action_from_serde(&rule.action); + if selector.is_some() && action.is_some() { + results.push(dnsdistsettings::ResponseRuleConfiguration { + name: rule.name.clone(), + uuid: rule.uuid.clone(), + selector: selector.unwrap(), + action: action.unwrap(), + }); + } + } + results +} + +fn register_remote_loggers( + config: &dnsdistsettings::RemoteLoggingConfiguration, +) { + for logger in &config.protobuf_loggers { + dnsdistsettings::registerProtobufLogger(&logger); + } + for logger in &config.dnstap_loggers { + dnsdistsettings::registerDnstapLogger(&logger); + } +} + +fn get_global_configuration_from_serde( + serde: GlobalConfigurationSerde, +) -> dnsdistsettings::GlobalConfiguration { + let mut config: dnsdistsettings::GlobalConfiguration = Default::default(); + config.key_value_stores = serde.key_value_stores; + config.webserver = serde.webserver; + config.console = serde.console; + config.edns_client_subnet = serde.edns_client_subnet; + config.dynamic_rules_settings = serde.dynamic_rules_settings; + config.dynamic_rules = serde.dynamic_rules; + config.acl = serde.acl; + config.ring_buffers = serde.ring_buffers; + config.binds = serde.binds; + config.backends = serde.backends; + config.cache_settings = serde.cache_settings; + config.security_polling = serde.security_polling; + config.general = serde.general; + config.packet_caches = serde.packet_caches; + config.proxy_protocol = serde.proxy_protocol; + config.snmp = serde.snmp; + config.query_count = serde.query_count; + config.load_balancing_policies = serde.load_balancing_policies; + config.pools = serde.pools; + config.metrics = serde.metrics; + config.remote_logging = serde.remote_logging; + config.tuning = serde.tuning; + // this needs to be done before the rules so that they can refer to the loggers + register_remote_loggers(&config.remote_logging); + // this needs to be done before the rules so that they can refer to the KVS objects + dnsdistsettings::registerKVSObjects(&config.key_value_stores); + // this needs to be done BEFORE the rules so that they can refer to the selectors + // by name + config.selectors = get_selectors_from_serde(&serde.selectors); + config.query_rules = get_query_rules_from_serde(&serde.query_rules); + config.cache_miss_rules = get_query_rules_from_serde(&serde.cache_miss_rules); + config.response_rules = get_response_rules_from_serde(&serde.response_rules); + config.cache_hit_response_rules = get_response_rules_from_serde(&serde.cache_hit_response_rules); + config.cache_inserted_response_rules = get_response_rules_from_serde(&serde.cache_inserted_response_rules); + config.self_answered_response_rules = get_response_rules_from_serde(&serde.self_answered_response_rules); + config.xfr_response_rules = get_response_rules_from_serde(&serde.xfr_response_rules); + config +} + +pub fn from_yaml_string( + str: &str, +) -> Result { + let serde_config: Result = + serde_yaml::from_str(str); + + if !serde_config.is_err() { + let validation_result = serde_config.as_ref().unwrap().validate(); + if let Err(e) = validation_result { + println!("Error validating the configuration loaded from {}: {}", str, e); + } + } + let config: dnsdistsettings::GlobalConfiguration = + get_global_configuration_from_serde(serde_config?); + return Ok(config); +} +// END INCLUDE ./rust-post-in.rs diff --git a/pdns/dnsdistdist/dnsdist-selectors-definitions.yml b/pdns/dnsdistdist/dnsdist-selectors-definitions.yml new file mode 100644 index 000000000000..c2cdee2163bf --- /dev/null +++ b/pdns/dnsdistdist/dnsdist-selectors-definitions.yml @@ -0,0 +1,409 @@ +--- +- name: "all" + description: "Matches all traffic" +- name: "And" + skip-cpp: true + skip-rust: true + skip-serde: true + description: "Matches traffic if all selectors match" + parameters: + - name: "selectors" + type: "Vec" + default: true + description: "List of selectors" +- name: "ByName" + skip-cpp: true + skip-rust: true + no-lua-equivalent: true + description: "References an already declared selector by its name" + parameters: + - name: "selector_name" + type: "String" + descripton: "The selector name" +- name: "DNSSEC" + description: "Matches queries with the DO flag set" +- name: "DSTPort" + description: "Matches questions received to the destination port" + parameters: + - name: "port" + type: "u16" + description: "Match destination port" +- name: "EDNSOption" + description: "Matches queries or responses with the specified EDNS option present" + parameters: + - name: "option_code" + type: "u16" + description: "The option code as an integer" +- name: "EDNSVersion" + description: "Matches queries or responses with an OPT record whose EDNS version is greater than the specified EDNS version" + parameters: + - name: "version" + type: "u8" + description: "The EDNS version to match on" +- name: "ERCode" + description: "Matches queries or responses with the specified rcode. The full 16bit RCode will be matched. If no EDNS OPT RR is present, the upper 12 bits are treated as 0" + parameters: + - name: "rcode" + type: "u64" + description: "The full 16bit RCode will be matched. If no EDNS OPT RR is present, the upper 12 bits are treated as 0" +- name: "HTTPHeader" + description: "Matches DNS over HTTPS queries with a HTTP header name whose content matches the supplied regular expression. It is necessary to set the ``keepIncomingHeaders`` to :func:`addDOHLocal()` to use this rule" + parameters: + - name: "header" + type: "String" + description: "The case-insensitive name of the HTTP header to match on" + - name: "expression" + type: "String" + description: "A regular expression to match the content of the specified header" + +- name: "HTTPPath" + description: "Matches DNS over HTTPS queries with a specific HTTP path. For example, if the query has been sent to the https://192.0.2.1:443/PowerDNS?dns=... URL, the path would be '/PowerDNS'. Only valid DNS over HTTPS queries are matched. If you want to match all HTTP queries, see :meth:`DOHFrontend:setResponsesMap` instead" + parameters: + - name: "path" + type: "String" + description: "The exact HTTP path to match on" +- name: "HTTPPathRegex" + description: | + Matches DNS over HTTPS queries with a path matching the supplied regular expression. For example, if the query has been sent to the https://192.0.2.1:443/PowerDNS?dns=... URL, the path would be '/PowerDNS'. + Only valid DNS over HTTPS queries are matched. If you want to match all HTTP queries, see :meth:`DOHFrontend:setResponsesMap` instead + parameters: + - name: "expression" + type: "String" + description: "The regex to match on" +- name: "KeyValueStoreLookup" + skip-cpp: true + skip-rust: true + description: "Matches if the key returned by ``lookup_key_name`` exists in the key value store" + parameters: + - name: "kvs_name" + type: "String" + description: "The key value store to query" + - name: "lookup_key_name" + type: "String" + description: "The key to use for the lookup" +- name: "KeyValueStoreRangeLookup" + skip-cpp: true + skip-rust: true + description: "Does a range-based lookup into the key value store using the key returned by ``lookup_key_name`` and matches if there is a range covering that key. +This assumes that there is a key, in network byte order, for the last element of the range (for example ``2001:0db8:ffff:ffff:ffff:ffff:ffff:ffff`` for ``2001:db8::/32``) which contains the first element of the range (``2001:0db8:0000:0000:0000:0000:0000:0000``) (optionally followed by any data) as value, still in network byte order, and that there is no overlapping ranges in the database. This requires that the underlying store supports ordered keys, which is true for ``LMDB`` but not for ``CDB``" + parameters: + - name: "kvs_name" + type: "String" + description: "The key value store to query" + - name: "lookup_key_name" + type: "String" + description: "The key to use for the lookup" +- name: "lua" + description: "Invoke a Lua function that accepts a :class:`DNSQuestion` object. The function should return true if the query matches, or false otherwise. If the Lua code fails, false is returned" + skip-cpp: true + skip-rust: true + parameters: + - name: "function_name" + type: "String" + default: "" + description: "The name of the Lua function" + - name: "function_code" + type: "String" + default: "" + description: "The code of the Lua function" + - name: "function_file" + type: "String" + default: "" + description: "The path to a file containing the code of the Lua function" +- name: "LuaFFI" + description: "Invoke a Lua FFI function that accepts a pointer to a ``dnsdist_ffi_dnsquestion_t`` object, whose bindings are defined in ``dnsdist-lua-ffi-interface.h``. The function should return true if the query matches, or false otherwise. If the Lua code fails, false is returned" + skip-cpp: true + skip-rust: true + parameters: + - name: "function_name" + type: "String" + default: "" + description: "The name of the Lua function" + - name: "function_code" + type: "String" + default: "" + description: "The code of the Lua function" + - name: "function_file" + type: "String" + default: "" + description: "The path to a file containing the code of the Lua function" +- name: "LuaFFIPerThread" + description: "Invoke a Lua FFI function that accepts a pointer to a ``dnsdist_ffi_dnsquestion_t`` object, whose bindings are defined in ``dnsdist-lua-ffi-interface.h``. The function should return true if the query matches, or false otherwise. If the Lua code fails, false is returned. + +The function will be invoked in a per-thread Lua state, without access to the global Lua state. All constants (:ref:`DNSQType`, :ref:`DNSRCode`, ...) are available in that per-thread context, as well as all FFI functions. Objects and their bindings that are not usable in a FFI context (:class:`DNSQuestion`, :class:`DNSDistProtoBufMessage`, :class:`PacketCache`, ...) are not available" + parameters: + - name: "code" + type: "String" + description: "The code of the Lua function" +- name: "MaxQPS" + description: "Matches traffic not exceeding this qps limit. If e.g. this is set to 50, starting at the 51st query of the current second traffic stops being matched. This can be used to enforce a global QPS limit" + parameters: + - name: "qps" + type: "u32" + description: "The number of queries per second allowed, above this number the traffic is **not** matched anymore" + - name: "burst" + type: "u32" + default: 0 + description: "The number of burstable queries per second allowed. Default is same as qps" +- name: "MaxQPSIP" + description: "Matches traffic for a subnet specified by the v4 or v6 mask exceeding ``qps`` queries per second up to ``burst`` allowed. This rule keeps track of QPS by netmask or source IP. This state is cleaned up regularly if ``cleanup_delay`` is greater than zero, removing existing netmasks or IP addresses that have not been seen in the last ``expiration`` seconds." + parameters: + - name: "qps" + type: "u32" + description: "The number of queries per second allowed, above this number traffic is matched" + - name: "ipv4_mask" + type: "u8" + default: 32 + description: "The IPv4 netmask to match on. Default is 32 (the whole address)" + - name: "ipv6_mask" + type: "u8" + default: 64 + description: "he IPv6 netmask to match on" + - name: "burst" + type: "u32" + default: 0 + description: "The number of burstable queries per second allowed. Default is same as qps" + - name: "expiration" + type: "u32" + default: 300 + description: "How long to keep netmask or IP addresses after they have last been seen, in seconds" + - name: "cleanup_delay" + type: "u32" + default: 60 + description: "The number of seconds between two cleanups" + - name: "scan_fraction" + type: "u32" + default: 10 + description: "he maximum fraction of the store to scan for expired entries, for example 5 would scan at most 20% of it" + - name: "shards" + type: "u32" + default: 10 + description: "How many shards to use, to decrease lock contention between threads. Default is 10 and is a safe default unless a very high number of threads are used to process incoming queries" +- name: "NetmaskGroup" + skip-cpp: true + skip-rust: true + description: "Matches traffic from/to the network range specified in either the supplied :class:`NetmaskGroup` object or the list of ``netmasks``. +Set the ``source`` parameter to ``false`` to match against destination address instead of source address. This can be used to differentiate between clients" + parameters: + - name: "netmask_group_name" + type: "String" + default: "" + description: "The name of the netmask group object to use" + - name: "netmasks" + type: "Vec" + default: "" + description: "A list of netmasks to use instead of an existing netmask group object" + - name: "source" + type: "bool" + default: "true" + description: "Whether to match source or destination address of the packet. Defaults to true (matches source)" + - name: "quiet" + type: "bool" + default: "false" + description: "Do not display the list of matched netmasks in Rules. Default is false." +- name: "Not" + description: "Matches the traffic if the selector rule does not match" + skip-cpp: true + skip-rust: true + skip-serde: true + parameters: + - name: "selector" + type: "Selector" + description: "The list of selectors" +- name: "opcode" + description: "Matches queries with opcode equals to ``code``" + parameters: + - name: "code" + type: "u8" + description: "The opcode to match" +- name: "Or" + description: "Matches the traffic if one or more of the selectors Rules does match" + skip-cpp: true + skip-rust: true + skip-serde: true + parameters: + - name: "selectors" + type: "Vec" + default: true + description: "The list of selectors" +- name: "PayloadSize" + description: "Matches queries or responses whose DNS payload size fits the given comparison" + parameters: + - name: "comparison" + type: "String" + description: "The comparison operator to use" + supported-values: ["equal", "greater", "greaterOrEqual", "smaller", "smallerOrEqual"] + - name: "size" + type: "u16" + description: "The size to compare to" +- name: "PoolAvailable" + description: "Check whether a pool has any servers available to handle queries" + parameters: + - name: "pool" + type: "String" + description: "The name of the pool" +- name: "PoolOutstanding" + description: "Check whether a pool has total outstanding queries above limit" + parameters: + - name: "pool" + type: "String" + description: "The name of the pool" + - name: "max_outstanding" + type: "u64" + description: "The maximum number of outstanding queries in that pool" +- name: "proba" + description: "Matches queries with a given probability. 1.0 means ``always``" + parameters: + - name: "probability" + type: "f64" + description: "Probability of a match" +- name: "ProxyProtocolValue" + description: "Matches queries that have a proxy protocol TLV value of the specified type. If ``option_value`` is set, the content of the value should also match the content of value" + parameters: + - name: "option_type" + type: "u8" + description: "The type of the value, ranging from 0 to 255 (both included)" + - name: "option_value" + type: "String" + default: "" + description: "The optional binary-safe value to match" +- name: "QClass" + description: "Matches queries with the specified qclass. The class can be specified as a numerical value or as a string" + skip-cpp: true + parameters: + - name: "qclass" + type: "String" + default: "" + description: "The Query Class to match on, as a string" + - name: "numeric_value" + type: "u16" + default: 0 + description: "The Query Class to match on, as an integer" +- name: "QName" + description: "Matches queries with the specified qname exactly" + skip-cpp: true + skip-rust: true + parameters: + - name: "qname" + type: "String" + description: "Qname to match" +- name: "QNameLabelsCount" + description: "Matches if the qname has less than ``min_labels_count`` or more than ``max_labels_count`` labels" + parameters: + - name: "min_labels_count" + type: "u16" + description: "Minimum number of labels" + - name: "max_labels_count" + type: "u16" + description: "Maximum number of labels" +- name: "QNameSet" + description: "Matches if the set contains exact qname. To match subdomain names, see :ref:`yaml-settings-QNameSuffixSelector`" + skip-cpp: true + skip-rust: true + parameters: + - name: "qnames" + type: "Vec" + description: "List of qnames" +- name: "QNameSuffix" + description: "Matches based on a group of domain suffixes for rapid testing of membership. Pass true to ``quiet`` to prevent listing of all domains matched in the console or the web interface" + skip-cpp: true + skip-rust: true + parameters: + - name: "suffixes" + type: "Vec" + description: "List of suffixes" + - name: "quiet" + type: "bool" + default: "false" + description: "Do not display the list of matched domains in Rules" +- name: "QNameWireLength" + description: "Matches if the qname’s length on the wire is less than ``min`` or more than ``max`` bytes." + parameters: + - name: "min" + type: "u16" + description: "Minimum number of bytes" + - name: "max" + type: "u16" + description: "Maximum number of bytes" +- name: "QType" + description: "Matches queries with the specified qtype, which can be supplied as a String or as a numerical value" + skip-cpp: true + parameters: + - name: "qtype" + type: "String" + description: "The qtype, as a string" + - name: "numeric_value" + type: "u16" + default: 0 + description: "The qtype, as a numerical value" +- name: "RCode" + description: "Matches queries or responses with the specified rcode" + parameters: + - name: "rcode" + type: "u64" + description: "The response code, as a numerical value" +- name: "RD" + description: "Matches queries with the RD flag set" +- name: "RE2" + description: "Matches the query name against the supplied regex using the RE2 engine" + parameters: + - name: "expression" + type: "String" + description: "The regular expression to match the QNAME" +- name: "RecordsCount" + description: "Matches if there is at least ``minimum`` and at most ``maximum`` records in the ``section`` section. ``section`` is specified as an integer with ``0`` being the question section, ``1`` answer, ``2`` authority and ``3`` additional" + parameters: + - name: "section" + type: "u8" + description: "The section to match on" + - name: "minimum" + type: "u16" + description: "The minimum number of entries" + - name: "maximum" + type: "u16" + description: "The maximum number of entries" +- name: "RecordsTypeCount" + description: "Matches if there is at least ``minimum`` and at most ``maximum`` records of type ``record_type`` in the section ``section``. ``section`` is specified as an integer with ``0`` being the question section, ``1`` answer, ``2`` authority and ``3`` additional" + parameters: + - name: "section" + type: "u8" + description: "The section to match on" + - name: "record_type" + type: "u16" + description: "The record type to match on" + - name: "minimum" + type: "u16" + description: "The minimum number of entries" + - name: "maximum" + type: "u16" + description: "The maximum number of entries" +- name: "regex" + description: "Matches the query name against the supplied regular expression" + parameters: + - name: "expression" + type: "String" + description: "The regular expression to match the QNAME" +- name: "SNI" + description: "Matches against the TLS Server Name Indication value sent by the client, if any. Only makes sense for DoT or DoH, and for that last one matching on the HTTP Host header using :ref:`yaml-settings-HTTPHeaderSelector` might provide more consistent results" + parameters: + - name: "server_name" + type: "String" + description: "The exact Server Name Indication value" +- name: "Tag" + description: "Matches question or answer with a tag named ``tag`` set. If ``value`` is specified, the existing tag value should match too" + parameters: + - name: "tag" + type: "String" + description: "The name of the tag that has to be set" + - name: "value" + type: "String" + default: "" + description: "If set, the value the tag has to be set to" +- name: "TCP" + description: "Matches question received over TCP if ``tcp`` is true, over UDP otherwise" + parameters: + - name: "tcp" + type: "bool" + description: "Match TCP traffic if true, UDP traffic if false" +- name: "TrailingData" + description: "Matches if the query has trailing data" diff --git a/pdns/dnsdistdist/dnsdist-selectors-factory-generated.cc b/pdns/dnsdistdist/dnsdist-selectors-factory-generated.cc new file mode 100644 index 000000000000..d9f7e15a1342 --- /dev/null +++ b/pdns/dnsdistdist/dnsdist-selectors-factory-generated.cc @@ -0,0 +1,121 @@ +// !! This file has been generated by dnsdist-rules-generator.py, do not edit by hand!! +std::shared_ptr getAllSelector() +{ + return std::make_shared(); +} +std::shared_ptr getDNSSECSelector() +{ + return std::make_shared(); +} +std::shared_ptr getDSTPortSelector(uint16_t port) +{ + return std::make_shared(port); +} +std::shared_ptr getEDNSOptionSelector(uint16_t option_code) +{ + return std::make_shared(option_code); +} +std::shared_ptr getEDNSVersionSelector(uint8_t version) +{ + return std::make_shared(version); +} +std::shared_ptr getERCodeSelector(uint64_t rcode) +{ + return std::make_shared(rcode); +} +std::shared_ptr getHTTPHeaderSelector(const std::string& header, const std::string& expression) +{ + return std::make_shared(header, expression); +} +std::shared_ptr getHTTPPathSelector(const std::string& path) +{ + return std::make_shared(path); +} +std::shared_ptr getHTTPPathRegexSelector(const std::string& expression) +{ + return std::make_shared(expression); +} +std::shared_ptr getLuaFFIPerThreadSelector(const std::string& code) +{ + return std::make_shared(code); +} +std::shared_ptr getMaxQPSSelector(uint32_t qps, std::optional burst) +{ + return std::make_shared(qps, burst ? *burst : 0); +} +std::shared_ptr getMaxQPSIPSelector(uint32_t qps, std::optional ipv4_mask, std::optional ipv6_mask, std::optional burst, std::optional expiration, std::optional cleanup_delay, std::optional scan_fraction, std::optional shards) +{ + return std::make_shared(qps, ipv4_mask ? *ipv4_mask : 32, ipv6_mask ? *ipv6_mask : 64, burst ? *burst : 0, expiration ? *expiration : 300, cleanup_delay ? *cleanup_delay : 60, scan_fraction ? *scan_fraction : 10, shards ? *shards : 10); +} +std::shared_ptr getOpcodeSelector(uint8_t code) +{ + return std::make_shared(code); +} +std::shared_ptr getPayloadSizeSelector(const std::string& comparison, uint16_t size) +{ + return std::make_shared(comparison, size); +} +std::shared_ptr getPoolAvailableSelector(const std::string& pool) +{ + return std::make_shared(pool); +} +std::shared_ptr getPoolOutstandingSelector(const std::string& pool, uint64_t max_outstanding) +{ + return std::make_shared(pool, max_outstanding); +} +std::shared_ptr getProbaSelector(double probability) +{ + return std::make_shared(probability); +} +std::shared_ptr getProxyProtocolValueSelector(uint8_t option_type, std::optional option_value) +{ + return std::make_shared(option_type, option_value ? *option_value : ""); +} +std::shared_ptr getQNameLabelsCountSelector(uint16_t min_labels_count, uint16_t max_labels_count) +{ + return std::make_shared(min_labels_count, max_labels_count); +} +std::shared_ptr getQNameWireLengthSelector(uint16_t min, uint16_t max) +{ + return std::make_shared(min, max); +} +std::shared_ptr getRCodeSelector(uint64_t rcode) +{ + return std::make_shared(rcode); +} +std::shared_ptr getRDSelector() +{ + return std::make_shared(); +} +std::shared_ptr getRE2Selector(const std::string& expression) +{ + return std::make_shared(expression); +} +std::shared_ptr getRecordsCountSelector(uint8_t section, uint16_t minimum, uint16_t maximum) +{ + return std::make_shared(section, minimum, maximum); +} +std::shared_ptr getRecordsTypeCountSelector(uint8_t section, uint16_t record_type, uint16_t minimum, uint16_t maximum) +{ + return std::make_shared(section, record_type, minimum, maximum); +} +std::shared_ptr getRegexSelector(const std::string& expression) +{ + return std::make_shared(expression); +} +std::shared_ptr getSNISelector(const std::string& server_name) +{ + return std::make_shared(server_name); +} +std::shared_ptr getTagSelector(const std::string& tag, std::optional value) +{ + return std::make_shared(tag, value ? *value : ""); +} +std::shared_ptr getTCPSelector(bool tcp) +{ + return std::make_shared(tcp); +} +std::shared_ptr getTrailingDataSelector() +{ + return std::make_shared(); +} diff --git a/pdns/dnsdistdist/dnsdist-selectors-factory-generated.hh b/pdns/dnsdistdist/dnsdist-selectors-factory-generated.hh new file mode 100644 index 000000000000..625358094ed0 --- /dev/null +++ b/pdns/dnsdistdist/dnsdist-selectors-factory-generated.hh @@ -0,0 +1,31 @@ +// !! This file has been generated by dnsdist-rules-generator.py, do not edit by hand!! +std::shared_ptr getAllSelector(); +std::shared_ptr getDNSSECSelector(); +std::shared_ptr getDSTPortSelector(uint16_t port); +std::shared_ptr getEDNSOptionSelector(uint16_t option_code); +std::shared_ptr getEDNSVersionSelector(uint8_t version); +std::shared_ptr getERCodeSelector(uint64_t rcode); +std::shared_ptr getHTTPHeaderSelector(const std::string& header, const std::string& expression); +std::shared_ptr getHTTPPathSelector(const std::string& path); +std::shared_ptr getHTTPPathRegexSelector(const std::string& expression); +std::shared_ptr getLuaFFIPerThreadSelector(const std::string& code); +std::shared_ptr getMaxQPSSelector(uint32_t qps, std::optional burst); +std::shared_ptr getMaxQPSIPSelector(uint32_t qps, std::optional ipv4_mask, std::optional ipv6_mask, std::optional burst, std::optional expiration, std::optional cleanup_delay, std::optional scan_fraction, std::optional shards); +std::shared_ptr getOpcodeSelector(uint8_t code); +std::shared_ptr getPayloadSizeSelector(const std::string& comparison, uint16_t size); +std::shared_ptr getPoolAvailableSelector(const std::string& pool); +std::shared_ptr getPoolOutstandingSelector(const std::string& pool, uint64_t max_outstanding); +std::shared_ptr getProbaSelector(double probability); +std::shared_ptr getProxyProtocolValueSelector(uint8_t option_type, std::optional option_value); +std::shared_ptr getQNameLabelsCountSelector(uint16_t min_labels_count, uint16_t max_labels_count); +std::shared_ptr getQNameWireLengthSelector(uint16_t min, uint16_t max); +std::shared_ptr getRCodeSelector(uint64_t rcode); +std::shared_ptr getRDSelector(); +std::shared_ptr getRE2Selector(const std::string& expression); +std::shared_ptr getRecordsCountSelector(uint8_t section, uint16_t minimum, uint16_t maximum); +std::shared_ptr getRecordsTypeCountSelector(uint8_t section, uint16_t record_type, uint16_t minimum, uint16_t maximum); +std::shared_ptr getRegexSelector(const std::string& expression); +std::shared_ptr getSNISelector(const std::string& server_name); +std::shared_ptr getTagSelector(const std::string& tag, std::optional value); +std::shared_ptr getTCPSelector(bool tcp); +std::shared_ptr getTrailingDataSelector(); diff --git a/pdns/dnsdistdist/dnsdist-self-answers.cc b/pdns/dnsdistdist/dnsdist-self-answers.cc new file mode 100644 index 000000000000..dd0ff0b119ec --- /dev/null +++ b/pdns/dnsdistdist/dnsdist-self-answers.cc @@ -0,0 +1,243 @@ +/* + * 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-self-answers.hh" + +#include "dnsdist-configuration.hh" +#include "dnsdist-ecs.hh" + +namespace dnsdist::self_answers +{ +static thread_local std::default_random_engine t_randomEngine; + +static void addRecordHeader(PacketBuffer& packet, size_t& position, uint16_t qclass, uint32_t ttl, QType qtype, uint16_t rdataLen) +{ + std::array recordstart{ + 0xc0, 0x0c, // compressed name + 0, 0, // QTYPE + 0, 0, // QCLASS + 0, 0, 0, 0, // TTL + 0, 0 // rdata length + }; + ttl = htonl(ttl); + qclass = htons(qclass); + qtype = htons(qtype); + rdataLen = htons(rdataLen); + static_assert(recordstart.size() == 12, "sizeof(recordstart) must be equal to 12, otherwise the above check is invalid"); + memcpy(&recordstart.at(2), &qtype, sizeof(qtype)); + memcpy(&recordstart.at(4), &qclass, sizeof(qclass)); + memcpy(&recordstart.at(6), &ttl, sizeof(ttl)); + memcpy(&recordstart.at(10), &rdataLen, sizeof(rdataLen)); + memcpy(&packet.at(position), recordstart.data(), recordstart.size()); + position += recordstart.size(); +} + +bool generateAnswerFromCNAME(DNSQuestion& dnsQuestion, const DNSName& cname, const dnsdist::ResponseConfig& responseConfig) +{ + QType qtype = QType::CNAME; + unsigned int totrdatalen = cname.getStorage().size(); + size_t numberOfRecords = 1U; + auto qnameWireLength = dnsQuestion.ids.qname.wirelength(); + if (dnsQuestion.getMaximumSize() < (sizeof(dnsheader) + qnameWireLength + 4 + numberOfRecords * 12 /* recordstart */ + totrdatalen)) { + return false; + } + + bool dnssecOK = false; + bool hadEDNS = false; + if (dnsdist::configuration::getCurrentRuntimeConfiguration().d_addEDNSToSelfGeneratedResponses && queryHasEDNS(dnsQuestion)) { + hadEDNS = true; + dnssecOK = ((dnsdist::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 + size_t position = sizeof(dnsheader) + qnameWireLength + 4; + + dnsdist::PacketMangling::editDNSHeaderFromPacket(dnsQuestion.getMutableData(), [responseConfig](dnsheader& header) { + header.qr = true; // for good measure + setResponseHeadersFromConfig(header, responseConfig); + header.ancount = 0; + header.arcount = 0; // for now, forget about your EDNS, we're marching over it + return true; + }); + + const auto& wireData = cname.getStorage(); // Note! This doesn't do compression! + addRecordHeader(data, position, dnsQuestion.ids.qclass, responseConfig.ttl, qtype, wireData.length()); + memcpy(&data.at(position), wireData.c_str(), wireData.length()); + + dnsdist::PacketMangling::editDNSHeaderFromPacket(dnsQuestion.getMutableData(), [numberOfRecords](dnsheader& header) { + header.ancount = htons(numberOfRecords); + return true; + }); + + if (hadEDNS) { + addEDNS(dnsQuestion.getMutableData(), dnsQuestion.getMaximumSize(), dnssecOK, dnsdist::configuration::getCurrentRuntimeConfiguration().d_payloadSizeSelfGenAnswers, 0); + } + + return true; +} + +bool generateAnswerFromIPAddresses(DNSQuestion& dnsQuestion, const std::vector& addresses, const dnsdist::ResponseConfig& responseConfig) +{ + uint16_t qtype = dnsQuestion.ids.qtype; + std::vector addrs = {}; + unsigned int totrdatalen = 0; + size_t numberOfRecords = 0; + for (const auto& addr : addresses) { + 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 = dnsQuestion.ids.qname.wirelength(); + if (dnsQuestion.getMaximumSize() < (sizeof(dnsheader) + qnameWireLength + 4 + numberOfRecords * 12 /* recordstart */ + totrdatalen)) { + return false; + } + + bool dnssecOK = false; + bool hadEDNS = false; + if (dnsdist::configuration::getCurrentRuntimeConfiguration().d_addEDNSToSelfGeneratedResponses && queryHasEDNS(dnsQuestion)) { + hadEDNS = true; + dnssecOK = ((dnsdist::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 + size_t position = sizeof(dnsheader) + qnameWireLength + 4; + + dnsdist::PacketMangling::editDNSHeaderFromPacket(dnsQuestion.getMutableData(), [responseConfig](dnsheader& header) { + header.qr = true; // for good measure + setResponseHeadersFromConfig(header, responseConfig); + header.ancount = 0; + header.arcount = 0; // for now, forget about your EDNS, we're marching over it + return true; + }); + + for (const auto& addr : addrs) { + uint16_t rdataLen = addr.sin4.sin_family == AF_INET ? sizeof(addr.sin4.sin_addr.s_addr) : sizeof(addr.sin6.sin6_addr.s6_addr); + qtype = addr.sin4.sin_family == AF_INET ? QType::A : QType::AAAA; + + addRecordHeader(data, position, dnsQuestion.ids.qclass, responseConfig.ttl, qtype, rdataLen); + + memcpy(&data.at(position), + // 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)); + + position += (addr.sin4.sin_family == AF_INET ? sizeof(addr.sin4.sin_addr.s_addr) : sizeof(addr.sin6.sin6_addr.s6_addr)); + } + + auto finalANCount = addrs.size(); + dnsdist::PacketMangling::editDNSHeaderFromPacket(dnsQuestion.getMutableData(), [finalANCount](dnsheader& header) { + header.ancount = htons(finalANCount); + return true; + }); + + if (hadEDNS) { + addEDNS(dnsQuestion.getMutableData(), dnsQuestion.getMaximumSize(), dnssecOK, dnsdist::configuration::getCurrentRuntimeConfiguration().d_payloadSizeSelfGenAnswers, 0); + } + + return true; +} + +bool generateAnswerFromRDataEntries(DNSQuestion& dnsQuestion, const std::vector& entries, std::optional typeForAny, const dnsdist::ResponseConfig& responseConfig) +{ + unsigned int totrdatalen = 0; + size_t numberOfRecords = 0; + auto shuffledEntries = entries; + for (const auto& entry : shuffledEntries) { + totrdatalen += entry.size(); + ++numberOfRecords; + } + if (shuffledEntries.size() > 1) { + shuffle(shuffledEntries.begin(), shuffledEntries.end(), t_randomEngine); + } + + auto qnameWireLength = dnsQuestion.ids.qname.wirelength(); + if (dnsQuestion.getMaximumSize() < (sizeof(dnsheader) + qnameWireLength + 4 + numberOfRecords * 12 /* recordstart */ + totrdatalen)) { + return false; + } + + bool dnssecOK = false; + bool hadEDNS = false; + if (dnsdist::configuration::getCurrentRuntimeConfiguration().d_addEDNSToSelfGeneratedResponses && queryHasEDNS(dnsQuestion)) { + hadEDNS = true; + dnssecOK = ((dnsdist::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 + size_t position = sizeof(dnsheader) + qnameWireLength + 4; + + dnsdist::PacketMangling::editDNSHeaderFromPacket(dnsQuestion.getMutableData(), [&responseConfig](dnsheader& header) { + header.qr = true; // for good measure + setResponseHeadersFromConfig(header, responseConfig); + header.ancount = 0; + header.arcount = 0; // for now, forget about your EDNS, we're marching over it + return true; + }); + + QType qtype = dnsQuestion.ids.qtype; + if (qtype == QType::ANY && typeForAny) { + qtype = *typeForAny; + } + + for (const auto& entry : shuffledEntries) { + uint16_t rdataLen = entry.size(); + addRecordHeader(data, position, dnsQuestion.ids.qclass, responseConfig.ttl, qtype, rdataLen); + memcpy(&data.at(position), entry.c_str(), entry.size()); + position += entry.size(); + } + + auto finalANCount = shuffledEntries.size(); + dnsdist::PacketMangling::editDNSHeaderFromPacket(dnsQuestion.getMutableData(), [finalANCount](dnsheader& header) { + header.ancount = htons(finalANCount); + return true; + }); + + if (hadEDNS) { + addEDNS(dnsQuestion.getMutableData(), dnsQuestion.getMaximumSize(), dnssecOK, dnsdist::configuration::getCurrentRuntimeConfiguration().d_payloadSizeSelfGenAnswers, 0); + } + + return true; +} + +bool generateAnswerFromRawPacket(DNSQuestion& dnsQuestion, const PacketBuffer& packet) +{ + auto questionId = dnsQuestion.getHeader()->id; + dnsQuestion.getMutableData() = packet; + dnsdist::PacketMangling::editDNSHeaderFromPacket(dnsQuestion.getMutableData(), [questionId](dnsheader& header) { + header.id = questionId; + return true; + }); + return true; +} + +} diff --git a/pdns/dnsdistdist/dnsdist-self-answers.hh b/pdns/dnsdistdist/dnsdist-self-answers.hh new file mode 100644 index 000000000000..5703db46488d --- /dev/null +++ b/pdns/dnsdistdist/dnsdist-self-answers.hh @@ -0,0 +1,32 @@ +/* + * 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-dnsparser.hh" + +namespace dnsdist::self_answers +{ +bool generateAnswerFromCNAME(DNSQuestion& dnsQuestion, const DNSName& cname, const dnsdist::ResponseConfig& responseConfig); +bool generateAnswerFromIPAddresses(DNSQuestion& dnsQuestion, const std::vector& addresses, const ResponseConfig& responseConfig); +bool generateAnswerFromRDataEntries(DNSQuestion& dnsQuestion, const std::vector& entries, std::optional typeForAny, const ResponseConfig& responseConfig); +bool generateAnswerFromRawPacket(DNSQuestion& dnsQuestion, const PacketBuffer& packet); +} diff --git a/pdns/dnsdistdist/dnsdist-settings-definitions.yml b/pdns/dnsdistdist/dnsdist-settings-definitions.yml new file mode 100644 index 000000000000..f46030fe4384 --- /dev/null +++ b/pdns/dnsdistdist/dnsdist-settings-definitions.yml @@ -0,0 +1,2052 @@ +--- +global: + skip-serde: true + parameters: + - name: "acl" + type: "Vec" + default: "127.0.0.0/8, 10.0.0.0/8, 100.64.0.0/10, 169.254.0.0/16, 192.168.0.0/16, 172.16.0.0/12, ::1/128, fc00::/7, fe80::/10" + description: "CIDR netmasks of the clients allowed to send DNS queries" + - name: "backends" + type: "Vec" + default: true + description: "List of backends" + - name: "binds" + type: "Vec" + default: true + description: "List of endpoints to accept queries on" + - name: "cache_hit_response_rules" + type: "Vec" + default: true + skip-serde: true + description: "List of rules executed on a cache hit" + - name: "cache_inserted_response_rules" + type: "Vec" + default: true + skip-serde: true + description: "List of rules executed after inserting a new response into the cache" + - name: "cache_miss_rules" + type: "Vec" + default: true + skip-serde: true + description: "List of rules executed after a cache miss" + - name: "cache_settings" + type: "CacheSettingsConfiguration" + default: true + description: "Caching-related settings" + - name: "console" + type: "ConsoleConfiguration" + default: true + description: "Console-related settings" + - name: "dynamic_rules" + type: "Vec" + default: true + description: "List of dynamic rules" + - name: "dynamic_rules_settings" + type: "DynamicRulesSettingsConfiguration" + default: true + description: "Dynamic rules-related settings" + - name: "ebpf" + type: "EbpfConfiguration" + default: true + description: "EBPF settings" + - name: "edns_client_subnet" + type: "EdnsClientSubnetConfiguration" + default: true + description: "EDNS Client Subnet-related settings" + - name: "general" + type: "GeneralConfiguration" + default: true + description: "General settings" + - name: "key_value_stores" + type: "KeyValueStoresConfiguration" + default: true + description: "Key-Value stores" + - name: "load_balancing_policies" + type: "LoadBalancingPoliciesConfiguration" + default: true + description: "Load-balancing policies" + - name: "logging" + type: "LoggingConfiguration" + default: true + description: "Logging settings" + - name: "metrics" + type: "MetricsConfiguration" + default: true + description: "Metrics-related settings" + - name: "packet_caches" + type: "Vec" + default: true + description: "Packet-cache definitions" + - name: "pools" + type: "Vec" + default: true + description: "Pools of backends" + - name: "proxy_protocol" + type: "ProxyProtocolConfiguration" + default: true + description: "Proxy-protocol-related settings" + - name: "query_count" + type: "QueryCountConfiguration" + default: true + description: "Queries counting-related settings" + - name: "query_rules" + type: "Vec" + default: true + skip-serde: true + description: "List of rules executed when a query is received" + - name: "remote_logging" + type: "RemoteLoggingConfiguration" + default: true + description: "Remote logging-related settings" + - name: "response_rules" + type: "Vec" + default: true + skip-serde: true + description: "List of rules executed when a response is received" + - name: "ring_buffers" + type: "RingBuffersConfiguration" + default: true + description: "In-memory ring buffer settings" + - name: "security_polling" + type: "SecurityPollingConfiguration" + default: true + description: "Automatic checking of outdated version" + - name: "selectors" + type: "Vec" + default: true + skip-serde: true + description: "List of selectors that can be reused in rules" + - name: "self_answered_response_rules" + type: "Vec" + default: true + skip-serde: true + description: "List of rules executed when a response is generated by DNSdist itself" + - name: "snmp" + type: "SnmpConfiguration" + default: true + description: "SNMP-related settings" + - name: "tuning" + type: "TuningConfiguration" + default: true + description: "Performance-related settings" + - name: "webserver" + type: "WebserverConfiguration" + default: true + description: "Internal web server configuration" + - name: "xfr_response_rules" + type: "Vec" + default: true + skip-serde: true + description: "List of rules executed when a XFR response is received" + - name: "xsk" + type: "Vec" + default: true + description: "List of AF_XDP / XSK objects" + +metrics: + description: "Metrics-related settings" + parameters: + - name: "carbon" + type: "Vec" + default: true + description: "List of Carbon endpoints to send metrics to" + +carbon: + description: "Carbon endpoint to send metrics to" + parameters: + - name: "address" + type: "String" + description: "Indicates the IP address where the statistics should be sent" + - name: "name" + type: "String" + default: "" + description: "An optional string specifying the hostname that should be used. If left empty, the system hostname is used" + - name: "interval" + type: u32 + default: 30 + description: "An optional unsigned integer indicating the interval in seconds between exports" + - name: "namespace" + rename: "name_space" + type: "String" + default: "" + description: "An optional string specifying the namespace name that should be used" + - name: "instance" + type: "String" + default: "" + description: "An optional string specifying the instance name that should be used" + +remote_logging: + description: "Queries and/or responses remote logging settings" + parameters: + - name: "protobuf_loggers" + type: "Vec" + default: true + description: "List of endpoints to send queries and/or responses data to, using the native PowerDNS format" + - name: "dnstap_loggers" + type: "Vec" + default: true + description: "List of endpoints to send queries and/or responses data to, using the dnstap format" + +protobuf_logger: + description: "Endpoint to send queries and/or responses data to, using the native PowerDNS format" + parameters: + - name: "name" + type: "String" + description: "Name of this endpoint" + - name: "address" + type: "String" + description: "An IP:PORT combination where the logger is listening" + - name: "timeout" + type: "u16" + default: 2 + description: "TCP connect timeout in seconds" + - name: "max_queued_entries" + type: "u64" + default: 100 + description: "Queue this many messages before dropping new ones (e.g. when the remote listener closes the connection)" + - name: "reconnect_wait_time" + type: "u8" + default: 1 + description: "Time in seconds between reconnection attempts" + +dnstap_logger: + description: "Endpoint to send queries and/or responses data to, using the dnstap format" + parameters: + - name: "name" + type: "String" + description: "Name of this endpoint" + - name: "transport" + type: "String" + description: "The dnstap transport to use" + supported-values: + - "unix" + - "tcp" + - name: "address" + type: "String" + description: "The address of the endpoint. If the transport is set to 'unix', the address should be local ``AF_UNIX`` socket path. Note that most platforms have a rather short limit on the length. Otherwise the address should be an IP:port" + - name: "buffer_hint" + type: "u64" + default: 0 + description: "The threshold number of bytes to accumulate in the output buffer before forcing a buffer flush. According to the libfstrm library, the minimum is 1024, the maximum is 65536, and the default is 8192" + - name: "flush_timeout" + type: "u64" + default: 0 + description: "The number of seconds to allow unflushed data to remain in the output buffer. According to the libfstrm library, the minimum is 1 second, the maximum is 600 seconds (10 minutes), and the default is 1 second" + - name: "input_queue_size" + type: "u64" + default: 0 + description: "The number of queue entries to allocate for each input queue. This value must be a power of 2. According to the fstrm library, the minimum is 2, the maximum is 16384, and the default is 512" + - name: "output_queue_size" + type: "u64" + default: 0 + description: "The number of queue entries to allocate for each output queue. According to the libfstrm library, the minimum is 2, the maximum is system-dependent and based on ``IOV_MAX``, and the default is 64" + - name: "queue_notify_threshold" + type: "u64" + default: 0 + description: "The number of outstanding queue entries to allow on an input queue before waking the I/O thread. According to the libfstrm library, the minimum is 1 and the default is 32" + - name: "reopen_interval" + type: "u64" + default: 0 + description: "The number of queue entries to allocate for each output queue. According to the libfstrm library, the minimum is 2, the maximum is system-dependent and based on IOV_MAX, and the default is 64" + +proto_buf_meta: + description: "Meta-data entry to be added to a Protocol Buffer message" + parameters: + - name: "key" + type: "String" + description: "Name of the meta entry" + - name: "value" + type: "String" + description: "Value of the meta entry" + +lmdb_kv_store: + description: "LMDB-based key-value store" + parameters: + - name: "name" + type: "String" + description: "The name of this object" + - name: "file_name" + type: "String" + description: "The path to an existing ``LMDB`` database created with ``MDB_NOSUBDIR``" + - name: "database_name" + type: "String" + description: "The name of the database to use" + - name: "no_lock" + type: "bool" + default: "false" + description: "Whether to open the database with the ``MDB_NOLOCK`` flag" + +cdb_kv_store: + description: "CDB-based key-value store" + parameters: + - name: "name" + type: "String" + description: "The name of this object" + - name: "file_name" + type: "String" + description: "The path to an existing CDB database" + - name: "refresh_delay" + type: "u32" + description: "The delay in seconds between two checks of the database modification time. 0 means disabled" + +kvs_lookup_key_source_ip: + description: "Lookup key that can be used with :ref:`yaml-settings-KeyValueStoreLookupAction` or :ref:`yaml-settings-KeyValueStoreLookupSelector`, will return the source IP of the client in network byte-order" + parameters: + - name: "name" + type: "String" + description: "The name of this lookup key" + - name: "v4_mask" + type: "u8" + default: "32" + description: "Mask applied to IPv4 addresses. Default is 32 (the whole address)" + - name: "v6_mask" + type: "u8" + default: "128" + description: "Mask applied to IPv6 addresses. Default is 128 (the whole address)" + - name: "include_port" + type: "bool" + default: "false" + description: "Whether to append the port (in network byte-order) after the address" + +kvs_lookup_key_qname: + description: "Lookup key that can be used with :ref:`yaml-settings-KeyValueStoreLookupAction` or :ref:`yaml-settings-KeyValueStoreLookupSelector`, will return the qname of the query in DNS wire format" + parameters: + - name: "name" + type: "String" + description: "The name of this lookup key" + - name: "wire_format" + type: "bool" + default: "true" + description: "Whether to do the lookup in wire format (default) or in plain text" + +kvs_lookup_key_suffix: + description: | + Lookup key that can be used with :ref:`yaml-settings-KeyValueStoreLookupAction` or :ref:`yaml-settings-KeyValueStoreLookupSelector`, will return a vector of keys based on the labels of the qname in DNS wire format or plain text. For example if the qname is sub.domain.powerdns.com. the following keys will be returned: + + - ``\\3sub\\6domain\\8powerdns\\3com\\0`` + - ``\\6domain\\8powerdns\\3com\\0`` + - ``\\8powerdns\\3com\\0`` + - ``\\3com\\0`` + - ``\\0`` + + If ``min_labels`` is set to a value larger than ``0`` the lookup will only be done as long as there is at least ``min_labels`` labels remaining. Taking back our previous example, it means only the following keys will be returned if ``min_labels`` is set to ``2``: + + - ``\\3sub\\6domain\\8powerdns\\3com\\0`` + - ``\\6domain\\8powerdns\\3com\\0`` + - ``\\8powerdns\\3com\\0`` + + parameters: + - name: "name" + type: "String" + description: "The name of this lookup key" + - name: "minimum_labels" + type: "u16" + default: "0" + description: "The minimum number of labels to do a lookup for. Default is 0 which means unlimited" + - name: "wire_format" + type: "bool" + default: "true" + description: "Whether to do the lookup in wire format (default) or in plain text" + +kvs_lookup_key_tag: + description: "Lookup key that can be used with :ref:`yaml-settings-KeyValueStoreLookupAction` or :ref:`yaml-settings-KeyValueStoreLookupSelector`, will return the value of the corresponding tag for this query, if it exists" + parameters: + - name: "name" + type: "String" + - name: "tag" + type: "String" + +kvs_lookup_keys: + description: "List of look keys that can be used with :ref:`yaml-settings-KeyValueStoreLookupAction` or :ref:`yaml-settings-KeyValueStoreLookupSelector`" + parameters: + - name: "source_ip_keys" + type: "Vec" + default: true + - name: "qname_keys" + type: "Vec" + default: true + - name: "suffix_keys" + type: "Vec" + default: true + - name: "tag_keys" + type: "Vec" + default: true + +key_value_stores: + description: "List of key-value stores that can be used with :ref:`yaml-settings-KeyValueStoreLookupAction` or :ref:`yaml-settings-KeyValueStoreLookupSelector`" + parameters: + - name: "lmdb" + type: "Vec" + default: true + description: "List of LMDB-based key-value stores" + - name: "cdb" + type: "Vec" + default: true + description: "List of CDB-based key-value stores" + - name: "lookup_keys" + type: "KvsLookupKeysConfiguration" + default: true + description: "List of lookup keys" + +webserver: + parameters: + - name: "listen_address" + type: "String" + default: "" + description: "IP address and port to listen on" + - name: "password" + type: "String" + default: "" + description: "The password used to access the internal webserver. Since 1.7.0 the password should be hashed and salted via the ``hashPassword()`` command" + - name: "api_key" + type: "String" + default: "" + description: "The API Key (set to an empty string do disable it). Since 1.7.0 the key should be hashed and salted via the ``hashPassword()`` command" + - name: "acl" + type: "Vec" + default: "127.0.0.1, ::1" + description: "List of network masks or IP addresses that are allowed to open a connection to the web server" + - name: "api_requires_authentication" + type: "bool" + default: "true" + description: "Whether access to the API (/api endpoints) requires a valid API key" + - name: "stats_require_authentication" + type: "bool" + default: "true" + description: "Whether access to the statistics (/metrics and /jsonstat endpoints) requires a valid password or API key" + - name: "dashboard_requires_authentication" + type: "bool" + default: "true" + description: "Whether access to the internal dashboard requires a valid password" + - name: "max_concurrent_connections" + type: "u32" + default: 100 + description: "The maximum number of concurrent web connections, or 0 which means an unlimited number" + - name: "hash_plaintext_credentials" + type: "bool" + default: "false" + description: "Whether passwords and API keys provided in plaintext should be hashed during startup, to prevent the plaintext versions from staying in memory. Doing so increases significantly the cost of verifying credentials" + - name: "custom_headers" + type: "Vec" + default: true + description: "List of custom HTTP headers to set in our responses" + - name: "api_configuration_directory" + type: "String" + default: "" + description: "A valid directory where the configuration files will be written by the API" + - name: "api_read_write" + type: "bool" + default: "false" + description: "Allow modifications via the API. Optionally saving these changes to disk. Modifications done via the API will not be written to the configuration by default and will not persist after a reload" + +console: + description: "Console-related settings" + parameters: + - name: "listen_address" + type: "String" + default: "" + description: "IP address and port to listen on for console connections" + - name: "key" + type: "String" + default: "" + description: "The shared secret used to secure connections between the console client and the server, generated via ``makeKey()``" + - name: "acl" + type: "Vec" + default: "127.0.0.1, ::1" + description: "List of network masks or IP addresses that are allowed to open a connection to the console server" + - name: "maximum_output_size" + type: "u32" + default: "10000000" + lua-name: "setConsoleOutputMaxMsgSize" + internal-field-name: "d_consoleOutputMsgMaxSize" + runtime-configurable: true + description: "Set the maximum size, in bytes, of a single console message" + - name: "log_connections" + type: "bool" + default: "true" + lua-name: "setConsoleConnectionsLogging" + internal-field-name: "d_logConsoleConnections" + runtime-configurable: true + description: "Whether to log the opening and closing of console connections" + - name: "max_concurrent_connections" + type: "u64" + default: "0" + lua-name: "setConsoleMaximumConcurrentConnections" + internal-field-name: "d_consoleMaxConcurrentConnections" + runtime-configurable: false + description: "Set the maximum number of concurrent console connection" + +ebpf_map: + description: "An ``eBPF`` map that is used to share data with kernel-land ``AF_XDP``/``XSK``, ``socket filter`` or ``XDP`` programs. Maps can be pinned to a filesystem path, which makes their content persistent across restarts and allows external programs to read their content and to add new entries. :program:`dnsdist` will try to load maps that are pinned to a filesystem path on startups, inheriting any existing entries, and fall back to creating them if they do not exist yet. Note that the user :program`dnsdist` is running under must have the right privileges to read and write to the given file, and to go through all the directories in the path leading to that file. The pinned path must be on a filesystem of type ``BPF``, usually below ``/sys/fs/bpf/``" + parameters: + - name: "max_entries" + type: "u32" + default: 0 + description: "Maximum number of entries in this map. 0 means no entry at all" + - name: "pinned_path" + type: "String" + default: "" + description: "The filesystem path this map should be pinned to" + +ebpf: + description: "``eBPF`` and ``XDP`` related settings" + parameters: + - name: "ipv4" + type: "EbpfMapConfiguration" + default: true + description: "IPv4 map" + - name: "ipv6" + type: "EbpfMapConfiguration" + default: true + description: "IPv6 map" + - name: "cidr_ipv4" + type: "EbpfMapConfiguration" + default: true + description: "IPv4 subnets map" + - name: "cidr_ipv6" + type: "EbpfMapConfiguration" + default: true + description: "IPv6 subnets map" + - name: "qnames" + type: "EbpfMapConfiguration" + default: true + description: "DNS names map" + - name: "external" + type: "bool" + default: "false" + description: "If set to true, :program:`dnsdist` does not load the internal ``eBPF`` program. This is useful for ``AF_XDP`` and ``XDP`` maps" + +edns_client_subnet: + description: "EDNS Client Subnet-related settings" + parameters: + - name: "override_existing" + lua-name: "setECSOverride" + internal-field-name: "d_ecsOverride" + runtime-configurable: true + type: "bool" + default: "false" + description: "When ``useClientSubnet`` in :func:`newServer()` or ``use_client_subnet`` in :ref:`yaml-settings-BackendConfiguration` are set, and :program:`dnsdist` adds an EDNS Client Subnet Client option to the query, override an existing option already present in the query, if any. Please see Passing the source address to the backend for more information. Note that it’s not recommended to enable this option in front of an authoritative server responding with EDNS Client Subnet information as mismatching data (ECS scopes) can confuse clients and lead to SERVFAIL responses on downstream nameservers" + - name: "source_prefix_v4" + lua-name: "setECSSourcePrefixV4" + internal-field-name: "d_ECSSourcePrefixV4" + runtime-configurable: true + type: "u8" + default: 32 + description: "When ``useClientSubnet`` in :func:`newServer()` or ``use_client_subnet`` in :ref:`yaml-settings-BackendConfiguration` are set, and :program:`dnsdist` adds an EDNS Client Subnet Client option to the query, truncate the requestor's IPv4 address to this number of bits" + - name: "source_prefix_v6" + lua-name: "setECSSourcePrefixV6" + internal-field-name: "d_ECSSourcePrefixV6" + runtime-configurable: true + type: "u8" + default: 56 + description: "When ``useClientSubnet`` in :func:`newServer()` or ``use_client_subnet`` in :ref:`yaml-settings-BackendConfiguration` are set, and :program:`dnsdist` adds an EDNS Client Subnet Client option to the query, truncate the requestor's IPv6 address to this number of bits" + +dynamic_rules_settings: + description: "Dynamic rules-related settings" + parameters: + - name: "purge_interval" + type: "u64" + default: "60" + lua-name: "setDynBlocksPurgeInterval" + internal-field-name: "d_dynBlocksPurgeInterval" + runtime-configurable: true + description: "Set at which interval, in seconds, the expired dynamic blocks entries will be effectively removed from the tree. Entries are not applied anymore as soon as they expire, but they remain in the tree for a while for performance reasons. Removing them makes the addition of new entries faster and frees up the memory they use. Setting this value to 0 disables the purging mechanism, so entries will remain in the tree" + - name: "default_action" + type: "String" + default: "Drop" + description: "Set which action is performed when a query is blocked" + supported-values: + - Drop + - NoOp + - NoRecurse + - NXDomain + - Refused + - Truncate + +dynamic_rule: + description: "Dynamic rule settings" + parameters: + - name: "type" + rename: "rule_type" + type: "String" + description: "The type of this rule" + supported-values: + - "query-rate" + - "rcode-rate" + - "rcode-ratio" + - "qtype-rate" + - "cache-miss-ratio" + - "response-byte-rate" + - name: "seconds" + type: "u32" + description: "Number of seconds the rule has been exceeded" + - name: "action_duration" + type: "u32" + description: "How long the action is going to be enforced" + - name: "comment" + type: "String" + description: "Comment describing why the action why taken" + - name: "rate" + type: "u32" + default: "0" + description: "For ``query-rate``, ``rcode-rate``, ``qtype-rate`` and ``response-byte-rate``, the rate that should be exceeded" + - name: "ratio" + type: "f64" + default: "0.0" + description: "For ``rcode-ratio``, ``qtype-ratio`` and ``cache-miss-ratio``, the ratio that should be exceeded" + - name: "action" + type: "String" + default: "drop" + description: "The action that will be taken once the rate or ratio is exceeded" + supported-values: + - "Drop" + - "NoNop" + - "NoRecurse" + - "NXDomain" + - "SetTag" + - "Truncate" + - "Refused" + - name: "warning_rate" + type: "u32" + default: "0" + description: "For ``query-rate``, ``rcode-rate``, ``qtype-rate`` and ``response-byte-rate``, the rate that should be exceeded for a warning to be logged, but no action enforced" + - name: "warning_ratio" + type: "f64" + default: "0.0" + description: "For ``rcode-ratio`` and ``cache-miss-ratio``, the ratio that should be exceeded for a warning to be logged, but no action enforced" + - name: "tag_name" + type: "String" + default: "" + descripton: "If ``action`` is set to ``SetTag``, the name of the tag that will be set" + - name: "tag_value" + type: "String" + default: "0" + description: "If ``action`` is set to ``SetTag``, the value that will be set" + - name: "visitor_function_name" + type: "String" + default: "" + description: "For ``suffix-match`` and ``suffix-match-ffi``, the name of the Lua visitor function to call for each label of every domain seen in recent queries and responses" + - name: "visitor_function_code" + type: "String" + default: "" + description: "For ``suffix-match`` and ``suffix-match-ffi``, the code of Lua visitor function for each label of every domain seen in recent queries and responses" + - name: "visitor_function_file" + type: "String" + default: "" + description: "For ``suffix-match`` and ``suffix-match-ffi``, a path to a file containing the code of Lua visitor function for each label of every domain seen in recent queries and responses" + - name: "rcode" + type: "String" + default: "" + description: "For ``rcode-rate`` and ``rcode-ratio``, the response code to match" + - name: "qtype" + type: "String" + default: "" + description: "For ``qtype-rate``, the query type to match" + - name: "minimum_number_of_responses" + type: "u32" + default: "0" + description: "For ``cache-miss-ratio`` and ``rcode-ratio``, the minimum number of responses to have received for this rule to apply" + - name: "minimum_global_cache_hit_ratio" + type: "f64" + default: "0.0" + description: "The minimum global cache-hit ratio (over all pools, so ``cache-hits`` / (``cache-hits`` + ``cache-misses``)) for a ``cache-miss-ratio`` rule to be applied" + +dynamic_rules: + description: "Group of dynamic rules" + parameters: + - name: "name" + type: "String" + description: "The name of this group of dynamic rules" + - name: "mask_ipv4" + type: "u8" + default: "32" + description: "Number of bits to keep for IPv4 addresses" + - name: "mask_ipv6" + type: "u8" + default: "64" + description: "Number of bits to keep for IPv6 addresses. In some scenarios it might make sense to block a whole /64 IPv6 range instead of a single address, for example" + - name: "mask_port" + type: u8 + default: "0" + description: "Number of bits of port to consider over IPv4, for CGNAT deployments. Default is 0 meaning that the port is not taken into account. For example passing ``2`` here, which only makes sense if the IPv4 parameter is set to ``32``, will split a given IPv4 address into four port ranges: ``0-16383``, ``16384-32767``, ``32768-49151`` and ``49152-65535``" + - name: "exclude_ranges" + type: "Vec" + default: "" + description: "Exclude this list of ranges, meaning that no dynamic block will ever be inserted for clients in that range. Default to empty, meaning rules are applied to all ranges. When used in combination with ``include_ranges`` the more specific entry wins" + - name: "include_ranges" + type: "Vec" + default: "" + description: "Include this list of ranges, meaning that dynamic rules will be inserted for clients in that range. When used in combination with ``exclude_ranges`` the more specific entry wins" + - name: "exclude_domains" + type: "Vec" + default: "" + description: "Exclude this list of domains, meaning that no dynamic rules will ever be inserted for this domain via ``suffix-match`` or ``suffix-match-ffi`` rules. Default to empty, meaning rules are applied to all domains" + - name: "rules" + type: "Vec" + description: "List of dynamic rules in this group" + +ring_buffers: + description: "Settings for in-memory ring buffers, that are used for live traffic inspection and dynamic rules" + parameters: + - name: "size" + type: "u64" + default: 10000 + description: "The maximum amount of queries to keep in the ringbuffer" + lua-name: "setRingBuffersSize" + internal-field-name: "d_ringsCapacity" + runtime-configurable: false + - name: "shards" + type: "u64" + default: 10 + description: "The number of shards to use to limit lock contention" + lua-name: "setRingBuffersSize" + internal-field-name: "d_ringsNumberOfShards" + runtime-configurable: false + - name: "lock_retries" + type: "u64" + default: 5 + description: "Set the number of shards to attempt to lock without blocking before giving up and simply blocking while waiting for the next shard to be available. Default to 5 if there is more than one shard, 0 otherwise" + lua-name: "setRingBuffersOptions" + internal-field-name: "d_ringsNbLockTries" + runtime-configurable: false + - name: "record_queries" + type: "bool" + default: "true" + description: "Whether to record queries in the ring buffers" + lua-name: "setRingBuffersOptions" + internal-field-name: "d_ringsRecordQueries" + runtime-configurable: false + - name: "record_responses" + type: "bool" + default: "true" + description: "Whether to record responses in the ring buffers" + lua-name: "setRingBuffersOptions" + internal-field-name: "d_ringsRecordResponses" + runtime-configurable: false + +incoming_tls_certificate_key_pair: + description: "A pair of TLS certificate and key, with an optional associated password" + parameters: + - name: "certificate" + type: "String" + description: "A path to a file containing the certificate, in ``PEM``, ``DER`` or ``PKCS12`` format" + - name: "key" + type: "String" + default: "" + description: "A path to a file containing the key corresponding to the certificate, in ``PEM``, ``DER`` or ``PKCS12`` format" + - name: "password" + type: "String" + default: "" + description: "Password protecting the PKCS12 file if appropriate" + +incoming_tls: + description: "TLS parameters for frontends" + parameters: + - name: "provider" + type: "String" + default: "OpenSSL" + description: "" + supported-values: + - "OpenSSL" + - "GnuTLS" + - name: "certificates" + type: "Vec" + default: true + description: "List of TLS certificates and their associated keys" + - name: "ciphers" + type: "String" + default: "" + description: "The TLS ciphers to use, in OpenSSL format. Note that ``ciphers_tls_13`` should be used for TLS 1.3" + - name: "ciphers_tls_13" + type: "String" + default: "" + description: "The TLS ciphers to use for TLS 1.3, in OpenSSL format" + - name: "minimum_version" + type: "String" + default: "tls1.0" + description: "The minimum version of the TLS protocol to support" + supported-values: + - "tls1.0" + - "tls1.1" + - "tls1.2" + - "tls1.3" + - name: "ticket_key_file" + type: "String" + default: "" + description: "The path to a file from where TLS tickets keys should be loaded, to support :rfc:`5077`. These keys should be rotated often and never written to persistent storage to preserve forward secrecy. The default is to generate a random key. dnsdist supports several tickets keys to be able to decrypt existing sessions after the rotation. See :doc:`../advanced/tls-sessions-management` for more information" + - name: "tickets_keys_rotation_delay" + type: "u32" + default: "43200" + description: "Set the delay before the TLS tickets key is rotated, in seconds. Default is 43200 (12h). A value of 0 disables the automatic rotation, which might be useful when ``ticket_key_file`` is used" + - name: "number_of_tickets_keys" + type: "u32" + default: "5" + description: "The maximum number of tickets keys to keep in memory at the same time. Only one key is marked as active and used to encrypt new tickets while the remaining ones can still be used to decrypt existing tickets after a rotation" + - name: "prefer_server_ciphers" + type: "bool" + default: "true" + description: "Whether to prefer the order of ciphers set by the server instead of the one set by the client. Default is true, meaning that the order of the server is used. For OpenSSL >= 1.1.1, setting this option also enables the temporary re-prioritization of the ChaCha20-Poly1305 cipher if the client prioritizes it" + - name: "session_timeout" + type: "u32" + default: "0" + description: "Set the TLS session lifetime in seconds, this is used both for TLS ticket lifetime and for sessions kept in memory" + - name: "session_tickets" + type: "bool" + default: "true" + description: "Whether session resumption via session tickets is enabled. Default is true, meaning tickets are enabled" + - name: "number_of_stored_sessions" + type: "u32" + default: "20480" + description: "The maximum number of sessions kept in memory at the same time. Default is 20480. Setting this value to 0 disables stored session entirely" + - name: "ocsp_response_files" + type: "Vec" + default: "" + description: "List of files containing OCSP responses, in the same order than the certificates and keys, that will be used to provide OCSP stapling responses" + - name: "key_log_file" + type: "String" + default: "" + description: "Write the TLS keys in the specified file so that an external program can decrypt TLS exchanges, in the format described in https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSS/Key_Log_Format. Note that this feature requires OpenSSL >= 1.1.1" + - name: "release_buffers" + type: "bool" + default: "true" + description: "Whether OpenSSL should release its I/O buffers when a connection goes idle, saving roughly 35 kB of memory per connection" + - name: "enable_renegotiation" + type: "bool" + default: "false" + description: "Whether secure TLS renegotiation should be enabled. Disabled by default since it increases the attack surface and is seldom used for DNS" + - name: "async_mode" + type: "bool" + default: "false" + description: "Whether to enable experimental asynchronous TLS I/O operations if the ``nghttp2`` library is used, ``OpenSSL`` is used as the TLS implementation and an asynchronous capable SSL engine (or provider) is loaded. See also :func:`loadTLSEngine` or :func:`loadTLSProvider` to load the engine (or provider)" + - name: "ktls" + type: "bool" + default: "false" + description: "Whether to enable the experimental kernel TLS support on Linux, if both the kernel and the OpenSSL library support it" + - name: "read_ahead" + type: "bool" + default: "true" + description: "When the TLS provider is set to OpenSSL, whether we tell the library to read as many input bytes as possible, which leads to better performance by reducing the number of syscalls" + - name: "proxy_protocol_outside_tls" + type: "bool" + default: "false" + description: "When the use of incoming proxy protocol is enabled, whether the payload is prepended after the start of the TLS session (so inside, meaning it is protected by the TLS layer providing encryption and authentication) or not (outside, meaning it is in clear-text). Default is false which means inside. Note that most third-party software like HAproxy expect the proxy protocol payload to be outside, in clear-text" + - name: "ignore_configuration_errors" + type: "bool" + default: "false" + description: "Ignore TLS configuration errors (such as invalid certificate path) and just issue a warning instead of aborting the whole process" + +outgoing_tls: + description: "TLS parameters for backends" + parameters: + - name: "provider" + type: "String" + default: "OpenSSL" + description: "" + supported-values: + - "OpenSSL" + - "GnuTLS" + - name: "subject_name" + type: "String" + default: "" + description: "The subject name passed in the SNI value of the TLS handshake, and against which to validate the certificate presented by the backend. Default is empty. If set this value supersedes any ``subject_addr`` one" + - name: "subject_address" + type: "String" + default: "" + description: "The subject IP address passed in the SNI value of the TLS handshake, and against which to validate the certificate presented by the backend" + - name: "validate_certificate" + type: "bool" + default: "true" + description: "Whether the certificate presented by the backend should be validated against the CA store (see ``ca_store``)" + - name: "ca_store" + type: "String" + default: "" + description: "Specifies the path to the CA certificate file, in PEM format, to use to check the certificate presented by the backend. Default is an empty string, which means to use the system CA store. Note that this directive is only used if ``validate_certificates`` is set" + - name: "ciphers" + type: "String" + default: "" + description: "The TLS ciphers to use. The exact format depends on the provider used. When the OpenSSL provider is used, ciphers for TLS 1.3 must be specified via ``ciphers_tls_13``" + - name: "ciphers_tls_13" + type: "String" + default: "" + description: "The ciphers to use for TLS 1.3, when the OpenSSL provider is used. When the GnuTLS provider is used, ``ciphers`` applies regardless of the TLS protocol and this setting is not used." + - name: "key_log_file" + type: "String" + default: "" + description: "Write the TLS keys in the specified file so that an external program can decrypt TLS exchanges, in the format described in https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSS/Key_Log_Format. Note that this feature requires OpenSSL >= 1.1.1" + - name: "release_buffers" + type: "bool" + default: "true" + description: "Whether OpenSSL should release its I/O buffers when a connection goes idle, saving roughly 35 kB of memory per connection" + - name: "enable_renegotiation" + type: "bool" + default: "false" + description: "Whether secure TLS renegotiation should be enabled. Disabled by default since it increases the attack surface and is seldom used for DNS" + - name: "ktls" + type: "bool" + default: "false" + description: "Whether to enable the experimental kernel TLS support on Linux, if both the kernel and the OpenSSL library support it. Default is false. Currently both DoT and DoH backend support this option" + +http_custom_response_header: + description: "List of custom HTTP headers" + parameters: + - name: "key" + type: "String" + description: "The key, or name, part of the header" + - name: "value" + type: "String" + description: "The value part of the header" + +http_responses_map: + description: | + An entry of an HTTP response map. Every query that matches the regular expression supplied in ``expression`` will be immediately answered with a HTTP response. + The status of the HTTP response will be the one supplied by ``status``, and the content set to the one supplied by ``content``, except if the status is a redirection (3xx) in which case the content is expected to be the URL to redirect to. + parameters: + - name: "expression" + type: "String" + description: "A regular expression to match the path against" + - name: "status" + type: "u16" + description: "The HTTP code to answer with" + - name: "content" + type: "String" + description: "The content of the HTTP response, or a URL if the status is a redirection (3xx)" + - name: "headers" + type: "Vec" + default: true + description: "The custom headers to set for the HTTP response, if any. The default is to use the value of the ``custom_response_headers`` parameter of the frontend" + +incoming_doh: + description: "The DNS over HTTP(s) parameters of a frontend" + parameters: + - name: "provider" + type: "String" + default: "nghttp2" + descripton: "Which underlying HTTP2 library should be used" + supported-values: + - "nghttp2" + - "h2o" + - name: "paths" + type: "Vec" + default: "/dns-query" + description: "The path part of a URL, or a list of paths, to accept queries on. Any query with a path matching exactly one of these will be treated as a DoH query (sub-paths can be accepted by setting the ``exact_path_matching`` setting to false)" + - name: "idle_timeout" + type: "u64" + default: 30 + description: "Set the idle timeout, in seconds" + - name: "server_tokens" + type: "String" + default: "" + description: "The content of the Server: HTTP header returned by dnsdist. The default is ``h2o/dnsdist`` when ``h2o`` is used, ``nghttp2-/dnsdist`` when ``nghttp2`` is" + - name: "send_cache_control_headers" + type: "bool" + default: "true" + description: "Whether to parse the response to find the lowest TTL and set a HTTP Cache-Control header accordingly" + - name: "keep_incoming_headers" + type: "bool" + default: "false" + description: "Whether to retain the incoming headers in memory, to be able to use :func:`HTTPHeaderRule` or :meth:`DNSQuestion.getHTTPHeaders`" + - name: "trust_forwarded_for_header" + type: "bool" + default: "false" + description: "Whether to parse any existing X-Forwarded-For header in the HTTP query and use the right-most value as the client source address and port, for ACL checks, rules, logging and so on" + - name: "early_acl_drop" + type: "bool" + default: "true" + description: "Whether to apply the ACL right when the connection is established, immediately dropping queries that are not allowed by the ACL (true), or later when a query is received, sending a HTTP 403 response when it is not allowed" + - name: "exact_path_matching" + type: "bool" + default: "true" + description: "Whether to do exact path matching of the query path against the paths configured in ``paths`` (true) or to accepts sub-paths (false)" + - name: "internal_pipe_buffer_size" + type: "u32" + default: 1048576 + description: "Set the size in bytes of the internal buffer of the pipes used internally to pass queries and responses between threads. Requires support for ``F_SETPIPE_SZ`` which is present in Linux since 2.6.35. The actual size might be rounded up to a multiple of a page size. 0 means that the OS default size is used." + - name: "custom_response_headers" + type: "Vec" + default: true + description: "Set custom HTTP header(s) returned by dnsdist" + - name: "responses_map" + type: "Vec" + default: true + description: "Set a list of HTTP response rules allowing to intercept HTTP queries very early, before the DNS payload has been processed, and send custom responses including error pages, redirects and static content" + +incoming_doq: + description: "Settings for DNS over QUIC frontends" + parameters: + - name: "max_concurrent_queries_per_connection" + type: "u64" + default: 65535 + description: "Maximum number of in-flight queries on a single connection" + +incoming_quic: + description: "QUIC settings for DNS over QUIC and DNS over HTTP/3 frontends" + parameters: + - name: "idle_timeout" + type: "u64" + default: 5 + description: "Set the idle timeout, in seconds" + - name: "congestion_control_algorithm" + type: "String" + default: "reno" + description: "The congestion control algorithm to be used" + supported-values: + - "reno" + - "cubic" + - "bbr" + - name: "internal_pipe_buffer_size" + type: "u32" + default: 1048576 + description: "Set the size in bytes of the internal buffer of the pipes used internally to pass queries and responses between threads. Requires support for ``F_SETPIPE_SZ`` which is present in Linux since 2.6.35. The actual size might be rounded up to a multiple of a page size. 0 means that the OS default size is used" + +incoming_dnscrypt_certificate_key_pair: + description: "Certificate and associated key for DNSCrypt frontends" + parameters: + - name: "certificate" + type: "String" + description: "The path to a DNSCrypt certificate file" + - name: "key" + type: "String" + description: "The path to the private key file corresponding to the certificate, or a list of paths to such files, whose order should match the certFile(s) ones" + +incoming_dnscrypt: + description: "Settings for DNSCrypt frontends" + parameters: + - name: "provider_name" + type: "String" + default: "" + description: "The DNSCrypt provider name for this frontend" + - name: "certificates" + type: "Vec" + default: true + description: "List of certificates and associated keys" + +outgoing_doh: + description: "DNS over HTTPS specific settings for backends" + parameters: + - name: "path" + type: "String" + default: "/dns-query" + description: "The HTTP path to send queries to" + - name: "add_x_forwarded_headers" + type: "bool" + default: "false" + description: "Whether to add X-Forwarded-For, X-Forwarded-Port and X-Forwarded-Proto headers to the backend" + +incoming_tcp: + description: "TCP-related settings for frontends" + parameters: + - name: "max_in_flight_queries" + type: "u32" + default: 0 + description: "Maximum number of in-flight queries over a single TCP connection. The default is 0, which disables out-of-order processing" + - name: "listen_queue_size" + type: "u32" + default: 0 + description: "Set the size of the listen queue. Default is ``SOMAXCONN``" + - name: "fast_open_queue_size" + type: "u32" + default: 0 + description: "Set the TCP Fast Open queue size, enabling TCP Fast Open when available and the value is larger than 0" + - name: "max_concurrent_connections" + type: "u32" + default: 0 + description: "Maximum number of concurrent incoming TCP connections to this frontend. The default is 0 which means unlimited" + +bind: + description: "General settings for frontends" + parameters: + - name: "listen_address" + type: "String" + description: "Address and port to listen to" + - name: "reuseport" + type: "bool" + default: "false" + description: "Set the ``SO_REUSEPORT`` socket option, allowing several sockets to be listening on this address and port" + - name: "protocol" + type: "String" + default: "Do53" + description: "The DNS protocol for this frontend" + supported-values: + - "Do53" + - "DoT" + - "DoH" + - "DoQ" + - "DoH3" + - "DNSCrypt" + - name: "threads" + type: "u32" + default: "1" + description: "Number of listening threads to create for this frontend" + - name: "interface" + type: "String" + default: "" + description: "Set the network interface to use" + - name: "cpus" + type: "String" + default: "" + description: "Set the CPU affinity for this listener thread, asking the scheduler to run it on a single CPU id, or a set of CPU ids. This parameter is only available if the OS provides the ``pthread_setaffinity_np()`` function" + - name: "enable_proxy_protocol" + type: "bool" + default: "false" + description: "Whether to expect a proxy protocol v2 header in front of incoming queries coming from an address allowed by the ACL in :ref:`yaml-settings-ProxyProtocolConfiguration`. Default is ``true``, meaning that queries are expected to have a proxy protocol payload if they come from an address present in the proxy protocol ACL" + - name: "tcp" + type: "IncomingTcpConfiguration" + default: true + description: "TCP-specific settings" + - name: "tls" + type: "IncomingTlsConfiguration" + default: true + description: "TLS-specific settings" + - name: "doh" + type: "IncomingDohConfiguration" + default: true + description: "DNS over HTTPS-specific settings" + - name: "doq" + type: "IncomingDoqConfiguration" + default: true + description: "DNS over QUIC-specific settings" + - name: "quic" + type: "IncomingQuicConfiguration" + default: true + description: "QUIC-specific settings" + - name: "dnscrypt" + type: "IncomingDnscryptConfiguration" + default: true + description: "DNSCrypt-specific settings" + - name: "additional_addresses" + type: "Vec" + default: "" + description: "List of additional addresses (with port) to listen on. Using this option instead of creating a new frontend for each address avoids the creation of new thread and Frontend objects, reducing the memory usage. The drawback is that there will be a single set of metrics for all addresses" + - name: "xsk" + type: "String" + default: "" + description: "The name of an XSK sockets map to attach to this frontend, if any" + +outgoing_tcp: + description: "TCP-related settings for backends" + parameters: + - name: "retries" + type: "u16" + default: 5 + description: "The number of TCP connection attempts to the backend, for a given query" + - name: "connect_timeout" + type: "u16" + default: 5 + description: "The timeout (in seconds) of a TCP connection attempt" + - name: "send_timeout" + type: "u16" + default: 30 + description: "The timeout (in seconds) of a TCP write attempt" + - name: "receive_timeout" + type: "u16" + default: 30 + description: "The timeout (in seconds) of a TCP read attempt" + - name: "fast_open" + type: "bool" + default: "false" + description: "Whether to enable TCP Fast Open" + +proxy_protocol_value: + description: "A proxy protocol Type-Length Value entry" + parameters: + - name: "key" + type: "u8" + description: "The type of the proxy protocol entry" + - name: "value" + type: "String" + description: "The value of the proxy protocol entry" + +lazy_health_check: + description: "Lazy health-check related settings for backends" + parameters: + - name: "interval" + type: "u16" + default: 30 + description: "The interval, in seconds, between health-check queries in 'lazy' mode. Note that when ``use_exponential_back_off`` is set to true, the interval doubles between every queries. These queries are only sent when a threshold of failing regular queries has been reached, and until the backend is available again" + - name: "min_sample_count" + type: "u16" + default: 1 + description: "The minimum amount of regular queries that should have been recorded before the ``threshold`` threshold can be applied" + - name: "mode" + type: "String" + default: "TimeoutOrServFail" + description: "The 'lazy' health-check mode: ``TimeoutOnly`` means that only timeout and I/O errors of regular queries will be considered for the ``threshold``, while ``TimeoutOrServFail`` will also consider ``Server Failure`` answers" + supported-values: + - "TimeoutOnly" + - "TimeoutOrServFail" + - name: "sample_size" + type: "u16" + default: 100 + description: "The maximum size of the sample of queries to record and consider for the ``threshold``. Default is 100, which means the result (failure or success) of the last 100 queries will be considered" + - name: "threshold" + type: "u16" + default: 20 + description: "The threshold, as a percentage, of queries that should fail for the 'lazy' health-check to be triggered. The default is 20 which means 20% of the last ``sample_size`` queries should fail for a health-check to be triggered" + - name: "use_exponential_back_off" + type: "bool" + default: "false" + description: "Whether the 'lazy' health-check should use an exponential back-off instead of a fixed value, between health-check probes. The default is false which means that after a backend has been moved to the ``down`` state health-check probes are sent every ``interval`` seconds. When set to true, the delay between each probe starts at ``interval`` seconds and doubles between every probe, capped at ``max_back_off`` seconds" + - name: "max_back_off" + type: "u16" + default: 3600 + description: "This value, in seconds, caps the time between two health-check queries when ``use_exponential_back_off`` is set to true. The default is 3600 which means that at most one hour will pass between two health-check queries" + +health_check: + description: "Health-checks related settings for backends" + parameters: + - name: "mode" + type: "String" + default: "auto" + description: "The health-check mode to use: 'auto' which sends health-check queries every ``check_interval`` seconds, 'up' which considers that the backend is always available, 'down' that it is always not available, and 'lazy' which only sends health-check queries after a configurable amount of regular queries have failed (see :ref:`yaml-settings-LazyHealthCheckConfiguration` for more information). Default is 'auto'. See :ref:`Healthcheck` for a more detailed explanation" + supported-values: + - "auto" + - "down" + - "lazy" + - "up" + - name: "qname" + type: "String" + default: "" + description: "The DNS name to use as QNAME in health-check queries" + - name: "qclass" + type: "String" + default: "IN" + description: "The DNS class to use in health-check queries" + - name: "qtype" + type: "String" + default: "A" + description: "The DNS type to use in health-check queries" + - name: "function" + type: "String" + default: "" + description: "The name of an optional Lua function to call to dynamically set the QNAME, QTYPE and QCLASS to use in the health-check query (see :ref:`Healthcheck`)" + - name: "lua" + type: "String" + default: "" + description: "The code of an optional Lua function to call to dynamically set the QNAME, QTYPE and QCLASS to use in the health-check query (see :ref:`Healthcheck`)" + - name: "lua_file" + type: "String" + default: "" + description: "A path to a file containing the code of an optional Lua function to call to dynamically set the QNAME, QTYPE and QCLASS to use in the health-check query (see :ref:`Healthcheck`)" + - name: "timeout" + type: "u16" + default: 1000 + description: "The timeout (in milliseconds) of a health-check query, default: 1000 (1s)" + - name: "set_cd" + type: "bool" + default: "false" + description: "Set the CD (Checking Disabled) flag in the health-check query" + - name: "max_failures" + type: "u8" + default: "1" + description: "Allow this many check failures before declaring the backend down" + - name: "rise" + type: "u8" + default: "1" + description: "Require ``number`` consecutive successful checks before declaring the backend up" + - name: "interval" + type: "u32" + default: "1" + description: "The time in seconds between health checks" + - name: "must_resolve" + type: "bool" + default: "false" + description: "Set to true when the health check MUST return a RCODE different from NXDomain, ServFail and Refused. Default is false, meaning that every RCODE except ServFail is considered valid" + - name: "use_tcp" + type: "bool" + default: "false" + description: "Whether to do healthcheck queries over TCP, instead of UDP. Always enabled for TCP-only, DNS over TLS and DNS over HTTPS backends" + - name: "lazy" + type: "LazyHealthCheckConfiguration" + default: true + description: "Settings for lazy health-checks" + +outgoing_auto_upgrade: + description: "Setting for the automatically upgraded backend to a more secure version of the DNS protocol" + parameters: + - name: "enabled" + type: "bool" + default: "false" + description: "Whether to use the 'Discovery of Designated Resolvers' mechanism to automatically upgrade a Do53 backend to DoT or DoH, depending on the priorities present in the SVCB record returned by the backend" + - name: "interval" + type: "u32" + default: "3600" + description: "If ``enabled`` is set, how often to check if an upgrade is available, in seconds" + - name: "keep" + type: "bool" + default: "false" + description: "If ``enabled`` is set, whether to keep the existing Do53 backend around after an upgrade. Default is false which means the Do53 backend will be replaced by the upgraded one" + - name: "pool" + type: "String" + default: "" + description: "If ``enabled`` is set, in which pool to place the newly upgraded backend. Default is empty which means the backend is placed in the default pool" + - name: "doh_key" + type: "u8" + default: "7" + description: "If ``enabled`` is set, the value to use for the SVC key corresponding to the DoH path. Default is 7" + - name: "use_lazy_health_check" + type: "bool" + default: "false" + description: "Whether the auto-upgraded version of this backend should use the lazy health-checking mode. Default is false, which means it will use the regular health-checking mode" + +backend: + description: "Generic settings for backends" + parameters: + - name: "address" + type: "String" + description: "``ip``:``port`` of the backend server (if unset, port defaults to 53 for Do53 backends, 853 for DoT and DoQ, and 443 for DoH and DoH3 ones" + - name: "id" + type: "String" + default: "" + description: "Use a pre-defined UUID instead of a random one" + - name: "name" + type: "String" + default: "" + description: "The name associated to this backend, for display purpose" + - name: "protocol" + type: "String" + description: "The DNS protocol to use to contact this backend" + supported-values: + - "Do53" + - "DoT" + - "DoH" + - name: "tls" + type: "OutgoingTlsConfiguration" + default: true + description: "TLS-related settings for DoT and DoH backends" + - name: "doh" + type: "OutgoingDohConfiguration" + default: true + description: "DoH-related settings for DoH backends" + - name: "use_client_subnet" + type: "bool" + default: "false" + description: "Whether to add (or override, see :ref:`yaml-settings-EdnsClientSubnetConfiguration`) an EDNS Client Subnet to the DNS payload before forwarding it to the backend. Please see :doc:`../advanced/passing-source-address` for more information" + - name: "use_proxy_protocol" + type: "bool" + default: "false" + description: "Add a proxy protocol header to the query, passing along the client's IP address and port along with the original destination address and port" + - name: "queries_per_second" + type: "u32" + default: 0 + description: "Limit the number of queries per second to ``number``, when using the ``firstAvailable`` policy" + - name: "order" + type: "u32" + default: 1 + description: "The order of this server, used by the `leastOutstanding` and `firstAvailable` policies" + - name: "weight" + type: "u32" + default: 1 + description: "The weight of this server, used by the `wrandom`, `whashed` and `chashed` policies, default: 1. Supported values are a minimum of 1, and a maximum of 2147483647" + - name: "pools" + type: "Vec" + default: "" + description: "List of pools to place this backend into. By default a server is placed in the default (\"\") pool" + - name: "tcp" + type: "OutgoingTcpConfiguration" + default: true + description: "TCP-related settings for a backend" + - name: "ip_bind_addr_no_port" + type: "bool" + default: "true" + description: "Whether to enable ``IP_BIND_ADDRESS_NO_PORT`` if available" + - name: "health_checks" + type: "HealthCheckConfiguration" + default: true + description: "Health-check settings" + - name: "source" + type: "String" + default: "" + description: | + The source address or interface to use for queries to this backend, by default this is left to the kernel's address selection. + The following formats are supported: + + - address, e.g. ``""192.0.2.2""`` + - interface name, e.g. ``""eth0""`` + - address@interface, e.g. ``""192.0.2.2@eth0""`` + + - name: "sockets" + type: "u32" + default: "1" + description: "Number of UDP sockets (and thus source ports) used toward the backend server, defaults to a single one. Note that for backends which are multithreaded, this setting will have an effect on the number of cores that will be used to process traffic from dnsdist. For example you may want to set ``sockets`` to a number somewhat greater than the number of worker threads configured in the backend, particularly if the Linux kernel is being used to distribute traffic to multiple threads listening on the same socket (via ``reuseport``). See also ``randomize_outgoing_sockets_to_backend`` in :ref:`yaml-settings-UdpTuningConfiguration`" + - name: "disable_zero_scope" + type: "bool" + default: "false" + description: "Disable the EDNS Client Subnet :doc:`../advanced/zero-scope` feature, which does a cache lookup for an answer valid for all subnets (ECS scope of 0) before adding ECS information to the query and doing the regular lookup. Default is false. This requires the ``parse_ecs`` option of the corresponding cache to be set to true" + - name: "reconnect_on_up" + type: "bool" + default: "false" + description: "Close and reopen the sockets when a server transits from Down to Up. This helps when an interface is missing when dnsdist is started" + - name: "max_in_flight" + type: "u32" + default: "1" + description: "Maximum number of in-flight queries. The default is 0, which disables out-of-order processing. It should only be enabled if the backend does support out-of-order processing. Out-of-order processing needs to be enabled on the frontend as well" + - name: "tcp_only" + type: "bool" + default: "false" + description: "Always forward queries to that backend over TCP, never over UDP. Always enabled for TLS backends" + - name: "auto_upgrade" + type: "OutgoingAutoUpgradeConfiguration" + default: true + description: "Auto-upgrade related settings" + - name: "max_concurrent_tcp_connections" + type: "u32" + default: 0 + description: "Maximum number of TCP connections to that backend. When that limit is reached, queries routed to that backend that cannot be forwarded over an existing connection will be dropped. Default is 0 which means no limit" + - name: "proxy_protocol_advertise_tls" + type: "bool" + default: "false" + description: "Whether to set the SSL Proxy Protocol TLV in the proxy protocol payload sent to the backend if the query was received over an encrypted channel (DNSCrypt, DoQ, DoH or DoT). Requires ``use_proxy_protocol``" + - name: "mac_address" + type: "String" + default: "" + description: "When the ``xsk`` option is set, this parameter can be used to specify the destination MAC address to use to reach the backend. If this options is not specified, dnsdist will try to get it from the IP of the backend by looking into the system's MAC address table, but it will fail if the corresponding MAC address is not present" + - name: "cpus" + type: "String" + default: "" + description: "Set the CPU affinity for this thread, asking the scheduler to run it on a single CPU id, or a set of CPU ids. This parameter is only available if the OS provides the ``pthread_setaffinity_np()`` function" + - name: "xsk" + type: "String" + default: "" + description: "The name of an XSK sockets map to attach to this frontend, if any" + +tuning: + description: "Tuning settings" + parameters: + - name: "doh" + type: "DohTuningConfiguration" + default: true + description: "DoH-related tuning settings" + - name: "tcp" + type: "TcpTuningConfiguration" + default: true + description: "TCP-related tuning settings" + - name: "tls" + type: "TlsTuningConfiguration" + description: "TLS-related tuning settings" + default: true + - name: "udp" + type: "UdpTuningConfiguration" + default: true + description: "UDP-related tuning settings" + +tcp_tuning: + category: "tuning.tcp" + parameters: + - name: "worker_threads" + type: "u32" + default: 10 + lua-name: "setMaxTCPClientThreads" + internal-field-name: "d_maxTCPClientThreads" + runtime-configurable: false + - name: "receive_timeout" + type: "u32" + default: 2 + lua-name: "setTCPRecvTimeout" + internal-field-name: "d_tcpRecvTimeout" + runtime-configurable: true + - name: "send_timeout" + type: "u32" + default: 2 + lua-name: "setTCPSendTimeout" + internal-field-name: "d_tcpSendTimeout" + runtime-configurable: true + - name: "max_queries_per_connection" + type: "u64" + default: "0" + lua-name: "setMaxTCPQueriesPerConnection" + internal-field-name: "d_maxTCPQueriesPerConn" + runtime-configurable: true + - name: "max_connection_duration" + type: "u64" + default: "0" + lua-name: "setMaxTCPConnectionDuration" + internal-field-name: "d_maxTCPConnectionDuration" + runtime-configurable: true + - name: "max_queued_connections" + type: "u64" + default: "10000" + lua-name: "setMaxTCPQueuedConnections" + internal-field-name: "d_maxTCPQueuedConnections" + runtime-configurable: false + - name: "internal_pipe_buffer_size" + type: "u32" + default: 1048576 + lua-name: "setTCPInternalPipeBufferSize" + internal-field-name: "d_tcpInternalPipeBufferSize" + runtime-configurable: false + - name: "outgoing_max_idle_time" + type: "u64" + default: 300 + lua-name: "setTCPDownstreamMaxIdleTime" + internal-field-name: "d_outgoingTCPMaxIdleTime" + runtime-configurable: false + - name: "outgoing_cleanup_interval" + type: "u64" + default: 60 + lua-name: "setTCPDownstreamCleanupInterval" + internal-field-name: "d_outgoingTCPCleanupInterval" + runtime-configurable: false + - name: "outgoing_max_idle_connection_per_backend" + type: "u64" + default: 10 + lua-name: "setMaxCachedTCPConnectionsPerDownstream" + internal-field-name: "d_outgoingTCPMaxIdlePerBackend" + runtime-configurable: false + - name: "max_connections_per_client" + type: "u32" + default: 0 + lua-name: "setMaxTCPConnectionsPerClient" + internal-field-name: "d_maxTCPConnectionsPerClient" + runtime-configurable: false + - name: "fast_open_key" + type: "String" + default: "" + lua-name: "setTCPFastOpenKey" + runtime-configurable: false + +udp_tuning: + category: "tuning.udp" + parameters: + - name: "messages_per_round" + type: "u32" + default: 1 + lua-name: "setUDPMultipleMessagesVectorSize" + internal-field-name: "d_udpVectorSize" + runtime-configurable: false + - name: "send_buffer_size" + type: "u32" + default: 0 + lua-name: "setUDPSocketBufferSizes" + internal-field-name: "d_socketUDPSendBuffer" + runtime-configurable: false + - name: "receive_buffer_size" + type: "u32" + default: 0 + lua-name: "setUDPSocketBufferSizes" + internal-field-name: "d_socketUDPRecvBuffer" + runtime-configurable: false + - name: "max_outstanding_per_backend" + type: "u32" + default: 65535 + lua-name: "setMaxUDPOutstanding" + internal-field-name: "d_maxUDPOutstanding" + runtime-configurable: false + - name: "timeout" + type: "u8" + default: 2 + lua-name: "setUDPTimeout" + internal-field-name: "d_udpTimeout" + runtime-configurable: false + - name: "randomize_outgoing_sockets_to_backend" + type: "bool" + default: "false" + lua-name: "setRandomizedOutgoingSockets" + internal-field-name: "d_randomizeUDPSocketsToBackend" + runtime-configurable: false + - name: "randomize_ids_to_backend" + type: "bool" + default: "false" + lua-name: "setRandomizedIdsOverUDP" + internal-field-name: "d_randomizeIDsToBackend" + runtime-configurable: false + +tls_engine: + description: "OpenSSL engine settings" + parameters: + - name: "name" + type: "String" + description: "The engine name" + - name: "default_string" + type: "String" + default: "" + description: "The default string to pass to the engine. The exact value depends on the engine but represents the algorithms to register with the engine, as a list of comma-separated keywords. For example 'RSA,EC,DSA,DH,PKEY,PKEY_CRYPTO,PKEY_ASN1'" + +tls_tuning: + category: "tuning.tls" + parameters: + - name: "outgoing_tickets_cache_cleanup_delay" + type: "u16" + default: "60" + lua-name: "setOutgoingTLSSessionsCacheCleanupDelay" + internal-field-name: "d_tlsSessionCacheCleanupDelay" + runtime-configurable: true + - name: "outgoing_tickets_cache_validity" + type: "u16" + default: "600" + lua-name: "setOutgoingTLSSessionsCacheMaxTicketValidity" + internal-field-name: "d_tlsSessionCacheSessionValidity" + runtime-configurable: true + - name: "max_outgoing_tickets_per_backend" + type: "u16" + default: "20" + lua-name: "setOutgoingTLSSessionsCacheMaxTicketsPerBackend" + internal-field-name: "d_tlsSessionCacheMaxSessionsPerBackend" + runtime-configurable: true + - name: "providers" + type: "Vec" + default: "" + lua-name: "loadTLSProvider" + description: "Load OpenSSL providers. Providers can be used to accelerate cryptographic operations, like for example Intel QAT. At the moment up to a maximum of 32 loaded providers are supported, and that support is experimental. Note that this feature is only available when building against OpenSSL version >= 3.0 and with the ``-–enable-tls-provider`` configure flag on. In other cases, ``engines`` should be used instead. Some providers might actually degrade performance unless the TLS asynchronous mode of OpenSSL is enabled. To enable it see the ``async_mode`` parameter of TLS frontends" + - name: "engines" + type: "Vec" + default: true + lua-name: "loadTLSEngine" + description: "Load OpenSSL engines. Engines can be used to accelerate cryptographic operations, like for example Intel QAT. At the moment up to a maximum of 32 loaded engines are supported, and that support is experimental. Some engines might actually degrade performance unless the TLS asynchronous mode of OpenSSL is enabled. To enable it see the ``async_mode`` parameter of TLS frontends" + +doh_tuning: + category: "tuning.doh" + parameters: + - name: "outgoing_worker_threads" + type: "u32" + default: 10 + lua-name: "setOutgoingDoHWorkerThreads" + internal-field-name: "d_outgoingDoHWorkers" + runtime-configurable: false + - name: "outgoing_max_idle_time" + type: "u64" + default: 300 + lua-name: "setDoHDownstreamMaxIdleTime" + internal-field-name: "d_outgoingDoHMaxIdleTime" + runtime-configurable: false + - name: "outgoing_cleanup_interval" + type: "u64" + default: 60 + lua-name: "setDoHDownstreamCleanupInterval" + internal-field-name: "d_outgoingDoHCleanupInterval" + runtime-configurable: false + - name: "outgoing_max_idle_connection_per_backend" + type: "u64" + default: 10 + lua-name: "setMaxIdleDoHConnectionsPerDownstream" + internal-field-name: "d_outgoingDoHMaxIdlePerBackend" + runtime-configurable: false + +cache_settings: + parameters: + - name: "stale_entries_ttl" + type: "u32" + default: "0" + lua-name: "setStaleCacheEntriesTTL" + internal-field-name: "d_staleCacheEntriesTTL" + runtime-configurable: true + - name: "cleaning_delay" + type: "u16" + default: "60" + lua-name: "setCacheCleaningDelay" + internal-field-name: "d_cacheCleaningDelay" + runtime-configurable: true + - name: "cleaning_percentage" + type: "u16" + default: "100" + lua-name: "setCacheCleaningPercentage" + internal-field-name: "d_cacheCleaningPercentage" + runtime-configurable: true + +security_polling: + parameters: + - name: "polling_interval" + type: "u32" + default: "3600" + lua-name: "setSecurityPollInterval" + internal-field-name: "d_secPollInterval" + runtime-configurable: true + - name: "suffix" + type: "String" + default: "secpoll.powerdns.com." + lua-name: "setSecurityPollSuffix" + internal-field-name: "d_secPollSuffix" + runtime-configurable: true + +structured_logging: + description: "Structured-like logging settings" + parameters: + - name: "enabled" + type: "bool" + default: "false" + description: | + Set whether log messages should be in a structured-logging-like format. This is turned off by default. + The resulting format looks like this (when timestamps are enabled via ``--log-timestamps`` and with ``level_prefix: prio`` and ``time_format: ISO8601``):: + + ts=\"2023-11-06T12:04:58+0100\" prio=\"Info\" msg=\"Added downstream server 127.0.0.1:53\" + + And with ``level_prefix: level`` and ``time_format: numeric``):: + + ts=\"1699268815.133\" level=\"Info\" msg=\"Added downstream server 127.0.0.1:53\" + + - name: "level_prefix" + type: "String" + default: "prio" + description: "Set the key name for the log level. There is unfortunately no standard name for this key, so in some setups it might be useful to set this value to a different name to have consistency across products" + - name: "time_format" + type: "String" + default: "numeric" + description: "Set the time format" + supported-values: + - "ISO8601" + - "numeric" + +logging: + description: "Logging settings" + parameters: + - name: "verbose" + type: "bool" + default: "false" + lua-name: "setVerbose" + internal-field-name: "d_verbose" + runtime-configurable: true + description: "Set whether log messages issued at the verbose level should be logged" + - name: "verbose_health_checks" + type: "bool" + default: "false" + lua-name: "setVerboseHealthChecks" + internal-field-name: "d_verboseHealthChecks" + runtime-configurable: true + description: "Set whether health check errors should be logged" + - name: "verbose_log_destination" + type: "String" + default: "" + lua-name: "setVerboseLogDestination" + description: "Set a destination file to write the ‘verbose’ log messages to, instead of sending them to syslog and/or the standard output which is the default. Note that these messages will no longer be sent to syslog or the standard output once this option has been set. There is no rotation or file size limit. Only use this feature for debugging under active operator control" + - name: "syslog_facility" + type: "String" + default: "" + lua-name: "setSyslogFacility" + description: "Set the syslog logging facility to the supplied value (values with or without the ``log_`` prefix are supported)" + supported-values: [local0, log_local0, local1, log_local1, local2, log_local2, local3, log_local3, local4, log_local4, local5, log_local5, local6, log_local6, local7, log_local7, kern, log_kern, user, log_user, mail, log_mail, daemon, log_daemon, auth, log_auth, syslog, log_syslog, lpr, log_lpr, news, log_news, uucp, log_uucp, cron, log_cron, authpriv, log_authpriv, ftp, log_ftp] + - name: "structured" + type: "StructuredLoggingConfiguration" + default: true + +general: + description: "General settings" + parameters: + - name: "edns_udp_payload_size_self_generated_answers" + type: "u16" + default: "1232" + lua-name: "setPayloadSizeOnSelfGeneratedAnswers" + internal-field-name: "d_payloadSizeSelfGenAnswers" + runtime-configurable: true + description: "Set the UDP payload size advertised via EDNS on self-generated responses. In accordance with :rfc:`RFC 6891 <6891#section-6.2.5>`, values lower than 512 will be treated as equal to 512" + - name: "add_edns_to_self_generated_answers" + type: "bool" + default: "true" + lua-name: "setAddEDNSToSelfGeneratedResponses" + internal-field-name: "d_addEDNSToSelfGeneratedResponses" + runtime-configurable: true + description: "Whether to add EDNS to self-generated responses, provided that the initial query had EDNS" + - name: "truncate_tc_answers" + type: "bool" + default: "false" + lua-name: "truncateTC" + internal-field-name: "d_truncateTC" + runtime-configurable: true + description: "Remove any left-over records in responses with the TC bit set, in accordance with :rfc:`RFC 6891 <6891#section-7>`" + - name: "fixup_case" + type: "bool" + default: "false" + lua-name: "fixupCase" + internal-field-name: "d_fixupCase" + runtime-configurable: true + description: "If set, ensure that the case of the DNS qname in the response matches the one from the query" + - name: "allow_empty_responses" + type: "bool" + default: "false" + lua-name: "setAllowEmptyResponse" + internal-field-name: "d_allowEmptyResponse" + runtime-configurable: true + description: "Set to true (defaults to false) to allow empty responses (qdcount=0) with a NoError or NXDomain rcode (default) from backends. dnsdist drops these responses by default because it can't match them against the initial query since they don't contain the qname, qtype and qclass, and therefore the risk of collision is much higher than with regular responses" + - name: "drop_empty_queries" + type: "bool" + default: "false" + lua-name: "setDropEmptyQueries" + internal-field-name: "d_dropEmptyQueries" + runtime-configurable: true + description: "Set to true (defaults to false) to drop empty queries (qdcount=0) right away, instead of answering with a NotImp rcode. dnsdist used to drop these queries by default because most rules and existing Lua code expects a query to have a qname, qtype and qclass. However :rfc:`7873` uses these queries to request a server cookie, and :rfc:`8906` as a conformance test, so answering these queries with NotImp is much better than not answering at all" + - name: "capabilities_to_retain" + type: "Vec" + default: "" + lua-name: "addCapabilitiesToRetain" + runtime-configurable: false + description: | + Accept a Linux capability as a string, or a list of these, to retain after startup so that privileged operations can still be performed at runtime. + Keeping ``CAP_SYS_ADMIN`` on kernel 5.8+ for example allows loading eBPF programs and altering eBPF maps at runtime even if the ``kernel.unprivileged_bpf_disabled`` sysctl is set. + Note that this does not grant the capabilities to the process, doing so might be done by running it as root which we don't advise, or by adding capabilities via the systemd unit file, for example. + Please also be aware that switching to a different user via ``--uid`` will still drop all capabilities." + +packet_cache: + description: "Packet-cache settings" + parameters: + - name: "name" + type: "String" + description: "The name of the packet cache object" + - name: "size" + type: "u64" + description: "The maximum number of entries in this cache" + - name: "deferrable_insert_lock" + type: "bool" + default: "true" + description: "Whether the cache should give up insertion if the lock is held by another thread, or simply wait to get the lock" + - name: "dont_age" + type: "bool" + default: "false" + description: "Don’t reduce TTLs when serving from the cache. Use this when dnsdist fronts a cluster of authoritative servers" + - name: "keep_stale_data" + type: "bool" + default: "false" + description: "Whether to suspend the removal of expired entries from the cache when there is no backend available in at least one of the pools using this cache" + - name: "max_negative_ttl" + type: "u32" + default: "3600" + description: "Cache a NXDomain or NoData answer from the backend for at most this amount of seconds, even if the TTL of the SOA record is higher" + - name: "max_ttl" + type: "u32" + default: "86400" + description: "Cap the TTL for records to his number" + - name: "min_ttl" + type: "u32" + default: 0 + description: "Don’t cache entries with a TTL lower than this" + - name: "shards" + type: "u32" + default: "20" + description: "Number of shards to divide the cache into, to reduce lock contention" + - name: "parse_ecs" + type: "bool" + default: "false" + description: "Whether any EDNS Client Subnet option present in the query should be extracted and stored to be able to detect hash collisions involving queries with the same qname, qtype and qclass but a different incoming ECS value. Enabling this option adds a parsing cost and only makes sense if at least one backend might send different responses based on the ECS value, so it's disabled by default. Enabling this option is required for the :doc:`../advanced/zero-scope` option to work" + - name: "stale_ttl" + type: "u32" + default: "60" + description: "When the backend servers are not reachable, and global configuration setStaleCacheEntriesTTL is set appropriately, TTL that will be used when a stale cache entry is returned" + - name: "temporary_failure_ttl" + type: "u32" + default: "60" + description: "On a SERVFAIL or REFUSED from the backend, cache for this amount of seconds" + - name: "cookie_hashing" + type: "bool" + default: "false" + description: "If true, EDNS Cookie values will be hashed, resulting in separate entries for different cookies in the packet cache. This is required if the backend is sending answers with EDNS Cookies, otherwise a client might receive an answer with the wrong cookie" + - name: "maximum_entry_size" + type: "u32" + default: "4096" + description: "The maximum size, in bytes, of a DNS packet that can be inserted into the packet cache" + - name: "options_to_skip" + type: "Vec" + default: "" + description: "Extra list of EDNS option codes to skip when hashing the packet (if ``cookie_hashing`` above is false, EDNS cookie option number will be added to this list internally)" + +proxy_protocol: + description: "Proxy Protocol-related settings" + parameters: + - name: "acl" + type: "Vec" + default: "" + description: "Set the list of netmasks from which a Proxy Protocol header will be required, over UDP, TCP and DNS over TLS. The default is empty. Note that a proxy protocol payload will be required from these clients, regular DNS queries will no longer be accepted if they are not preceded by a proxy protocol payload. Be also aware that, if ``apply_acl_to_proxied_clients`` is set (default is false), the general ACL will be applied to the source IP address as seen by dnsdist first, but also to the source IP address provided in the Proxy Protocol header." + - name: "maximum_payload_size" + type: "u32" + default: "512" + lua-name: "setProxyProtocolMaximumPayloadSize" + internal-field-name: "d_proxyProtocolMaximumSize" + runtime-configurable: true + description: "Set the maximum size of a Proxy Protocol payload that dnsdist is willing to accept, in bytes. The default is 512, which is more than enough except for very large TLV data. This setting can’t be set to a value lower than 16 since it would deny of Proxy Protocol headers" + - name: "apply_acl_to_proxied_clients" + type: "bool" + default: "false" + lua-name: "setProxyProtocolApplyACLToProxiedClients" + internal-field-name: "d_applyACLToProxiedClients" + runtime-configurable: true + description: "Whether the general ACL should be applied to the source IP address provided in the Proxy Protocol header, in addition to being applied to the source IP address as seen by dnsdist first" + +snmp: + description: "SNMP-related settings" + parameters: + - name: "enabled" + type: "bool" + default: "false" + lua-name: "snmpAgent" + internal-field-name: "d_snmpEnabled" + runtime-configurable: false + description: "Enable SNMP support" + - name: "traps_enabled" + type: "bool" + default: "false" + lua-name: "snmpAgent" + internal-field-name: "d_snmpTrapsEnabled" + runtime-configurable: false + description: "Enable the sending of SNMP traps for specific events" + - name: "daemon_socket" + type: "String" + default: "" + lua-name: "snmpAgent" + internal-field-name: "d_snmpDaemonSocketPath" + runtime-configurable: false + description: "A string specifying how to connect to the daemon agent. This is usually the path to a UNIX socket, but e.g. ``tcp:localhost:705`` can be used as well. By default, SNMP agent’s default socket is used" + +query_count: + description: "Per-record Carbon statistics of the amount of queries. See :doc:`../guides/carbon`" + parameters: + - name: "enabled" + type: "bool" + default: "false" + description: "Enable per-record Carbon statistics of the amount of queries" + - name: "filter_function_name" + type: "String" + default: "" + description: "The name of a Lua function to filter which query should be accounted for, and how" + - name: "filter_function_code" + type: "String" + default: "" + description: "The code of a Lua function to filter which query should be accounted for, and how" + - name: "filter_function_file" + type: "String" + default: "" + description: "The path to a file containing the code of a Lua function to filter which query should be accounted for, and how" + +pool: + description: "Settings for a pool of servers" + parameters: + - name: "name" + type: "String" + description: "The name of this pool" + - name: "packet_cache" + type: "String" + default: "" + description: "The name of a packet cache object, if any" + - name: "policy" + type: "String" + default: "" + description: "The name of the load-balancing policy associated to this pool. If left empty, the global policy will be used" + +custom_load_balancing_policy: + description: "Settings for a custom load-balancing policy" + parameters: + - name: "name" + type: "String" + description: "The name of this load-balancing policy" + - name: "function_name" + type: "String" + default: "" + description: "The name of a Lua function implementing the custom load-balancing policy. If ``ffi`` is false, this function takes a table of :class:`Server` objects and a :class:`DNSQuestion` representing the current query, and must return the index of the selected server in the supplied table. If ``ffi`` is true, this function takes a ``const dnsdist_ffi_servers_list_t*`` and a ``dnsdist_ffi_dnsquestion_t*``" + - name: "function_code" + type: "String" + default: "" + description: "Same than ``function_name`` but contain actual Lua code returning a function instead of a name" + - name: "function_file" + type: "String" + default: "" + description: "Same than ``function_name`` but contain the path to a file containing actual Lua code returning a function instead of a name" + - name: "ffi" + type: "bool" + default: "false" + description: "Whether the function uses the faster but more complicated Lua FFI API" + - name: "per_thread" + type: "bool" + default: "false" + description: "If set, the resulting policy will be executed in a lock-free per-thread context, instead of running in the global Lua context. Note that ``function_name`` cannot be used, since this needs the Lua code to create the function in a new Lua context instead of just a function" + +load_balancing_policies: + description: "Setting for load-balancing policies" + parameters: + - name: "default_policy" + type: "String" + default: "leastOutstanding" + lua-name: "setServerPolicy" + runtime-configurable: true + description: "Set the default server selection policy" + - name: "servfail_on_no_server" + type: "bool" + default: "false" + lua-name: "setServFailWhenNoServer" + internal-field-name: "d_servFailOnNoPolicy" + runtime-configurable: true + description: "If set, return a ServFail when no servers are available, instead of the default behaviour of dropping the query" + - name: "round_robin_servfail_on_no_server" + type: "bool" + default: "false" + lua-name: "setRoundRobinFailOnNoServer" + internal-field-name: "d_roundrobinFailOnNoServer" + runtime-configurable: true + description: "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" + - name: "weighted_balancing_factor" + type: "f64" + default: 0.0 + lua-name: "setWeightedBalancingFactor" + internal-field-name: "d_weightedBalancingFactor" + runtime-configurable: false + description: "Set the maximum imbalance between the number of outstanding queries intended for a given server, based on its weight, and the actual number, when using the ``whashed`` or ``wrandom`` load-balancing policy. Default is 0, which disables the bounded-load algorithm" + - name: "consistent_hashing_balancing_factor" + type: "f64" + default: 0.0 + lua-name: "setConsistentHashingBalancingFactor" + internal-field-name: "d_consistentHashBalancingFactor" + runtime-configurable: false + description: "Set the maximum imbalance between the number of outstanding queries intended for a given server, based on its weight, and the actual number, when using the ``chashed`` consistent hashing load-balancing policy. Default is 0, which disables the bounded-load algorithm" + - name: "custom_policies" + type: "Vec" + default: true + description: "Custom load-balancing policies implemented in Lua" + - name: "hash_perturbation" + type: "u32" + default: "0" + lua-name: "setWHashedPertubation" + internal-field-name: "d_hashPerturbation" + runtime-configurable: false + description: "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 instances" + +query_rule: + description: "A rule that can applied on queries" + skip-serde: true + parameters: + - name: "name" + type: "String" + default: "" + description: "The name to assign to this rule" + - name: "uuid" + type: "String" + description: "The UUID to assign to this rule, if any" + - name: "selector" + type: "Selector" + description: "The selector to match queries against" + - name: "action" + type: "Action" + description: "The action taken if the selector matches" + +response_rule: + description: "A rule that can applied on responses" + skip-serde: true + parameters: + - name: "name" + type: "String" + default: "" + description: "The name to assign to this rule" + - name: "uuid" + type: "String" + default: "" + description: "The UUID to assign to this rule, if any" + - name: "selector" + type: "Selector" + description: "The selector to match responses against" + - name: "action" + type: "ResponseAction" + description: "The action taken if the selector matches" + +xsk: + description: "An ``XSK`` / ``AF_XDP`` sockets map" + parameters: + - name: "name" + type: "String" + description: "The name to give to this map" + - name: "interface" + type: "String" + description: "The network interface to which the sockets will be associated" + - name: "queues" + type: "u16" + description: "The number of queues the network interface has (can be retrieved by looking at the ``Combined`` line in the output of ``sudo ethtool -l ``). It should match the number of threads of the frontend or backend associated to this map" + - name: "frames" + type: "u32" + default: 65536 + description: "The number of frames to allocate for this map" + - name: "map_path" + type: "String" + default: "/sys/fs/bpf/dnsdist/xskmap" + descripton: "The filesystem path this map will be pinned to, which allows the external ``XDP`` program to communicate with :program:`dnsdist`" diff --git a/pdns/dnsdistdist/dnsdist-svc.cc b/pdns/dnsdistdist/dnsdist-svc.cc index 574cc9eea1e3..18ec3a62c3e1 100644 --- a/pdns/dnsdistdist/dnsdist-svc.cc +++ b/pdns/dnsdistdist/dnsdist-svc.cc @@ -21,6 +21,7 @@ */ #include "dnsdist-svc.hh" #include "dnsdist.hh" +#include "dnsdist-dnsparser.hh" #include "dnsdist-ecs.hh" #include "dnsdist-lua.hh" #include "dnswriter.hh" diff --git a/pdns/dnsdistdist/dnsdist-web.cc b/pdns/dnsdistdist/dnsdist-web.cc index 26c34edc4d14..253c00d3bb2d 100644 --- a/pdns/dnsdistdist/dnsdist-web.cc +++ b/pdns/dnsdistdist/dnsdist-web.cc @@ -43,6 +43,7 @@ #include "dnsdist-prometheus.hh" #include "dnsdist-rings.hh" #include "dnsdist-rule-chains.hh" +#include "dnsdist-rules.hh" #include "dnsdist-web.hh" #include "dolog.hh" #include "gettime.hh" diff --git a/pdns/dnsdistdist/dnsdist.cc b/pdns/dnsdistdist/dnsdist.cc index 758e0b7dadad..e54005a99122 100644 --- a/pdns/dnsdistdist/dnsdist.cc +++ b/pdns/dnsdistdist/dnsdist.cc @@ -23,6 +23,7 @@ #include "config.h" #include +#include #include #include #include @@ -43,6 +44,7 @@ #include "dnsdist-cache.hh" #include "dnsdist-carbon.hh" #include "dnsdist-configuration.hh" +#include "dnsdist-configuration-yaml.hh" #include "dnsdist-console.hh" #include "dnsdist-crypto.hh" #include "dnsdist-discovery.hh" @@ -57,7 +59,9 @@ #include "dnsdist-proxy-protocol.hh" #include "dnsdist-random.hh" #include "dnsdist-rings.hh" +#include "dnsdist-rules.hh" #include "dnsdist-secpoll.hh" +#include "dnsdist-self-answers.hh" #include "dnsdist-snmp.hh" #include "dnsdist-tcp.hh" #include "dnsdist-tcp-downstream.hh" @@ -520,9 +524,7 @@ bool processResponseAfterRules(PacketBuffer& response, DNSResponse& dnsResponse, } if (dnsResponse.ids.ttlCap > 0) { - std::string result; - LimitTTLResponseAction lrac(0, dnsResponse.ids.ttlCap, {}); - lrac(&dnsResponse, &result); + dnsdist::PacketMangling::restrictDNSPacketTTLs(dnsResponse.getMutableData(), 0, dnsResponse.ids.ttlCap); } if (dnsResponse.ids.d_extendedError) { @@ -832,28 +834,28 @@ static void spoofResponseFromString(DNSQuestion& dnsQuestion, const string& spoo string result; if (raw) { + dnsdist::ResponseConfig config; std::vector raws; stringtok(raws, spoofContent, ","); - SpoofAction tempSpoofAction(raws, std::nullopt); - tempSpoofAction(&dnsQuestion, &result); + dnsdist::self_answers::generateAnswerFromRDataEntries(dnsQuestion, raws, std::nullopt, config); } else { std::vector addrs; stringtok(addrs, spoofContent, " ,"); if (addrs.size() == 1) { + dnsdist::ResponseConfig config; try { ComboAddress spoofAddr(spoofContent); - SpoofAction tempSpoofAction({spoofAddr}); - tempSpoofAction(&dnsQuestion, &result); + dnsdist::self_answers::generateAnswerFromIPAddresses(dnsQuestion, {spoofAddr}, config); } catch (const PDNSException& e) { DNSName cname(spoofContent); - SpoofAction tempSpoofAction(cname); // CNAME then - tempSpoofAction(&dnsQuestion, &result); + dnsdist::self_answers::generateAnswerFromCNAME(dnsQuestion, cname, config); } } else { + dnsdist::ResponseConfig config; std::vector cas; for (const auto& addr : addrs) { try { @@ -862,18 +864,15 @@ static void spoofResponseFromString(DNSQuestion& dnsQuestion, const string& spoo catch (...) { } } - SpoofAction tempSpoofAction(cas); - tempSpoofAction(&dnsQuestion, &result); + dnsdist::self_answers::generateAnswerFromIPAddresses(dnsQuestion, cas, config); } } } static void spoofPacketFromString(DNSQuestion& dnsQuestion, const string& spoofContent) { - string result; - - SpoofAction tempSpoofAction(spoofContent.c_str(), spoofContent.size()); - tempSpoofAction(&dnsQuestion, &result); + // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic) + dnsdist::self_answers::generateAnswerFromRawPacket(dnsQuestion, PacketBuffer(spoofContent.data(), spoofContent.data() + spoofContent.size())); } bool processRulesResult(const DNSAction::Action& action, DNSQuestion& dnsQuestion, std::string& ruleresult, bool& drop) @@ -1348,9 +1347,7 @@ static bool prepareOutgoingResponse(const ClientState& clientState, DNSQuestion& } if (dnsResponse.ids.ttlCap > 0) { - std::string result; - LimitTTLResponseAction ltrac(0, dnsResponse.ids.ttlCap, {}); - ltrac(&dnsResponse, &result); + dnsdist::PacketMangling::restrictDNSPacketTTLs(dnsResponse.getMutableData(), 0, dnsResponse.ids.ttlCap); } if (dnsResponse.ids.d_extendedError) { @@ -1500,7 +1497,7 @@ ProcessQueryResult processQueryAfterRules(DNSQuestion& dnsQuestion, std::shared_ ++dnsdist::metrics::g_stats.cacheMisses; - //coverity[auto_causes_copy] + // coverity[auto_causes_copy] const auto existingPool = dnsQuestion.ids.poolName; const auto& chains = dnsdist::configuration::getCurrentRuntimeConfiguration().d_ruleChains; const auto& cacheMissRuleActions = dnsdist::rules::getRuleChain(chains, dnsdist::rules::RuleChain::CacheMissRules); @@ -2234,6 +2231,9 @@ static void maintThread() (*maintenanceCallback)(); } dnsdist::lua::hooks::runMaintenanceHooks(*lua); +#if !defined(DISABLE_DYNBLOCKS) + dnsdist::DynamicBlocks::runRegisteredGroups(*lua); +#endif /* DISABLE_DYNBLOCKS */ secondsToWaitLog = 0; } catch (const std::exception& e) { @@ -2343,7 +2343,7 @@ static void healthChecksThread() std::unique_ptr mplexer{nullptr}; // this points to the actual shared_ptrs! - //coverity[auto_causes_copy] + // coverity[auto_causes_copy] const auto servers = dnsdist::configuration::getCurrentRuntimeConfiguration().d_backends; for (const auto& dss : servers) { dss->updateStatisticsInfo(); @@ -3271,6 +3271,44 @@ static ListeningSockets initListeningSockets() return result; } +static std::optional lookForTentativeConfigurationFileWithExtension(const std::string& configurationFile, const std::string& extension) +{ + auto dotPos = configurationFile.rfind('.'); + if (dotPos == std::string::npos) { + return std::nullopt; + } + auto tentativeFile = configurationFile.substr(0, dotPos + 1) + extension; + if (!std::filesystem::exists(tentativeFile)) { + return std::nullopt; + } + return tentativeFile; +} + +static bool loadConfigurationFromFile(const std::string& configurationFile, bool isClient, bool configCheck) +{ + if (boost::ends_with(configurationFile, ".yml")) { + if (auto tentativeLuaConfFile = lookForTentativeConfigurationFileWithExtension(configurationFile, "lua")) { + vinfolog("Loading configuration from auto-discovered Lua file %s", *tentativeLuaConfFile); + dnsdist::configuration::lua::loadLuaConfigurationFile(*(g_lua.lock()), *tentativeLuaConfFile, configCheck); + } + vinfolog("Loading configuration from YAML file %s", configurationFile); + return dnsdist::configuration::yaml::loadConfigurationFromFile(configurationFile, isClient, configCheck); + } + if (boost::ends_with(configurationFile, ".lua")) { + vinfolog("Loading configuration from Lua file %s", configurationFile); + dnsdist::configuration::lua::loadLuaConfigurationFile(*(g_lua.lock()), configurationFile, configCheck); + if (auto tentativeYamlConfFile = lookForTentativeConfigurationFileWithExtension(configurationFile, "yml")) { + vinfolog("Loading configuration from auto-discovered YAML file %s", *tentativeYamlConfFile); + return dnsdist::configuration::yaml::loadConfigurationFromFile(*tentativeYamlConfFile, isClient, configCheck); + } + } + else { + vinfolog("Loading configuration from Lua file %s", configurationFile); + dnsdist::configuration::lua::loadLuaConfigurationFile(*(g_lua.lock()), configurationFile, configCheck); + } + return true; +} + int main(int argc, char** argv) { try { @@ -3320,7 +3358,14 @@ int main(int argc, char** argv) }); if (cmdLine.beClient || !cmdLine.command.empty()) { - setupLua(*(g_lua.lock()), true, false, cmdLine.config); + dnsdist::lua::setupLua(*(g_lua.lock()), true, false); + if (!loadConfigurationFromFile(cmdLine.config, true, false)) { +#ifdef COVERAGE + exit(EXIT_FAILURE); +#else + _exit(EXIT_FAILURE); +#endif + } if (clientAddress != ComboAddress()) { dnsdist::configuration::updateRuntimeConfiguration([&clientAddress](dnsdist::configuration::RuntimeConfiguration& config) { config.d_consoleServerAddress = clientAddress; @@ -3350,7 +3395,14 @@ int main(int argc, char** argv) dnsdist::webserver::registerBuiltInWebHandlers(); if (cmdLine.checkConfig) { - setupLua(*(g_lua.lock()), false, true, cmdLine.config); + dnsdist::lua::setupLua(*(g_lua.lock()), false, true); + if (!loadConfigurationFromFile(cmdLine.config, false, true)) { +#ifdef COVERAGE + exit(EXIT_FAILURE); +#else + _exit(EXIT_FAILURE); +#endif + } // No exception was thrown infolog("Configuration '%s' OK!", cmdLine.config); #ifdef COVERAGE @@ -3368,7 +3420,14 @@ int main(int argc, char** argv) /* create the default pool no matter what */ createPoolIfNotExists(""); - setupLua(*(g_lua.lock()), false, false, cmdLine.config); + dnsdist::lua::setupLua(*(g_lua.lock()), false, false); + if (!loadConfigurationFromFile(cmdLine.config, false, false)) { +#ifdef COVERAGE + exit(EXIT_FAILURE); +#else + _exit(EXIT_FAILURE); +#endif + } setupPools(); @@ -3383,12 +3442,6 @@ int main(int argc, char** argv) } } - if (dnsdist::configuration::getImmutableConfiguration().d_maxTCPClientThreads == 0 && tcpBindsCount > 0) { - dnsdist::configuration::updateImmutableConfiguration([](dnsdist::configuration::ImmutableConfiguration& config) { - config.d_maxTCPClientThreads = static_cast(10); - }); - } - dnsdist::configuration::setImmutableConfigurationDone(); { @@ -3450,9 +3503,12 @@ int main(int argc, char** argv) g_delay = std::make_unique>(); #endif /* DISABLE_DELAY_PIPE */ - if (g_snmpAgent != nullptr) { +#if defined(HAVE_NET_SNMP) + if (dnsdist::configuration::getImmutableConfiguration().d_snmpEnabled) { + g_snmpAgent = std::make_unique("dnsdist", dnsdist::configuration::getImmutableConfiguration().d_snmpDaemonSocketPath); g_snmpAgent->run(); } +#endif /* HAVE_NET_SNMP */ /* we need to create the TCP worker threads before the acceptor ones, otherwise we might crash when processing @@ -3507,7 +3563,7 @@ int main(int argc, char** argv) checkFileDescriptorsLimits(udpBindsCount, tcpBindsCount); { - //coverity[auto_causes_copy] + // coverity[auto_causes_copy] const auto states = dnsdist::configuration::getCurrentRuntimeConfiguration().d_backends; // it is a copy, but the internal shared_ptrs are the real deal auto mplexer = std::unique_ptr(FDMultiplexer::getMultiplexerSilent(states.size())); for (auto& dss : states) { diff --git a/pdns/dnsdistdist/dnsdist.hh b/pdns/dnsdistdist/dnsdist.hh index d4bd9b9cb7c3..1ea7104cc0f0 100644 --- a/pdns/dnsdistdist/dnsdist.hh +++ b/pdns/dnsdistdist/dnsdist.hh @@ -719,6 +719,9 @@ private: bool d_stopped{false}; public: + static bool parseSourceParameter(const std::string& source, Config& config); + static std::optional getAvailabilityFromStr(const std::string& mode); + void updateStatisticsInfo() { auto delta = sw.udiffAndSet() / 1000000.0; @@ -892,17 +895,6 @@ void responderThread(std::shared_ptr dss); class DNSDistPacketCache; -class DNSRule -{ -public: - virtual ~DNSRule() - { - } - virtual bool matches(const DNSQuestion* dq) const = 0; - virtual string toString() const = 0; - mutable stat_t d_matches{0}; -}; - struct ServerPool { ServerPool() : diff --git a/pdns/dnsdistdist/docs/guides/cache.rst b/pdns/dnsdistdist/docs/guides/cache.rst index 0217ca605753..24b1a3f00da3 100644 --- a/pdns/dnsdistdist/docs/guides/cache.rst +++ b/pdns/dnsdistdist/docs/guides/cache.rst @@ -25,6 +25,22 @@ Something along the lines of a dozen bytes per pre-allocated entry can be expect That does not mean that the memory is completely allocated up-front, the final memory usage depending mostly on the size of cached responses and therefore varying during the cache's lifetime. Assuming an average response size of 512 bytes, a cache size of 10000000 entries on a 64-bit host with 8GB of dedicated RAM would be a safe choice. +The equivalent ``yaml`` configuration would be: + +.. code-block:: yaml + + packet_caches: + - name: "pc" + size: 1000 + max_ttl: 86400 + min_ttl: 0 + temporary_failure_ttl: 60 + state_ttl: 60 + dont_age: false + pools: + - name: "" + packet_cache: "pc" + The :func:`setStaleCacheEntriesTTL` directive can be used to allow dnsdist to use expired entries from the cache when no backend is available. Only entries that have expired for less than n seconds will be used, and the returned TTL can be set when creating a new cache with :func:`newPacketCache`. diff --git a/pdns/dnsdistdist/docs/guides/carbon.rst b/pdns/dnsdistdist/docs/guides/carbon.rst index bac9ffaaed2f..2cbad5ca5554 100644 --- a/pdns/dnsdistdist/docs/guides/carbon.rst +++ b/pdns/dnsdistdist/docs/guides/carbon.rst @@ -11,6 +11,19 @@ To emit metrics to Graphite, or any other software supporting the Carbon protoco Where ``ourname`` can be used to override your hostname, and ``30`` is the reporting interval in seconds. ``dnsdist`` and ``main`` are used as namespace and instance variables. For querycount statistics these two variables are currently ignored. The last four arguments can be omitted. The latest version of `PowerDNS Metronome `_ comes with attractive graphs for dnsdist by default. +The equivalent ``yaml`` configuration: + +.. code-block:: yaml + + metrics: + carbon: + - address: "ip-address-of-carbon-server" + name: "ourname" + interval: "30" + namespace: "dnsdist" + instance: "main" + + Query counters -------------- diff --git a/pdns/dnsdistdist/docs/guides/console.rst b/pdns/dnsdistdist/docs/guides/console.rst index b039320f7da2..0fd384c692fc 100644 --- a/pdns/dnsdistdist/docs/guides/console.rst +++ b/pdns/dnsdistdist/docs/guides/console.rst @@ -11,6 +11,14 @@ The console can be enabled with :func:`controlSocket`: controlSocket('192.0.2.53:5199') +Or in ``yaml``: + +.. code-block:: yaml + + console: + listen_address: "192.0.2.53:5199" + + Enabling the console without encryption enabled is not recommended. Note that encryption requires building dnsdist with either libsodium or libcrypto support enabled. Once you have a console-enabled dnsdist, the first step to enable encryption is to generate a key with :func:`makeKey`:: @@ -40,6 +48,12 @@ Then add the generated :func:`setKey` line to your dnsdist configuration file, a controlSocket('192.0.2.53:5199') -- Listen on this IP and port for client connections setKey("ENCODED KEY") -- Shared secret for the console +.. code-block:: yaml + + console: + listen_address: "192.0.2.53:5199" + key: "ENCODED KEY" + Now you can run ``dnsdist -c`` to connect to the console. This makes dnsdist read its configuration file and use the :func:`controlSocket` and :func:`setKey` statements to set up its connection to the server. @@ -60,6 +74,15 @@ Since 1.3.0, dnsdist supports restricting which client can connect to the consol controlSocket('192.0.2.53:5199') setConsoleACL('192.0.2.0/24') +.. code-block:: yaml + + console: + listen_address: "192.0.2.53:5199" + key: "ENCODED KEY" + acl: + - "192.0.2.0/24" + + The default value is '127.0.0.1', restricting the use of the console to local users. Please make sure that encryption is enabled before using :func:`addConsoleACL` or :func:`setConsoleACL` to allow connection from remote clients. Even if the console is restricted to local users, the use of encryption is still strongly advised to prevent unauthorized local users from connecting to diff --git a/pdns/dnsdistdist/docs/guides/dns-over-http3.rst b/pdns/dnsdistdist/docs/guides/dns-over-http3.rst index 0b907e5790dd..5feeec16e2e8 100644 --- a/pdns/dnsdistdist/docs/guides/dns-over-http3.rst +++ b/pdns/dnsdistdist/docs/guides/dns-over-http3.rst @@ -21,6 +21,19 @@ The fourth parameter, if present, indicates various options. For instance, you c addDOH3Local('2001:db8:1:f00::1', '/etc/ssl/certs/example.com.pem', '/etc/ssl/private/example.com.key', {congestionControlAlgo="bbr"}) +.. code-block:: yaml + + binds: + - listen_address: "2001:db8:1:f00::1" + protocol: "DoH3" + tls: + certificates: + - certificate: "/etc/ssl/certs/example.com.pem" + key: "/etc/ssl/private/example.com.key" + quic: + congestion_control_algorithm: "bbr" + + A particular attention should be taken to the permissions of the certificate and key files. Many ACME clients used to get and renew certificates, like CertBot, set permissions assuming that services are started as root, which is no longer true for dnsdist as of 1.5.0. For that particular case, making a copy of the necessary files in the /etc/dnsdist directory is advised, using for example CertBot's ``--deploy-hook`` feature to copy the files with the right permissions after a renewal. More information about sessions management can also be found in :doc:`../advanced/tls-sessions-management`. diff --git a/pdns/dnsdistdist/docs/guides/dns-over-https.rst b/pdns/dnsdistdist/docs/guides/dns-over-https.rst index c1f4fcbcf93e..2b8a015b7f4c 100644 --- a/pdns/dnsdistdist/docs/guides/dns-over-https.rst +++ b/pdns/dnsdistdist/docs/guides/dns-over-https.rst @@ -34,6 +34,25 @@ A more complicated (and more realistic) example is when you want to indicate met addDOHLocal('2001:db8:1:f00::1', '/etc/ssl/certs/example.com.pem', '/etc/ssl/private/example.com.key', "/", {customResponseHeaders={["link"]=" rel=\\"service-meta\\"; type=\\"text/html\\""}}) +Or in ``yaml``: + +.. code-block:: yaml + + - listen_address: "2001:db8:1:f00::1" + protocol: "DoH" + tls: + certificates: + - certificate: "/etc/ssl/certs/example.com.pem" + key: "/etc/ssl/private/example.com.key" + doh: + provider: "nghttp2" + paths: + - "/" + custom_response_headers: + - key: "link" + value: " rel=\\"service-meta\\"; type=\\"text/html\\"" + + A particular attention should be taken to the permissions of the certificate and key files. Many ACME clients used to get and renew certificates, like CertBot, set permissions assuming that services are started as root, which is no longer true for dnsdist as of 1.5.0. For that particular case, making a copy of the necessary files in the /etc/dnsdist directory is advised, using for example CertBot's ``--deploy-hook`` feature to copy the files with the right permissions after a renewal. More information about sessions management can also be found in :doc:`../advanced/tls-sessions-management`. @@ -128,6 +147,18 @@ That support can be enabled via the ``dohPath`` parameter of the :func:`newServe newServer({address="[2001:DB8::1]:443", tls="openssl", subjectName="doh.powerdns.com", dohPath="/dns-query", validateCertificates=true}) +.. code-block:: yaml + + backends: + - address: "127.0.0.1:%d" + protocol: "DoH" + tls: + provider: "openssl" + validate_certificate: true + subject_name: "doh.powerdns.com" + doh: + path: "/dns-query" + Internal design ^^^^^^^^^^^^^^^ diff --git a/pdns/dnsdistdist/docs/guides/dns-over-quic.rst b/pdns/dnsdistdist/docs/guides/dns-over-quic.rst index 957c82edbcd5..e7d1be18bb34 100644 --- a/pdns/dnsdistdist/docs/guides/dns-over-quic.rst +++ b/pdns/dnsdistdist/docs/guides/dns-over-quic.rst @@ -18,6 +18,19 @@ The fourth parameter, if present, indicates various options. For instance, you c addDOQLocal('2001:db8:1:f00::1', '/etc/ssl/certs/example.com.pem', '/etc/ssl/private/example.com.key', {congestionControlAlgo="bbr"}) +.. code-block:: yaml + + binds: + - listen_address: "2001:db8:1:f00::1" + protocol: "DoQ" + tls: + certificates: + - certificate: "/etc/ssl/certs/example.com.pem" + key: "/etc/ssl/private/example.com.key" + quic: + congestion_control_algorithm: "bbr" + + A particular attention should be taken to the permissions of the certificate and key files. Many ACME clients used to get and renew certificates, like CertBot, set permissions assuming that services are started as root, which is no longer true for dnsdist as of 1.5.0. For that particular case, making a copy of the necessary files in the /etc/dnsdist directory is advised, using for example CertBot's ``--deploy-hook`` feature to copy the files with the right permissions after a renewal. More information about sessions management can also be found in :doc:`../advanced/tls-sessions-management`. diff --git a/pdns/dnsdistdist/docs/guides/dns-over-tls.rst b/pdns/dnsdistdist/docs/guides/dns-over-tls.rst index 62362adebad2..430dbaa4ab70 100644 --- a/pdns/dnsdistdist/docs/guides/dns-over-tls.rst +++ b/pdns/dnsdistdist/docs/guides/dns-over-tls.rst @@ -18,6 +18,18 @@ In order to support multiple certificates and keys, for example an ECDSA and an addTLSLocal('192.0.2.55', {'/etc/ssl/certs/example.com.rsa.pem', '/etc/ssl/certs/example.com.ecdsa.pem'}, {'/etc/ssl/private/example.com.rsa.key', '/etc/ssl/private/example.com.ecdsa.key'}) +.. code-block:: yaml + + binds: + - listen_address: "192.0.2.55" + protocol: "DoT" + tls: + certificates: + - certificate: "/etc/ssl/certs/example.com.rsa.pem" + key: "/etc/ssl/private/example.com.rsa.key" + - certificate: "/etc/ssl/certs/example.com.ecdsa.pem" + key: "/etc/ssl/private/example.com.ecdsa.key" + The certificate chain presented by the server to an incoming client will then be selected based on the algorithms this client advertised support for. A particular attention should be taken to the permissions of the certificate and key files. Many ACME clients used to get and renew certificates, like CertBot, set permissions assuming that services are started as root, which is no longer true for dnsdist as of 1.5.0. For that particular case, making a copy of the necessary files in the /etc/dnsdist directory is advised, using for example CertBot's ``--deploy-hook`` feature to copy the files with the right permissions after a renewal. @@ -42,4 +54,3 @@ dnsdist provides a lot of counters to investigate issues: * :func:`showTCPStats` will display a lot of information about current and passed connections * :func:`showTLSErrorCounters` some metrics about why TLS sessions failed to establish - diff --git a/pdns/dnsdistdist/docs/guides/dnscrypt.rst b/pdns/dnsdistdist/docs/guides/dnscrypt.rst index 00b08d4aca01..562a0c0e2066 100644 --- a/pdns/dnsdistdist/docs/guides/dnscrypt.rst +++ b/pdns/dnsdistdist/docs/guides/dnscrypt.rst @@ -6,6 +6,21 @@ To make :program:`dnsdist` listen to incoming DNSCrypt queries on 127.0.0.1 port addDNSCryptBind("127.0.0.1:8443", "2.providername", "/path/to/resolver.cert", "/path/to/resolver.key") + +And in ``yaml``: + +.. code-block:: yaml + + binds: + - listen_address: "127.0.0.1:8443" + protocol: "DNSCrypt" + dnscrypt: + provider_name: "2.providername" + certificates: + - certificate: "/path/to/resolver.cert" + key: "/path/to/resolver.key" + + To generate the provider and resolver certificates and keys, you can simply do:: > generateDNSCryptProviderKeys("/path/to/providerPublic.key", "/path/to/providerPrivate.key") diff --git a/pdns/dnsdistdist/docs/guides/downstreams.rst b/pdns/dnsdistdist/docs/guides/downstreams.rst index 52e534915ea4..ffa27171226a 100644 --- a/pdns/dnsdistdist/docs/guides/downstreams.rst +++ b/pdns/dnsdistdist/docs/guides/downstreams.rst @@ -56,6 +56,20 @@ e.g.:: newServer({address="192.0.2.1", checkType="AAAA", checkClass=DNSClass.CHAOS, checkName="a.root-servers.net.", mustResolve=true}) +In ``yaml``: + +.. code-block:: yaml + + backends: + - address: "192.0.2.1" + protocol: "Do53" + health_checks: + qname: "a.root-servers.net." + qtype: "AAAA" + qclass: "CHAOS" + must_resolve: true + + You can turn on logging of health check errors using the :func:`setVerboseHealthChecks` function. Lazy health-checking @@ -86,6 +100,23 @@ So for example, if we set ``healthCheckMode`` to ``lazy``, ``lazyHealthCheckSamp newServer({address="192.0.2.1", healthCheckMode='lazy', checkInterval=1, lazyHealthCheckFailedInterval=30, rise=2, maxCheckFailures=3, lazyHealthCheckThreshold=30, lazyHealthCheckSampleSize=100, lazyHealthCheckMinSampleCount=10, lazyHealthCheckMode='TimeoutOnly'}) +.. code-block:: yaml + + backends: + - address: "192.0.2.1" + protocol: "Do53" + health_checks: + mode: "lazy" + rise: 2 + max_failures: 3 + check_interval: 1 + lazy: + mode: "TimeoutOnly" + interval: 30 + threshold: 30 + sample_size: 100 + min_sample_count: 10 + The 'lazy' mode also supports using an exponential back-off time between health-check queries, once a backend has been moved to the 'down' state. This can be enabled by setting the ``lazyHealthCheckUseExponentialBackOff`` parameter to 'true'. Once the backend has been marked as 'down', the first query will be sent after ``lazyHealthCheckFailedInterval`` seconds, the second one after 2 times ``lazyHealthCheckFailedInterval`` seconds, the third after 4 times ``lazyHealthCheckFailedInterval`` seconds, and so on and so forth, until ``lazyHealthCheckMaxBackOff`` has been reached. Then probes will be sent every ``lazyHealthCheckMaxBackOff`` seconds (default is 3600 so one hour) until the backend comes 'up' again. Source address selection @@ -98,6 +129,13 @@ interface used by dnsdist to contact a downstream server. This can be done by us newServer({address="192.0.2.1", source="eth1"}) newServer({address="192.0.2.1", source="192.0.2.127@eth1"}) +.. code-block:: yaml + + backends: + - address: "192.0.2.1" + protocol: "Do53" + source: "192.0.2.127@eth1" + The supported values for source are: - an IPv4 or IPv6 address, which must exist on the system diff --git a/pdns/dnsdistdist/docs/guides/serverpools.rst b/pdns/dnsdistdist/docs/guides/serverpools.rst index 18790ed4f30a..700ede19ea84 100644 --- a/pdns/dnsdistdist/docs/guides/serverpools.rst +++ b/pdns/dnsdistdist/docs/guides/serverpools.rst @@ -2,7 +2,7 @@ Server pools ------------ dnsdist has the concept to "server pools", any number of servers can belong to a group. -A default pool, identified by the empty string ``''`` is always present, and `newServer` without a pool argument will assign the new server to that pool. +A default pool, identified by the empty string ``''`` is always present, and :func:`newServer` without a pool argument will assign the new server to that pool. Let's say we know we're getting a whole bunch of traffic for a domain used in DoS attacks, for example 'example.com'. We can do two things with this kind of traffic. @@ -42,4 +42,3 @@ Traffic exceeding the :term:`QPS` limit will not match that rule, and subsequent getServer(4):addPool("abuse") getServer(4):rmPool("abuse") - diff --git a/pdns/dnsdistdist/docs/guides/webserver.rst b/pdns/dnsdistdist/docs/guides/webserver.rst index 8ac3ec32f484..534deb1788df 100755 --- a/pdns/dnsdistdist/docs/guides/webserver.rst +++ b/pdns/dnsdistdist/docs/guides/webserver.rst @@ -16,6 +16,18 @@ Since 1.5.0, only connections from 127.0.0.1 and ::1 are allowed by default. To setWebserverConfig({password="supersecretpassword", apiKey="supersecretAPIkey", acl="192.0.2.0/24, !192.0.2.1"}) +The equivalent ``yaml`` configuration would be: + +.. code-block:: yaml + + webserver: + listen_address: "127.0.0.1:8083" + password: "supersecretpassword" + api_key: "supersecretAPIkey" + acl: + - "192.0.2.0/24" + - "!192.0.2.1" + Security of the Webserver ------------------------- diff --git a/pdns/dnsdistdist/docs/index.rst b/pdns/dnsdistdist/docs/index.rst index a87777af4d04..8bc1eb741091 100644 --- a/pdns/dnsdistdist/docs/index.rst +++ b/pdns/dnsdistdist/docs/index.rst @@ -1,22 +1,44 @@ dnsdist Overview ================ -dnsdist is a highly DNS-, DoS- and abuse-aware loadbalancer. +:program:`dnsdist` is a highly DNS-, DoS- and abuse-aware loadbalancer. Its goal in life is to route traffic to the best server, delivering top performance to legitimate users while shunting or blocking abusive traffic. -dnsdist is dynamic, its configuration language is `Lua `_ and it can be changed at runtime, and its statistics can be queried from a console-like interface or an HTTP API. +:program:`dnsdist` is dynamic, its configuration can be changed at runtime via a :doc:`console-like interface `. +It exposes :doc:`metrics ` that can be exported via Carbon, Prometheus, an HTTP API and the console. + +Until 2.0.0 the configuration was written in `Lua `_, but it is now possible to write the configuration in :doc:`yaml ` as well. A configuration to balance DNS queries to several backend servers: .. code-block:: lua - newServer({address="2620:fe::fe", qps=1}) - newServer({address="2620:fe::9", qps=1}) - newServer({address="9.9.9.9", qps=1}) - newServer({address="2001:db8::1", qps=10}) - newServer({address="[2001:db8::2]:5300", name="dns1", qps=10}) + newServer({address="2620:fe::fe"}) + newServer({address="2620:fe::9"}) + newServer({address="9.9.9.9"}) + newServer({address="2001:db8::1"}) + newServer({address="[2001:db8::2]:5300", name="dns1"}) newServer("192.0.2.1") - setServerPolicy(firstAvailable) -- first server within its QPS limit + +Or in ``yaml``: + +.. code-block:: yaml + + backends: + - address: "2620:fe::fe" + protocol: Do53 + - address: "2620:fe::9" + protocol: Do53 + - address: "9.9.9.9" + protocol: Do53 + - address: "2001:db8::1" + protocol: Do53 + - address: "[2001:db8::1]:5300" + name: "dns1" + protocol: Do53 + - address: "192.0.2.1" + protocol: Do53 + Running dnsdist --------------- diff --git a/pdns/dnsdistdist/docs/install.rst b/pdns/dnsdistdist/docs/install.rst index 34e3af1bfdb0..5d455d76388c 100644 --- a/pdns/dnsdistdist/docs/install.rst +++ b/pdns/dnsdistdist/docs/install.rst @@ -9,7 +9,7 @@ Building from source is also supported. Installing from Packages ------------------------ -If dnsdist is available in your operating system's software repositories, install it from there. +If dnsdist is available in your operating system's software repositories, you can install it from there. However, the version of dnsdist in the repositories might be an older version that might not have a feature that was added in a later version. Or you might want to be brave and try a development snapshot from the master branch. PowerDNS provides software repositories for the most popular distributions. @@ -49,20 +49,21 @@ dnsdist depends on the following libraries: * `Lua `_ 5.1+ or `LuaJit `_ * `Editline (libedit) `_ * `libfstrm `_ (optional, dnstap support) -* `GnuTLS `_ (optional, DoT and outgoing DoH support) +* `GnuTLS `_ (optional, DoT and DoH support) * `libbpf `_ and `libxdp `_ (optional, `XSK`/`AF_XDP` support) * `libcap `_ (optional, capabilities support) * `libh2o `_ (optional, incoming DoH support, deprecated in 1.9.0 in favor of ``nghttp2``) -* `libsodium `_ (optional, DNSCrypt and console encryption support) +* `libsodium `_ (optional, DNSCrypt support) * `LMDB `_ (optional, LMDB support) * `net-snmp `_ (optional, SNMP support) -* `nghttp2 `_ (optional, outgoing DoH support) +* `nghttp2 `_ (optional, DoH support) * `OpenSSL `_ (optional, DoT and DoH support) -* `protobuf `_ (optional, not needed as of 1.6.0) -* `quiche `_ (optional, incoming DoQ support) +* `Quiche `_ (optional, incoming DoQ and DoH3 support) * `re2 `_ (optional) * `TinyCDB `_ (optional, CDB support) +Since 2.0.0, the optional ``yaml`` configuration requires a Rust compiler and a Python 3 interpreter. + Should :program:`dnsdist` be run on a system with systemd, it is highly recommended to have the systemd header files (``libsystemd-dev`` on Debian and ``systemd-devel`` on CentOS) installed to have :program:`dnsdist` support ``systemd-notify``. diff --git a/pdns/dnsdistdist/docs/quickstart.rst b/pdns/dnsdistdist/docs/quickstart.rst index 179f841d1d4a..e10f52ad24db 100644 --- a/pdns/dnsdistdist/docs/quickstart.rst +++ b/pdns/dnsdistdist/docs/quickstart.rst @@ -17,6 +17,8 @@ This will make dnsdist listen on IP address 127.0.0.1, port 5300 and forward all Here is more complete configuration, save it to ``dnsdist.conf``:: +.. code-block:: lua + newServer({address="2001:db8::1", qps=1}) newServer({address="2001:db8::2", qps=1}) newServer({address="[2001:db8::3]:5300", qps=10}) @@ -26,6 +28,29 @@ Here is more complete configuration, save it to ``dnsdist.conf``:: The :func:`newServer` function is used to add a backend server to the configuration. +The ``yaml`` equivalent, from 2.0+ onwards, would be: + +.. code-block:: yaml + + backends: + - address: "2001:db8::1" + protocol: Do53 + qps: 1 + - address: "2001:db8::2" + protocol: Do53 + qps: 1 + - address: "[2001:db8::3]:5300" + protocol: Do53 + qps: 10 + - address: "[2001:db8::4]" + name: "dns1" + protocol: Do53 + qps: 10 + - address: "192.0.2.1" + protocol: Do53 + load_balancing_policies: + default_policy: "firstAvailable" + Now run dnsdist again, reading this configuration:: $ dnsdist -C dnsdist.conf --local=0.0.0.0:5300 @@ -118,6 +143,20 @@ Adding network ranges to the :term:`ACL` is done with the :func:`setACL` and :fu setACL({'192.0.2.0/28', '2001:db8:1::/56'}) -- Set the ACL to only allow these subnets addACL('2001:db8:2::/56') -- Add this subnet to the existing ACL +And in ``yaml`` format: + +.. code-block:: yaml +acl: + - "192.0.2.0/28" + - "2001:db8:1::/56" + - "2001:db8:2::/56" +binds: + - listen_address: "192.0.2.53" + protocol: Do53 + - listen_address: "[::1]:5300" + protocol: Do53 + + Securing the path to the backend -------------------------------- diff --git a/pdns/dnsdistdist/docs/reference/actions.rst b/pdns/dnsdistdist/docs/reference/actions.rst index e665a774626c..5442d647c4c7 100644 --- a/pdns/dnsdistdist/docs/reference/actions.rst +++ b/pdns/dnsdistdist/docs/reference/actions.rst @@ -2,6 +2,8 @@ Rule Actions ============ :doc:`selectors` need to be combined with an action for them to actually do something with the matched packets. +This page describes the ``Lua`` versions of these actions, for the ``YAML`` version please see :doc:`yaml-actions` and :doc:`yaml-response-actions`. + Some actions allow further processing of rules, this is noted in their description. Most of these start with 'Set' with a few exceptions, mostly for logging actions. These exceptions are: - :func:`ClearRecordTypesResponseAction` diff --git a/pdns/dnsdistdist/docs/reference/config.rst b/pdns/dnsdistdist/docs/reference/config.rst index e3fe1835b42d..11521e1be42b 100644 --- a/pdns/dnsdistdist/docs/reference/config.rst +++ b/pdns/dnsdistdist/docs/reference/config.rst @@ -265,7 +265,7 @@ Listen Sockets * ``reusePort=false``: bool - Set the ``SO_REUSEPORT`` socket option. * ``tcpFastOpenQueueSize=0``: int - Set the TCP Fast Open queue size, enabling TCP Fast Open when available and the value is larger than 0. * ``interface=""``: str - Set the network interface to use. - * ``cpus={}``: table - Set the CPU affinity for this listener thread, asking the scheduler to run it on a single CPU id, or a set of CPU ids. This parameter is only available if the OS provides the pthread_setaffinity_np() function. + * ``cpus={}``: table - Set the CPU affinity for this listener thread, asking the scheduler to run it on a single CPU id, or a set of CPU ids. This parameter is only available if the OS provides the ``pthread_setaffinity_np()`` function. * ``provider``: str - The TLS library to use between GnuTLS and OpenSSL, if they were available and enabled at compilation time. Default is to use OpenSSL when available. * ``ciphers``: str - The TLS ciphers to use. The exact format depends on the provider used. When the OpenSSL provider is used, ciphers for TLS 1.3 must be specified via ``ciphersTLS13``. * ``ciphersTLS13``: str - The ciphers to use for TLS 1.3, when the OpenSSL provider is used. When the GnuTLS provider is used, ``ciphers`` applies regardless of the TLS protocol and this setting is not used. @@ -693,7 +693,7 @@ Servers ``tcpSendTimeout`` ``number`` "The timeout (in seconds) of a TCP write attempt" ``tcpRecvTimeout`` ``number`` "The timeout (in seconds) of a TCP read attempt" ``tcpFastOpen`` ``bool`` "Whether to enable TCP Fast Open" - ``ipBindAddrNoPort`` ``bool`` "Whether to enable IP_BIND_ADDRESS_NO_PORT if available, default: true" + ``ipBindAddrNoPort`` ``bool`` "Whether to enable ``IP_BIND_ADDRESS_NO_PORT`` if available, default: true" ``name`` ``string`` "The name associated to this backend, for display purpose" ``checkClass`` ``number`` "Use ``number`` as QCLASS in the health-check query, default: DNSClass.IN" ``checkName`` ``string`` "Use ``string`` as QNAME in the health-check query, default: ``""a.root-servers.net.""`` " diff --git a/pdns/dnsdistdist/docs/reference/dnscrypt.rst b/pdns/dnsdistdist/docs/reference/dnscrypt.rst index 97bc83dcb604..46f2f89e8dba 100644 --- a/pdns/dnsdistdist/docs/reference/dnscrypt.rst +++ b/pdns/dnsdistdist/docs/reference/dnscrypt.rst @@ -17,7 +17,7 @@ DNSCrypt objects and functions :param string address: The address and port to listen on :param string provider: The provider name for this bind - :param str certFile(s): The path to a X.509 certificate file in PEM format, or a list of paths to such files. + :param str certFile(s): The path to a DNSCrypt certificate file, or a list of paths to such files. :param str keyFile(s): The path to the private key file corresponding to the certificate, or a list of paths to such files, whose order should match the certFile(s) ones. :param table options: A table with key: value pairs with options (see below) diff --git a/pdns/dnsdistdist/docs/reference/index.rst b/pdns/dnsdistdist/docs/reference/index.rst index 4f2938705ad5..94dbdfd4f9d5 100755 --- a/pdns/dnsdistdist/docs/reference/index.rst +++ b/pdns/dnsdistdist/docs/reference/index.rst @@ -31,3 +31,8 @@ These chapters contain extensive information on all functions and object availab svc custommetrics xsk + yaml-settings + yaml-selectors + yaml-actions + yaml-response-actions + yaml-support-structures diff --git a/pdns/dnsdistdist/docs/reference/selectors.rst b/pdns/dnsdistdist/docs/reference/selectors.rst index e0eeaa37a168..2830f68dd4cf 100644 --- a/pdns/dnsdistdist/docs/reference/selectors.rst +++ b/pdns/dnsdistdist/docs/reference/selectors.rst @@ -11,6 +11,8 @@ These ``DNSRule``\ s be one of the following items: * A list of :class:`DNSName`\ s * A (compounded) ``Rule`` +This page describes the ``Lua`` versions of these selectors, for the ``YAML`` version please see :doc:`yaml-selectors`. + Selectors can be combined via :func:`AndRule`, :func:`OrRule` and :func:`NotRule`. .. function:: AllRule() diff --git a/pdns/dnsdistdist/docs/reference/yaml-actions.rst b/pdns/dnsdistdist/docs/reference/yaml-actions.rst new file mode 100644 index 000000000000..4d76a602c645 --- /dev/null +++ b/pdns/dnsdistdist/docs/reference/yaml-actions.rst @@ -0,0 +1,653 @@ +.. THIS IS A GENERATED FILE. DO NOT EDIT. See dnsdist-settings-documentation-generator.py + +.. raw:: latex + + \setcounter{secnumdepth}{-1} + +.. _yaml-settings-Action: + +YAML action reference +===================== + +.. _yaml-settings-AllowAction: + +AllowAction +----------- + +Let these packets go through + +Lua equivalent: :func:`AllowAction` + +.. _yaml-settings-ContinueAction: + +ContinueAction +-------------- + +Execute the specified action and override its return with None, making it possible to continue the processing. Subsequent rules are processed after this action + +Lua equivalent: :func:`ContinueAction` + +Parameters: + +- **action**: :ref:`Action ` - The action to execute + + +.. _yaml-settings-DelayAction: + +DelayAction +----------- + +Delay the response by the specified amount of milliseconds (UDP-only). Note that the sending of the query to the backend, if needed, is not delayed. Only the sending of the response to the client will be delayed. Subsequent rules are processed after this action + +Lua equivalent: :func:`DelayAction` + +Parameters: + +- **msec**: Unsigned integer - The amount of milliseconds to delay the response + + +.. _yaml-settings-DnstapLogAction: + +DnstapLogAction +--------------- + +Send the current query to a remote logger as a dnstap message. ``alter_function`` is a callback, receiving a :class:`DNSQuestion` and a :class:`DnstapMessage`, that can be used to modify the message. Subsequent rules are processed after this action + +Lua equivalent: :func:`DnstapLogAction` + +Parameters: + +- **identity**: String - Server identity to store in the dnstap message +- **logger_name**: String - The name of dnstap logger +- **alter_function_name**: String ``("")`` - The name of the Lua function that will alter the message +- **alter_function_code**: String ``("")`` - The code of the Lua function that will alter the message +- **alter_function_file**: String ``("")`` - The path to a file containing the code of the Lua function that will alter the message + + +.. _yaml-settings-DropAction: + +DropAction +---------- + +Drop the packet + +Lua equivalent: :func:`DropAction` + +.. _yaml-settings-SetEDNSOptionAction: + +SetEDNSOptionAction +------------------- + +Add arbitrary EDNS option and data to the query. Any existing EDNS content with the same option code will be overwritten. Subsequent rules are processed after this action + +Lua equivalent: :func:`SetEDNSOptionAction` + +Parameters: + +- **code**: Unsigned integer - The EDNS option number +- **data**: String - The EDNS0 option raw content + + +.. _yaml-settings-ERCodeAction: + +ERCodeAction +------------ + +Reply immediately by turning the query into a response with the specified EDNS extended rcode + +Lua equivalent: :func:`ERCodeAction` + +Parameters: + +- **rcode**: Unsigned integer - The RCODE to respond with +- **vars**: :ref:`ResponseConfig ` - The response options + + +.. _yaml-settings-HTTPStatusAction: + +HTTPStatusAction +---------------- + +Return an HTTP response with a status code of ``status``. For HTTP redirects, ``body`` should be the redirect URL + +Lua equivalent: :func:`HTTPStatusAction` + +Parameters: + +- **status**: Unsigned integer - The HTTP status code to return +- **body**: String - The body of the HTTP response, or a URL if the status code is a redirect (3xx) +- **content_type**: String ``("")`` - The HTTP Content-Type header to return for a 200 response, ignored otherwise. Default is ``application/dns-message`` +- **vars**: :ref:`ResponseConfig ` - The response options + + +.. _yaml-settings-KeyValueStoreLookupAction: + +KeyValueStoreLookupAction +------------------------- + +Does a lookup into the key value store using the key returned by ``lookup_key_name``, and storing the result if any into the tag named ``destination_tag``. The store can be a ``CDB`` or a ``LMDB`` database. The key can be based on the qname, source IP or the value of an existing tag. Subsequent rules are processed after this action. Note that the tag is always created, even if there was no match, but in that case the content is empty + +Lua equivalent: :func:`KeyValueStoreLookupAction` + +Parameters: + +- **kvs_name**: String - The name of the KV store +- **lookup_key_name**: String - The name of the key to use for the lookup +- **destination_tag**: String - The name of the tag to store the result into + + +.. _yaml-settings-KeyValueStoreRangeLookupAction: + +KeyValueStoreRangeLookupAction +------------------------------ + +Does a range-based lookup into the key value store using the key returned by ``lookup_key_name``, and storing the result if any into the tag named ``destination_tag``. This assumes that there is a key in network byte order for the last element of the range (for example ``2001:0db8:ffff:ffff:ffff:ffff:ffff:ffff`` for ``2001:db8::/32``) which contains the first element of the range (``2001:0db8:0000:0000:0000:0000:0000:0000``) (optionally followed by any data) as value, also in network byte order, and that there is no overlapping ranges in the database. This requires that the underlying store supports ordered keys, which is true for LMDB but not for CDB + +Lua equivalent: :func:`KeyValueStoreRangeLookupAction` + +Parameters: + +- **kvs_name**: String - The name of the KV store +- **lookup_key_name**: String - The name of the key to use for the lookup +- **destination_tag**: String - The name of the tag to store the result into + + +.. _yaml-settings-LogAction: + +LogAction +--------- + +Log a line for each query, to the specified file if any, to the console (require verbose) if the empty string is given as filename. If an empty string is supplied in the file name, the logging is done to stdout, and only in verbose mode by default. This can be changed by setting ``verbose_only`` to ``false``. When logging to a file, the ``binary`` parameter specifies whether we log in binary form (default) or in textual form. The ``append`` parameter specifies whether we open the file for appending or truncate each time (default). The ``buffered`` parameter specifies whether writes to the file are buffered (default) or not. Subsequent rules are processed after this action + +Lua equivalent: :func:`LogAction` + +Parameters: + +- **file_name**: String ``("")`` - File to log to. Set to an empty string to log to the normal stdout log, this only works when ``-v`` is set on the command line +- **binary**: Boolean ``(true)`` - Whether to do binary logging +- **append**: Boolean ``(false)`` - Whether to append to an existing file +- **buffered**: Boolean ``(false)`` - Whether to use buffered I/O +- **verbose_only**: Boolean ``(true)`` - Whether to log only in verbose mode when logging to stdout +- **include_timestamp**: Boolean ``(false)`` - Whether to include a timestamp for every entry + + +.. _yaml-settings-LuaAction: + +LuaAction +--------- + +Invoke a Lua function that accepts a :class:`DNSQuestion`. The function should return a :ref:`DNSAction`. If the Lua code fails, ``ServFail`` is returned + +Lua equivalent: :func:`LuaAction` + +Parameters: + +- **function_name**: String ``("")`` - The name of the Lua function +- **function_code**: String ``("")`` - The code of the Lua function +- **function_file**: String ``("")`` - The path to a file containing the code of the Lua function + + +.. _yaml-settings-LuaFFIAction: + +LuaFFIAction +------------ + +Invoke a Lua function that accepts a pointer to a ``dnsdist_ffi_dnsquestion_t`` object, whose bindings are defined in ``dnsdist-lua-ffi-interface.h``. The function should return a :ref:`DNSAction`. If the Lua code fails, ``ServFail`` is returned + +Lua equivalent: :func:`LuaFFIAction` + +Parameters: + +- **function_name**: String ``("")`` - The name of the Lua function +- **function_code**: String ``("")`` - The code of the Lua function +- **function_file**: String ``("")`` - The path to a file containing the code of the Lua function + + +.. _yaml-settings-LuaFFIPerThreadAction: + +LuaFFIPerThreadAction +--------------------- + +Invoke a Lua function that accepts a pointer to a ``dnsdist_ffi_dnsquestion_t`` object, whose bindings are defined in ``dnsdist-lua-ffi-interface.h``. The function should return a :ref:`DNSAction`. If the Lua code fails, ``ServFail`` is returned. The function will be invoked in a per-thread Lua state, without access to the global Lua state. All constants (:ref:`DNSQType`, :ref:`DNSRCode`, ...) are available in that per-thread context, as well as all FFI functions. Objects and their bindings that are not usable in a FFI context (:class:`DNSQuestion`, :class:`DNSDistProtoBufMessage`, :class:`PacketCache`, ...) are not available. + +Lua equivalent: :func:`LuaFFIPerThreadAction` + +Parameters: + +- **code**: String - The code of the Lua function + + +.. _yaml-settings-NegativeAndSOAAction: + +NegativeAndSOAAction +-------------------- + +Turn a question into a response, either a ``NXDOMAIN`` or a ``NODATA`` one based on ``nxd``, setting the ``QR`` bit to ``1`` and adding a ``SOA`` record in the additional section + +Lua equivalent: :func:`NegativeAndSOAAction` + +Parameters: + +- **nxd**: Boolean - Whether the answer is a NXDOMAIN (true) or a NODATA (false) +- **zone**: String - The owner name for the SOA record +- **ttl**: Unsigned integer - The TTL of the SOA record +- **mname**: String - The mname of the SOA record +- **rname**: String - The rname of the SOA record +- **soa_parameters**: :ref:`SOAParams ` - The fields of the SOA record +- **soa_in_authority**: Boolean ``(false)`` - Whether the SOA record should be the authority section for a complete NXDOMAIN/NODATA response that works as a cacheable negative response, rather than the RPZ-style response with a purely informational SOA in the additional section. Default is false (SOA in additional section) +- **vars**: :ref:`ResponseConfig ` - Response options + + +.. _yaml-settings-NoneAction: + +NoneAction +---------- + +Does nothing. Subsequent rules are processed after this action + +Lua equivalent: :func:`NoneAction` + +.. _yaml-settings-PoolAction: + +PoolAction +---------- + +Send the packet into the specified pool. If ``stop_processing`` is set to ``false``, subsequent rules will be processed after this action + +Lua equivalent: :func:`PoolAction` + +Parameters: + +- **pool_name**: String - The name of the pool +- **stop_processing**: Boolean ``(true)`` - Whether subsequent rules should be executed after this one + + +.. _yaml-settings-QPSAction: + +QPSAction +--------- + +Drop a packet if it does exceed the ``limit`` queries per second limit. Letting the subsequent rules apply otherwise + +Lua equivalent: :func:`QPSAction` + +Parameters: + +- **limit**: Unsigned integer - The QPS limit + + +.. _yaml-settings-QPSPoolAction: + +QPSPoolAction +------------- + +Send the packet into the specified pool only if it does not exceed the ``limit`` queries per second limit. If ``stop-processing`` is set to ``false``, subsequent rules will be processed after this action. Letting the subsequent rules apply otherwise + +Lua equivalent: :func:`QPSPoolAction` + +Parameters: + +- **limit**: Unsigned integer - The QPS limit +- **pool_name**: String - The name of the pool +- **stop_processing**: Boolean ``(true)`` - Whether subsequent rules should be executed after this one + + +.. _yaml-settings-RCodeAction: + +RCodeAction +----------- + +Reply immediately by turning the query into a response with the specified rcode + +Lua equivalent: :func:`RCodeAction` + +Parameters: + +- **rcode**: Unsigned integer - The response code +- **vars**: :ref:`ResponseConfig ` - Response options + + +.. _yaml-settings-RemoteLogAction: + +RemoteLogAction +--------------- + +Send the current query to a remote logger as a Protocol Buffer message. ``alter_function`` is a callback, receiving a :class:`DNSQuestion` and a :class:`DNSDistProtoBufMessage`, that can be used to modify the message, for example for anonymization purposes. Subsequent rules are processed after this action + +Lua equivalent: :func:`RemoteLogAction` + +Parameters: + +- **logger_name**: String - The name of the protocol buffer logger +- **alter_function_name**: String ``("")`` - The name of the Lua function +- **alter_function_code**: String ``("")`` - The code of the Lua function +- **alter_function_file**: String ``("")`` - The path to a file containing the code of the Lua function +- **server_id**: String ``("")`` - Set the Server Identity field +- **ip_encrypt_key**: String ``("")`` - A key, that can be generated via the :func:`makeIPCipherKey` function, to encrypt the IP address of the requestor for anonymization purposes. The encryption is done using ipcrypt for IPv4 and a 128-bit AES ECB operation for IPv6 +- **export_tags**: Sequence of String ``("")`` - The comma-separated list of keys of internal tags to export into the ``tags`` Protocol Buffer field, as ``key:value`` strings. Note that a tag with an empty value will be exported as ````, not ``:``. An empty string means that no internal tag will be exported. The special value ``*`` means that all tags will be exported +- **metas**: Sequence of :ref:`ProtoBufMetaConfiguration ` - A list of ``name``=``key`` pairs, for meta-data to be added to Protocol Buffer message + + +.. _yaml-settings-SetAdditionalProxyProtocolValueAction: + +SetAdditionalProxyProtocolValueAction +------------------------------------- + +Add a Proxy-Protocol Type-Length value to be sent to the server along with this query. It does not replace any existing value with the same type but adds a new value. Be careful that Proxy Protocol values are sent once at the beginning of the TCP connection for TCP and DoT queries. That means that values received on an incoming TCP connection will be inherited by subsequent queries received over the same incoming TCP connection, if any, but values set to a query will not be inherited by subsequent queries. Subsequent rules are processed after this action + +Lua equivalent: :func:`SetAdditionalProxyProtocolValueAction` + +Parameters: + +- **proxy_type**: Unsigned integer - The proxy protocol type +- **value**: String - The value + + +.. _yaml-settings-SetDisableECSAction: + +SetDisableECSAction +------------------- + +Disable the sending of ECS to the backend. Subsequent rules are processed after this action + +Lua equivalent: :func:`SetDisableECSAction` + +.. _yaml-settings-SetDisableValidationAction: + +SetDisableValidationAction +-------------------------- + +Set the CD bit in the query and let it go through. Subsequent rules are processed after this action + +Lua equivalent: :func:`SetDisableValidationAction` + +.. _yaml-settings-SetECSAction: + +SetECSAction +------------ + +Set the ECS prefix and prefix length sent to backends to an arbitrary value. If both IPv4 and IPv6 masks are supplied the IPv4 one will be used for IPv4 clients and the IPv6 one for IPv6 clients. Otherwise the first mask is used for both, and can actually be an IPv6 mask. Subsequent rules are processed after this action + +Lua equivalent: :func:`SetECSAction` + +Parameters: + +- **ipv4**: String - The IPv4 netmask, for example 192.0.2.1/32 +- **ipv6**: String ``("")`` - The IPv6 netmask, if any + + +.. _yaml-settings-SetECSOverrideAction: + +SetECSOverrideAction +-------------------- + +Whether an existing EDNS Client Subnet value should be overridden (true) or not (false). Subsequent rules are processed after this action + +Lua equivalent: :func:`SetECSOverrideAction` + +Parameters: + +- **override_existing**: Boolean - Whether to override an existing EDNS Client Subnet value + + +.. _yaml-settings-SetECSPrefixLengthAction: + +SetECSPrefixLengthAction +------------------------ + +Set the ECS prefix length. Subsequent rules are processed after this action + +Lua equivalent: :func:`SetECSPrefixLengthAction` + +Parameters: + +- **ipv4**: Unsigned integer - The IPv4 netmask length +- **ipv6**: Unsigned integer - The IPv6 netmask length + + +.. _yaml-settings-SetExtendedDNSErrorAction: + +SetExtendedDNSErrorAction +------------------------- + +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 + +Lua equivalent: :func:`SetExtendedDNSErrorAction` + +Parameters: + +- **info_code**: Unsigned integer - The EDNS Extended DNS Error code +- **extra_text**: String ``("")`` - The optional EDNS Extended DNS Error extra text + + +.. _yaml-settings-SetMacAddrAction: + +SetMacAddrAction +---------------- + +Add the source MAC address to the query as an EDNS0 option. This action is currently only supported on Linux. Subsequent rules are processed after this action + +Lua equivalent: :func:`SetMacAddrAction` + +Parameters: + +- **code**: Unsigned integer - The EDNS option code + + +.. _yaml-settings-SetMaxReturnedTTLAction: + +SetMaxReturnedTTLAction +----------------------- + +Cap the TTLs of the response to the given maximum, but only after inserting the response into the packet cache with the initial TTL value + +Lua equivalent: :func:`SetMaxReturnedTTLAction` + +Parameters: + +- **max**: Unsigned integer - The TTL cap + + +.. _yaml-settings-SetNoRecurseAction: + +SetNoRecurseAction +------------------ + +Strip RD bit from the question, let it go through. Subsequent rules are processed after this action + +Lua equivalent: :func:`SetNoRecurseAction` + +.. _yaml-settings-SetProxyProtocolValuesAction: + +SetProxyProtocolValuesAction +---------------------------- + +Set the Proxy-Protocol Type-Length values to be sent to the server along with this query to values. Subsequent rules are processed after this action + +Lua equivalent: :func:`SetProxyProtocolValuesAction` + +Parameters: + +- **values**: Sequence of :ref:`ProxyProtocolValueConfiguration ` - List of proxy protocol values + + +.. _yaml-settings-SetSkipCacheAction: + +SetSkipCacheAction +------------------ + +Don’t lookup the cache for this query, don’t store the answer. Subsequent rules are processed after this action. + +Lua equivalent: :func:`SetSkipCacheAction` + +.. _yaml-settings-SetTagAction: + +SetTagAction +------------ + +Associate a tag named ``tag`` with a value of ``value`` to this query, that will be passed on to the response. This function will overwrite any existing tag value. Subsequent rules are processed after this action + +Lua equivalent: :func:`SetTagAction` + +Parameters: + +- **tag**: String - The tag name +- **value**: String - The tag value + + +.. _yaml-settings-SetTempFailureCacheTTLAction: + +SetTempFailureCacheTTLAction +---------------------------- + +Set the cache TTL to use for ServFail and Refused replies. TTL is not applied for successful replies. Subsequent rules are processed after this action + +Lua equivalent: :func:`SetTempFailureCacheTTLAction` + +Parameters: + +- **ttl**: Unsigned integer - The TTL to use + + +.. _yaml-settings-SNMPTrapAction: + +SNMPTrapAction +-------------- + +Send an SNMP trap, adding the message string as the query description. Subsequent rules are processed after this action + +Lua equivalent: :func:`SNMPTrapAction` + +Parameters: + +- **reason**: String ``("")`` - The SNMP trap reason + + +.. _yaml-settings-SpoofAction: + +SpoofAction +----------- + +Forge a response with the specified IPv4 (for an A query) or IPv6 (for an AAAA) addresses. If you specify multiple addresses, all that match the query type (A, AAAA or ANY) will get spoofed in + +Lua equivalent: :func:`SpoofAction` + +Parameters: + +- **ips**: Sequence of String - List of IP addresses to spoof +- **vars**: :ref:`ResponseConfig ` - Response options + + +.. _yaml-settings-SpoofCNAMEAction: + +SpoofCNAMEAction +---------------- + +Forge a response with the specified CNAME value. Please be aware that DNSdist will not chase the target of the CNAME, so it will not be present in the response which might be a problem for stub resolvers that do not know how to follow a CNAME + +Lua equivalent: :func:`SpoofCNAMEAction` + +Parameters: + +- **cname**: String - The CNAME to use in the response +- **vars**: :ref:`ResponseConfig ` - Response options + + +.. _yaml-settings-SpoofPacketAction: + +SpoofPacketAction +----------------- + +Spoof a raw self-generated answer + +Lua equivalent: :func:`SpoofPacketAction` + +Parameters: + +- **response**: String - The DNS packet +- **len**: Unsigned integer - The length of the DNS packet + + +.. _yaml-settings-SpoofRawAction: + +SpoofRawAction +-------------- + +Forge a response with the specified raw bytes as record data +.. code-block:: Lua + + -- select queries for the 'raw.powerdns.com.' name and TXT type, and answer with both a "aaa" "bbbb" and "ccc" TXT record: + addAction(AndRule({QNameRule('raw.powerdns.com.'), QTypeRule(DNSQType.TXT)}), SpoofRawAction({"\003aaa\004bbbb", "\003ccc"})) + -- select queries for the 'raw-srv.powerdns.com.' name and SRV type, and answer with a '0 0 65535 srv.powerdns.com.' SRV record, setting the AA bit to 1 and the TTL to 3600s + addAction(AndRule({QNameRule('raw-srv.powerdns.com.'), QTypeRule(DNSQType.SRV)}), SpoofRawAction("\000\000\000\000\255\255\003srv\008powerdns\003com\000", { aa=true, ttl=3600 })) + -- select reverse queries for '127.0.0.1' and answer with 'localhost' + addAction(AndRule({QNameRule('1.0.0.127.in-addr.arpa.'), QTypeRule(DNSQType.PTR)}), SpoofRawAction("\009localhost\000")) + -- rfc8482: Providing Minimal-Sized Responses to DNS Queries That Have QTYPE=ANY via HINFO of value "rfc8482" + addAction(QTypeRule(DNSQType.ANY), SpoofRawAction("\007rfc\056\052\056\050\000", { typeForAny=DNSQType.HINFO })) + +:func:`DNSName:toDNSString` is convenient for converting names to wire format for passing to ``SpoofRawAction``. + +``sdig dumpluaraw`` and ``pdnsutil raw-lua-from-content`` from PowerDNS can generate raw answers for you: + +.. code-block:: Shell + + $ pdnsutil raw-lua-from-content SRV '0 0 65535 srv.powerdns.com.' + "\000\000\000\000\255\255\003srv\008powerdns\003com\000" + $ sdig 127.0.0.1 53 open-xchange.com MX recurse dumpluaraw + Reply to question for qname='open-xchange.com.', qtype=MX + Rcode: 0 (No Error), RD: 1, QR: 1, TC: 0, AA: 0, opcode: 0 + 0 open-xchange.com. IN MX "\000c\004mx\049\049\012open\045xchange\003com\000" + 0 open-xchange.com. IN MX "\000\010\003mx\049\012open\045xchange\003com\000" + 0 open-xchange.com. IN MX "\000\020\003mx\050\012open\045xchange\003com\000" + + +Lua equivalent: :func:`SpoofRawAction` + +Parameters: + +- **answers**: Sequence of String - A list of DNS record content entries to use in the response +- **qtype_for_any**: String ``("")`` - The type to use for ANY queries +- **vars**: :ref:`ResponseConfig ` - Response options + + +.. _yaml-settings-SpoofSVCAction: + +SpoofSVCAction +-------------- + +Forge a response with the specified ``SVC`` record data. If the list contains more than one ``SVC`` parameter, they are all returned, and should have different priorities. The hints provided in the SVC parameters, if any, will also be added as ``A``/``AAAA`` records in the additional section, using the target name present in the parameters as owner name if it’s not empty (root) and the qname instead + +Lua equivalent: :func:`SpoofSVCAction` + +Parameters: + +- **parameters**: Sequence of :ref:`SVCRecordParameters ` - List of SVC record parameters +- **vars**: :ref:`ResponseConfig ` - Response options + + +.. _yaml-settings-TCAction: + +TCAction +-------- + +Create answer to query with the ``TC`` bit set, and the ``RA`` bit set to the value of ``RD`` in the query, to force the client to TCP + +Lua equivalent: :func:`TCAction` + +.. _yaml-settings-TeeAction: + +TeeAction +--------- + +Send copy of query to remote, keep stats on responses. If ``add_ecs`` is set to true, EDNS Client Subnet information will be added to the query. If ``add_proxy_protocol`` is set to true, a Proxy Protocol v2 payload will be prepended in front of the query. The payload will contain the protocol the initial query was received over (UDP or TCP), as well as the initial source and destination addresses and ports. If ``lca`` has provided a value like “192.0.2.53”, dnsdist will try binding that address as local address when sending the queries. Subsequent rules are processed after this action + +Lua equivalent: :func:`TeeAction` + +Parameters: + +- **rca**: String - The address and port of the remote server +- **lca**: String ``("")`` - The source address to use to send packets to the remote server +- **add_ecs**: Boolean ``(false)`` - Whether to add EDNS Client Subnet to the query +- **add_proxy_protocol**: Boolean ``(false)`` - Whether to add a proxy protocol payload to the query + + diff --git a/pdns/dnsdistdist/docs/reference/yaml-response-actions.rst b/pdns/dnsdistdist/docs/reference/yaml-response-actions.rst new file mode 100644 index 000000000000..0efaa216cf12 --- /dev/null +++ b/pdns/dnsdistdist/docs/reference/yaml-response-actions.rst @@ -0,0 +1,296 @@ +.. THIS IS A GENERATED FILE. DO NOT EDIT. See dnsdist-settings-documentation-generator.py + +.. raw:: latex + + \setcounter{secnumdepth}{-1} + +.. _yaml-settings-ResponseAction: + +YAML response-action reference +============================== + +.. _yaml-settings-AllowResponseAction: + +AllowResponseAction +------------------- + +Let these packets go through. + +Lua equivalent: :func:`AllowResponseAction` + +.. _yaml-settings-ClearRecordTypesResponseAction: + +ClearRecordTypesResponseAction +------------------------------ + +Removes given type(s) records from the response. Beware you can accidentally turn the answer into a NODATA response without a SOA record in the additional section in which case you may want to use NegativeAndSOAAction() to generate an answer, see example below. Subsequent rules are processed after this action. + +Lua equivalent: :func:`ClearRecordTypesResponseAction` + +Parameters: + +- **types**: Sequence of Unsigned integer - List of types to remove + + +.. _yaml-settings-DelayResponseAction: + +DelayResponseAction +------------------- + +Delay the response by the specified amount of milliseconds (UDP-only). Note that the sending of the query to the backend, if needed, is not delayed. Only the sending of the response to the client will be delayed. Subsequent rules are processed after this action + +Lua equivalent: :func:`DelayResponseAction` + +Parameters: + +- **msec**: Unsigned integer - The amount of milliseconds to delay the response + + +.. _yaml-settings-DnstapLogResponseAction: + +DnstapLogResponseAction +----------------------- + +Send the current response to a remote logger as a dnstap message. ``alter-function`` is a callback, receiving a :class:`DNSResponse` and a :class:`DnstapMessage`, that can be used to modify the message. Subsequent rules are processed after this action + +Lua equivalent: :func:`DnstapLogResponseAction` + +Parameters: + +- **identity**: String - Server identity to store in the dnstap message +- **logger_name**: String - The name of dnstap logger +- **alter_function_name**: String ``("")`` - The name of the Lua function that will alter the message +- **alter_function_code**: String ``("")`` - The code of the Lua function that will alter the message +- **alter_function_file**: String ``("")`` - The path to a file containing the code of the Lua function that will alter the message + + +.. _yaml-settings-DropResponseAction: + +DropResponseAction +------------------ + +Drop the packet + +Lua equivalent: :func:`DropResponseAction` + +.. _yaml-settings-LimitTTLResponseAction: + +LimitTTLResponseAction +---------------------- + +Cap the TTLs of the response to the given boundaries + +Lua equivalent: :func:`LimitTTLResponseAction` + +Parameters: + +- **min**: Unsigned integer - The minimum allowed value +- **max**: Unsigned integer - The maximum allowed value +- **types**: Sequence of Unsigned integer - The record types to cap the TTL for, as integers. Default is empty which means all records will be capped + + +.. _yaml-settings-LogResponseAction: + +LogResponseAction +----------------- + +Log a line for each response, to the specified file if any, to the console (require verbose) if the empty string is given as filename. If an empty string is supplied in the file name, the logging is done to stdout, and only in verbose mode by default. This can be changed by setting ``verbose-only`` to ``false``. The ``append`` parameter specifies whether we open the file for appending or truncate each time (default). The ``buffered`` parameter specifies whether writes to the file are buffered (default) or not. Subsequent rules are processed after this action + +Lua equivalent: :func:`LogResponseAction` + +Parameters: + +- **file_name**: String ``("")`` - File to log to. Set to an empty string to log to the normal stdout log, this only works when ``-v`` is set on the command line +- **append**: Boolean ``(false)`` - Whether to append to an existing file +- **buffered**: Boolean ``(false)`` - Whether to use buffered I/O +- **verbose_only**: Boolean ``(true)`` - Whether to log only in verbose mode when logging to stdout +- **include_timestamp**: Boolean ``(false)`` - Whether to include a timestamp for every entry + + +.. _yaml-settings-LuaResponseAction: + +LuaResponseAction +----------------- + +Invoke a Lua function that accepts a :class:`DNSResponse`. The function should return a :ref:`DNSResponseAction`. If the Lua code fails, ``ServFail`` is returned + +Lua equivalent: :func:`LuaResponseAction` + +Parameters: + +- **function_name**: String ``("")`` - The name of the Lua function +- **function_code**: String ``("")`` - The code of the Lua function +- **function_file**: String ``("")`` - The path to a file containing the code of the Lua function + + +.. _yaml-settings-LuaFFIResponseAction: + +LuaFFIResponseAction +-------------------- + +Invoke a Lua function that accepts a pointer to a ``dnsdist_ffi_dnsquestion_t`` object, whose bindings are defined in ``dnsdist-lua-ffi-interface.h``. The function should return a :ref:`DNSResponseAction`. If the Lua code fails, ``ServFail`` is returned + +Lua equivalent: :func:`LuaFFIResponseAction` + +Parameters: + +- **function_name**: String ``("")`` - The name of the Lua function +- **function_code**: String ``("")`` - The code of the Lua function +- **function_file**: String ``("")`` - The path to a file containing the code of the Lua function + + +.. _yaml-settings-LuaFFIPerThreadResponseAction: + +LuaFFIPerThreadResponseAction +----------------------------- + +Invoke a Lua function that accepts a pointer to a ``dnsdist_ffi_dnsquestion_t`` object, whose bindings are defined in ``dnsdist-lua-ffi-interface.h``. The function should return a :ref:`DNSResponseAction`. If the Lua code fails, ``ServFail`` is returned. The function will be invoked in a per-thread Lua state, without access to the global Lua state. All constants (:ref:`DNSQType`, :ref:`DNSRCode`, ...) are available in that per-thread context, as well as all FFI functions. Objects and their bindings that are not usable in a FFI context (:class:`DNSQuestion`, :class:`DNSDistProtoBufMessage`, :class:`PacketCache`, ...) are not available. + +Lua equivalent: :func:`LuaFFIPerThreadResponseAction` + +Parameters: + +- **code**: String - The code of the Lua function + + +.. _yaml-settings-RemoteLogResponseAction: + +RemoteLogResponseAction +----------------------- + +Send the current response to a remote logger as a Protocol Buffer message. ``alter-function`` is a callback, receiving a :class:`DNSResponse` and a :class:`DNSDistProtoBufMessage`, that can be used to modify the message, for example for anonymization purposes. Subsequent rules are processed after this action + +Lua equivalent: :func:`RemoteLogResponseAction` + +Parameters: + +- **logger_name**: String - The name of the protocol buffer logger +- **alter_function_name**: String ``("")`` - The name of the Lua function +- **alter_function_code**: String ``("")`` - The code of the Lua function +- **alter_function_file**: String ``("")`` - The path to a file containing the code of the Lua function +- **server_id**: String ``("")`` - Set the Server Identity field +- **ip_encrypt_key**: String ``("")`` - A key, that can be generated via the :func:`makeIPCipherKey` function, to encrypt the IP address of the requestor for anonymization purposes. The encryption is done using ipcrypt for IPv4 and a 128-bit AES ECB operation for IPv6 +- **include_cname**: Boolean ``(false)`` - Whether or not to parse and export CNAMEs +- **export_tags**: Sequence of String ``("")`` - The comma-separated list of keys of internal tags to export into the ``tags`` Protocol Buffer field, as ``key:value`` strings. Note that a tag with an empty value will be exported as ````, not ``:``. An empty string means that no internal tag will be exported. The special value ``*`` means that all tags will be exported +- **export_extended_errors_to_meta**: String ``("")`` - Export Extended DNS Errors present in the DNS response, if any, into the ``meta`` Protocol Buffer field using the specified ``key``. The EDE info code will be exported as an integer value, and the EDE extra text, if present, as a string value +- **metas**: Sequence of :ref:`ProtoBufMetaConfiguration ` - A list of ``name``=``key`` pairs, for meta-data to be added to Protocol Buffer message + + +.. _yaml-settings-SetExtendedDNSErrorResponseAction: + +SetExtendedDNSErrorResponseAction +--------------------------------- + +Set an Extended DNS Error status that will be added to the response. Subsequent rules are processed after this action + +Lua equivalent: :func:`SetExtendedDNSErrorResponseAction` + +Parameters: + +- **info_code**: Unsigned integer - The EDNS Extended DNS Error code +- **extra_text**: String ``("")`` - The optional EDNS Extended DNS Error extra text + + +.. _yaml-settings-SetMaxReturnedTTLResponseAction: + +SetMaxReturnedTTLResponseAction +------------------------------- + +Cap the TTLs of the response to the given maximum, but only after inserting the response into the packet cache with the initial TTL values + +Lua equivalent: :func:`SetMaxReturnedTTLResponseAction` + +Parameters: + +- **max**: Unsigned integer - The TTL cap + + +.. _yaml-settings-SetMaxTTLResponseAction: + +SetMaxTTLResponseAction +----------------------- + +Cap the TTLs of the response to the given maximum + +Lua equivalent: :func:`SetMaxTTLResponseAction` + +Parameters: + +- **max**: Unsigned integer - The TTL cap + + +.. _yaml-settings-SetMinTTLResponseAction: + +SetMinTTLResponseAction +----------------------- + +Cap the TTLs of the response to the given minimum + +Lua equivalent: :func:`SetMinTTLResponseAction` + +Parameters: + +- **min**: Unsigned integer - The TTL cap + + +.. _yaml-settings-SetReducedTTLResponseAction: + +SetReducedTTLResponseAction +--------------------------- + +Reduce the TTL of records in a response to a percentage of the original TTL. For example, passing 50 means that the original TTL will be cut in half. Subsequent rules are processed after this action + +Lua equivalent: :func:`SetReducedTTLResponseAction` + +Parameters: + +- **percentage**: Unsigned integer - The percentage to use + + +.. _yaml-settings-SetSkipCacheResponseAction: + +SetSkipCacheResponseAction +-------------------------- + +Don’t store this answer in the cache. Subsequent rules are processed after this action. + +Lua equivalent: :func:`SetSkipCacheResponseAction` + +.. _yaml-settings-SetTagResponseAction: + +SetTagResponseAction +-------------------- + +Associate a tag named ``tag`` with a value of ``value`` to this response. This function will overwrite any existing tag value. Subsequent rules are processed after this action + +Lua equivalent: :func:`SetTagResponseAction` + +Parameters: + +- **tag**: String - The tag name +- **value**: String - The tag value + + +.. _yaml-settings-SNMPTrapResponseAction: + +SNMPTrapResponseAction +---------------------- + +Send an SNMP trap, adding the message string as the query description. Subsequent rules are processed after this action + +Lua equivalent: :func:`SNMPTrapResponseAction` + +Parameters: + +- **reason**: String ``("")`` - The SNMP trap reason + + +.. _yaml-settings-TCResponseAction: + +TCResponseAction +---------------- + +Truncate an existing answer, to force the client to TCP. Only applied to answers that will be sent to the client over TCP. In addition to the TC bit being set, all records are removed from the answer, authority and additional sections + +Lua equivalent: :func:`TCResponseAction` + diff --git a/pdns/dnsdistdist/docs/reference/yaml-selectors.rst b/pdns/dnsdistdist/docs/reference/yaml-selectors.rst new file mode 100644 index 000000000000..4fb2d90b4021 --- /dev/null +++ b/pdns/dnsdistdist/docs/reference/yaml-selectors.rst @@ -0,0 +1,640 @@ +.. THIS IS A GENERATED FILE. DO NOT EDIT. See dnsdist-settings-documentation-generator.py + +.. raw:: latex + + \setcounter{secnumdepth}{-1} + +.. _yaml-settings-Selector: + +YAML selector reference +======================= + +.. _yaml-settings-AllSelector: + +AllSelector +----------- + +Matches all traffic + +Lua equivalent: :func:`AllRule` + +.. _yaml-settings-AndSelector: + +AndSelector +----------- + +Matches traffic if all selectors match + +Lua equivalent: :func:`AndRule` + +Parameters: + +- **selectors**: Sequence of :ref:`Selector ` - List of selectors + + +.. _yaml-settings-ByNameSelector: + +ByNameSelector +-------------- + +References an already declared selector by its name + +Parameters: + +- **selector_name**: String + + +.. _yaml-settings-DNSSECSelector: + +DNSSECSelector +-------------- + +Matches queries with the DO flag set + +Lua equivalent: :func:`DNSSECRule` + +.. _yaml-settings-DSTPortSelector: + +DSTPortSelector +--------------- + +Matches questions received to the destination port + +Lua equivalent: :func:`DSTPortRule` + +Parameters: + +- **port**: Unsigned integer - Match destination port + + +.. _yaml-settings-EDNSOptionSelector: + +EDNSOptionSelector +------------------ + +Matches queries or responses with the specified EDNS option present + +Lua equivalent: :func:`EDNSOptionRule` + +Parameters: + +- **option_code**: Unsigned integer - The option code as an integer + + +.. _yaml-settings-EDNSVersionSelector: + +EDNSVersionSelector +------------------- + +Matches queries or responses with an OPT record whose EDNS version is greater than the specified EDNS version + +Lua equivalent: :func:`EDNSVersionRule` + +Parameters: + +- **version**: Unsigned integer - The EDNS version to match on + + +.. _yaml-settings-ERCodeSelector: + +ERCodeSelector +-------------- + +Matches queries or responses with the specified rcode. The full 16bit RCode will be matched. If no EDNS OPT RR is present, the upper 12 bits are treated as 0 + +Lua equivalent: :func:`ERCodeRule` + +Parameters: + +- **rcode**: Unsigned integer - The full 16bit RCode will be matched. If no EDNS OPT RR is present, the upper 12 bits are treated as 0 + + +.. _yaml-settings-HTTPHeaderSelector: + +HTTPHeaderSelector +------------------ + +Matches DNS over HTTPS queries with a HTTP header name whose content matches the supplied regular expression. It is necessary to set the ``keepIncomingHeaders`` to :func:`addDOHLocal()` to use this rule + +Lua equivalent: :func:`HTTPHeaderRule` + +Parameters: + +- **header**: String - The case-insensitive name of the HTTP header to match on +- **expression**: String - A regular expression to match the content of the specified header + + +.. _yaml-settings-HTTPPathSelector: + +HTTPPathSelector +---------------- + +Matches DNS over HTTPS queries with a specific HTTP path. For example, if the query has been sent to the https://192.0.2.1:443/PowerDNS?dns=... URL, the path would be '/PowerDNS'. Only valid DNS over HTTPS queries are matched. If you want to match all HTTP queries, see :meth:`DOHFrontend:setResponsesMap` instead + +Lua equivalent: :func:`HTTPPathRule` + +Parameters: + +- **path**: String - The exact HTTP path to match on + + +.. _yaml-settings-HTTPPathRegexSelector: + +HTTPPathRegexSelector +--------------------- + +Matches DNS over HTTPS queries with a path matching the supplied regular expression. For example, if the query has been sent to the https://192.0.2.1:443/PowerDNS?dns=... URL, the path would be '/PowerDNS'. +Only valid DNS over HTTPS queries are matched. If you want to match all HTTP queries, see :meth:`DOHFrontend:setResponsesMap` instead + + +Lua equivalent: :func:`HTTPPathRegexRule` + +Parameters: + +- **expression**: String - The regex to match on + + +.. _yaml-settings-KeyValueStoreLookupSelector: + +KeyValueStoreLookupSelector +--------------------------- + +Matches if the key returned by ``lookup_key_name`` exists in the key value store + +Lua equivalent: :func:`KeyValueStoreLookupRule` + +Parameters: + +- **kvs_name**: String - The key value store to query +- **lookup_key_name**: String - The key to use for the lookup + + +.. _yaml-settings-KeyValueStoreRangeLookupSelector: + +KeyValueStoreRangeLookupSelector +-------------------------------- + +Does a range-based lookup into the key value store using the key returned by ``lookup_key_name`` and matches if there is a range covering that key. This assumes that there is a key, in network byte order, for the last element of the range (for example ``2001:0db8:ffff:ffff:ffff:ffff:ffff:ffff`` for ``2001:db8::/32``) which contains the first element of the range (``2001:0db8:0000:0000:0000:0000:0000:0000``) (optionally followed by any data) as value, still in network byte order, and that there is no overlapping ranges in the database. This requires that the underlying store supports ordered keys, which is true for ``LMDB`` but not for ``CDB`` + +Lua equivalent: :func:`KeyValueStoreRangeLookupRule` + +Parameters: + +- **kvs_name**: String - The key value store to query +- **lookup_key_name**: String - The key to use for the lookup + + +.. _yaml-settings-LuaSelector: + +LuaSelector +----------- + +Invoke a Lua function that accepts a :class:`DNSQuestion` object. The function should return true if the query matches, or false otherwise. If the Lua code fails, false is returned + +Lua equivalent: :func:`LuaRule` + +Parameters: + +- **function_name**: String ``("")`` - The name of the Lua function +- **function_code**: String ``("")`` - The code of the Lua function +- **function_file**: String ``("")`` - The path to a file containing the code of the Lua function + + +.. _yaml-settings-LuaFFISelector: + +LuaFFISelector +-------------- + +Invoke a Lua FFI function that accepts a pointer to a ``dnsdist_ffi_dnsquestion_t`` object, whose bindings are defined in ``dnsdist-lua-ffi-interface.h``. The function should return true if the query matches, or false otherwise. If the Lua code fails, false is returned + +Lua equivalent: :func:`LuaFFIRule` + +Parameters: + +- **function_name**: String ``("")`` - The name of the Lua function +- **function_code**: String ``("")`` - The code of the Lua function +- **function_file**: String ``("")`` - The path to a file containing the code of the Lua function + + +.. _yaml-settings-LuaFFIPerThreadSelector: + +LuaFFIPerThreadSelector +----------------------- + +Invoke a Lua FFI function that accepts a pointer to a ``dnsdist_ffi_dnsquestion_t`` object, whose bindings are defined in ``dnsdist-lua-ffi-interface.h``. The function should return true if the query matches, or false otherwise. If the Lua code fails, false is returned. +The function will be invoked in a per-thread Lua state, without access to the global Lua state. All constants (:ref:`DNSQType`, :ref:`DNSRCode`, ...) are available in that per-thread context, as well as all FFI functions. Objects and their bindings that are not usable in a FFI context (:class:`DNSQuestion`, :class:`DNSDistProtoBufMessage`, :class:`PacketCache`, ...) are not available + +Lua equivalent: :func:`LuaFFIPerThreadRule` + +Parameters: + +- **code**: String - The code of the Lua function + + +.. _yaml-settings-MaxQPSSelector: + +MaxQPSSelector +-------------- + +Matches traffic not exceeding this qps limit. If e.g. this is set to 50, starting at the 51st query of the current second traffic stops being matched. This can be used to enforce a global QPS limit + +Lua equivalent: :func:`MaxQPSRule` + +Parameters: + +- **qps**: Unsigned integer - The number of queries per second allowed, above this number the traffic is **not** matched anymore +- **burst**: Unsigned integer ``(0)`` - The number of burstable queries per second allowed. Default is same as qps + + +.. _yaml-settings-MaxQPSIPSelector: + +MaxQPSIPSelector +---------------- + +Matches traffic for a subnet specified by the v4 or v6 mask exceeding ``qps`` queries per second up to ``burst`` allowed. This rule keeps track of QPS by netmask or source IP. This state is cleaned up regularly if ``cleanup_delay`` is greater than zero, removing existing netmasks or IP addresses that have not been seen in the last ``expiration`` seconds. + +Lua equivalent: :func:`MaxQPSIPRule` + +Parameters: + +- **qps**: Unsigned integer - The number of queries per second allowed, above this number traffic is matched +- **ipv4_mask**: Unsigned integer ``(32)`` - The IPv4 netmask to match on. Default is 32 (the whole address) +- **ipv6_mask**: Unsigned integer ``(64)`` - he IPv6 netmask to match on +- **burst**: Unsigned integer ``(0)`` - The number of burstable queries per second allowed. Default is same as qps +- **expiration**: Unsigned integer ``(300)`` - How long to keep netmask or IP addresses after they have last been seen, in seconds +- **cleanup_delay**: Unsigned integer ``(60)`` - The number of seconds between two cleanups +- **scan_fraction**: Unsigned integer ``(10)`` - he maximum fraction of the store to scan for expired entries, for example 5 would scan at most 20% of it +- **shards**: Unsigned integer ``(10)`` - How many shards to use, to decrease lock contention between threads. Default is 10 and is a safe default unless a very high number of threads are used to process incoming queries + + +.. _yaml-settings-NetmaskGroupSelector: + +NetmaskGroupSelector +-------------------- + +Matches traffic from/to the network range specified in either the supplied :class:`NetmaskGroup` object or the list of ``netmasks``. Set the ``source`` parameter to ``false`` to match against destination address instead of source address. This can be used to differentiate between clients + +Lua equivalent: :func:`NetmaskGroupRule` + +Parameters: + +- **netmask_group_name**: String ``("")`` - The name of the netmask group object to use +- **netmasks**: Sequence of String ``("")`` - A list of netmasks to use instead of an existing netmask group object +- **source**: Boolean ``(true)`` - Whether to match source or destination address of the packet. Defaults to true (matches source) +- **quiet**: Boolean ``(false)`` - Do not display the list of matched netmasks in Rules. Default is false. + + +.. _yaml-settings-NotSelector: + +NotSelector +----------- + +Matches the traffic if the selector rule does not match + +Lua equivalent: :func:`NotRule` + +Parameters: + +- **selector**: :ref:`Selector ` - The list of selectors + + +.. _yaml-settings-OpcodeSelector: + +OpcodeSelector +-------------- + +Matches queries with opcode equals to ``code`` + +Lua equivalent: :func:`OpcodeRule` + +Parameters: + +- **code**: Unsigned integer - The opcode to match + + +.. _yaml-settings-OrSelector: + +OrSelector +---------- + +Matches the traffic if one or more of the selectors Rules does match + +Lua equivalent: :func:`OrRule` + +Parameters: + +- **selectors**: Sequence of :ref:`Selector ` - The list of selectors + + +.. _yaml-settings-PayloadSizeSelector: + +PayloadSizeSelector +------------------- + +Matches queries or responses whose DNS payload size fits the given comparison + +Lua equivalent: :func:`PayloadSizeRule` + +Parameters: + +- **comparison**: String - The comparison operator to use. Supported values are: equal, greater, greaterOrEqual, smaller, smallerOrEqual +- **size**: Unsigned integer - The size to compare to + + +.. _yaml-settings-PoolAvailableSelector: + +PoolAvailableSelector +--------------------- + +Check whether a pool has any servers available to handle queries + +Lua equivalent: :func:`PoolAvailableRule` + +Parameters: + +- **pool**: String - The name of the pool + + +.. _yaml-settings-PoolOutstandingSelector: + +PoolOutstandingSelector +----------------------- + +Check whether a pool has total outstanding queries above limit + +Lua equivalent: :func:`PoolOutstandingRule` + +Parameters: + +- **pool**: String - The name of the pool +- **max_outstanding**: Unsigned integer - The maximum number of outstanding queries in that pool + + +.. _yaml-settings-ProbaSelector: + +ProbaSelector +------------- + +Matches queries with a given probability. 1.0 means ``always`` + +Lua equivalent: :func:`ProbaRule` + +Parameters: + +- **probability**: Double - Probability of a match + + +.. _yaml-settings-ProxyProtocolValueSelector: + +ProxyProtocolValueSelector +-------------------------- + +Matches queries that have a proxy protocol TLV value of the specified type. If ``option_value`` is set, the content of the value should also match the content of value + +Lua equivalent: :func:`ProxyProtocolValueRule` + +Parameters: + +- **option_type**: Unsigned integer - The type of the value, ranging from 0 to 255 (both included) +- **option_value**: String ``("")`` - The optional binary-safe value to match + + +.. _yaml-settings-QClassSelector: + +QClassSelector +-------------- + +Matches queries with the specified qclass. The class can be specified as a numerical value or as a string + +Lua equivalent: :func:`QClassRule` + +Parameters: + +- **qclass**: String ``("")`` - The Query Class to match on, as a string +- **numeric_value**: Unsigned integer ``(0)`` - The Query Class to match on, as an integer + + +.. _yaml-settings-QNameSelector: + +QNameSelector +------------- + +Matches queries with the specified qname exactly + +Lua equivalent: :func:`QNameRule` + +Parameters: + +- **qname**: String - Qname to match + + +.. _yaml-settings-QNameLabelsCountSelector: + +QNameLabelsCountSelector +------------------------ + +Matches if the qname has less than ``min_labels_count`` or more than ``max_labels_count`` labels + +Lua equivalent: :func:`QNameLabelsCountRule` + +Parameters: + +- **min_labels_count**: Unsigned integer - Minimum number of labels +- **max_labels_count**: Unsigned integer - Maximum number of labels + + +.. _yaml-settings-QNameSetSelector: + +QNameSetSelector +---------------- + +Matches if the set contains exact qname. To match subdomain names, see :ref:`yaml-settings-QNameSuffixSelector` + +Lua equivalent: :func:`QNameSetRule` + +Parameters: + +- **qnames**: Sequence of String - List of qnames + + +.. _yaml-settings-QNameSuffixSelector: + +QNameSuffixSelector +------------------- + +Matches based on a group of domain suffixes for rapid testing of membership. Pass true to ``quiet`` to prevent listing of all domains matched in the console or the web interface + +Lua equivalent: :func:`QNameSuffixRule` + +Parameters: + +- **suffixes**: Sequence of String - List of suffixes +- **quiet**: Boolean ``(false)`` - Do not display the list of matched domains in Rules + + +.. _yaml-settings-QNameWireLengthSelector: + +QNameWireLengthSelector +----------------------- + +Matches if the qname’s length on the wire is less than ``min`` or more than ``max`` bytes. + +Lua equivalent: :func:`QNameWireLengthRule` + +Parameters: + +- **min**: Unsigned integer - Minimum number of bytes +- **max**: Unsigned integer - Maximum number of bytes + + +.. _yaml-settings-QTypeSelector: + +QTypeSelector +------------- + +Matches queries with the specified qtype, which can be supplied as a String or as a numerical value + +Lua equivalent: :func:`QTypeRule` + +Parameters: + +- **qtype**: String - The qtype, as a string +- **numeric_value**: Unsigned integer ``(0)`` - The qtype, as a numerical value + + +.. _yaml-settings-RCodeSelector: + +RCodeSelector +------------- + +Matches queries or responses with the specified rcode + +Lua equivalent: :func:`RCodeRule` + +Parameters: + +- **rcode**: Unsigned integer - The response code, as a numerical value + + +.. _yaml-settings-RDSelector: + +RDSelector +---------- + +Matches queries with the RD flag set + +Lua equivalent: :func:`RDRule` + +.. _yaml-settings-RE2Selector: + +RE2Selector +----------- + +Matches the query name against the supplied regex using the RE2 engine + +Lua equivalent: :func:`RE2Rule` + +Parameters: + +- **expression**: String - The regular expression to match the QNAME + + +.. _yaml-settings-RecordsCountSelector: + +RecordsCountSelector +-------------------- + +Matches if there is at least ``minimum`` and at most ``maximum`` records in the ``section`` section. ``section`` is specified as an integer with ``0`` being the question section, ``1`` answer, ``2`` authority and ``3`` additional + +Lua equivalent: :func:`RecordsCountRule` + +Parameters: + +- **section**: Unsigned integer - The section to match on +- **minimum**: Unsigned integer - The minimum number of entries +- **maximum**: Unsigned integer - The maximum number of entries + + +.. _yaml-settings-RecordsTypeCountSelector: + +RecordsTypeCountSelector +------------------------ + +Matches if there is at least ``minimum`` and at most ``maximum`` records of type ``record_type`` in the section ``section``. ``section`` is specified as an integer with ``0`` being the question section, ``1`` answer, ``2`` authority and ``3`` additional + +Lua equivalent: :func:`RecordsTypeCountRule` + +Parameters: + +- **section**: Unsigned integer - The section to match on +- **record_type**: Unsigned integer - The record type to match on +- **minimum**: Unsigned integer - The minimum number of entries +- **maximum**: Unsigned integer - The maximum number of entries + + +.. _yaml-settings-RegexSelector: + +RegexSelector +------------- + +Matches the query name against the supplied regular expression + +Lua equivalent: :func:`RegexRule` + +Parameters: + +- **expression**: String - The regular expression to match the QNAME + + +.. _yaml-settings-SNISelector: + +SNISelector +----------- + +Matches against the TLS Server Name Indication value sent by the client, if any. Only makes sense for DoT or DoH, and for that last one matching on the HTTP Host header using :ref:`yaml-settings-HTTPHeaderSelector` might provide more consistent results + +Lua equivalent: :func:`SNIRule` + +Parameters: + +- **server_name**: String - The exact Server Name Indication value + + +.. _yaml-settings-TagSelector: + +TagSelector +----------- + +Matches question or answer with a tag named ``tag`` set. If ``value`` is specified, the existing tag value should match too + +Lua equivalent: :func:`TagRule` + +Parameters: + +- **tag**: String - The name of the tag that has to be set +- **value**: String ``("")`` - If set, the value the tag has to be set to + + +.. _yaml-settings-TCPSelector: + +TCPSelector +----------- + +Matches question received over TCP if ``tcp`` is true, over UDP otherwise + +Lua equivalent: :func:`TCPRule` + +Parameters: + +- **tcp**: Boolean - Match TCP traffic if true, UDP traffic if false + + +.. _yaml-settings-TrailingDataSelector: + +TrailingDataSelector +-------------------- + +Matches if the query has trailing data + +Lua equivalent: :func:`TrailingDataRule` + diff --git a/pdns/dnsdistdist/docs/reference/yaml-settings.rst b/pdns/dnsdistdist/docs/reference/yaml-settings.rst new file mode 100644 index 000000000000..2a889a5ded37 --- /dev/null +++ b/pdns/dnsdistdist/docs/reference/yaml-settings.rst @@ -0,0 +1,1007 @@ +.. THIS IS A GENERATED FILE. DO NOT EDIT. See dnsdist-settings-documentation-generator.py + +.. raw:: latex + + \setcounter{secnumdepth}{-1} + +YAML configuration reference +============================ + +Since 2.0.0, :program:`dnsdist` supports the YAML configuration format in addition to the existing Lua one. + +If the configuration file passed to :program:`dnsdist` via the ``-C`` command-line switch ends in ``.yml``, it is assumed to be in the new YAML format, and an attempt to load a Lua configuration file with the same name but the ``.lua`` will be done before loading the YAML configuration. If the names ends in ``.lua``, there will also be an attempt to find a file with the same name but ending in ``.yml``. Otherwise the existing Lua configuration format is assumed. + +A YAML configuration file contains several sections, that are described below. + +.. code-block:: yaml + +.. _yaml-settings-GlobalConfiguration: + +GlobalConfiguration +------------------- + +- **acl**: Sequence of String ``(127.0.0.0/8, 10.0.0.0/8, 100.64.0.0/10, 169.254.0.0/16, 192.168.0.0/16, 172.16.0.0/12, ::1/128, fc00::/7, fe80::/10)`` - CIDR netmasks of the clients allowed to send DNS queries +- **backends**: Sequence of :ref:`BackendConfiguration ` - List of backends +- **binds**: Sequence of :ref:`BindConfiguration ` - List of endpoints to accept queries on +- **cache_hit_response_rules**: Sequence of :ref:`ResponseRuleConfiguration ` - List of rules executed on a cache hit +- **cache_inserted_response_rules**: Sequence of :ref:`ResponseRuleConfiguration ` - List of rules executed after inserting a new response into the cache +- **cache_miss_rules**: Sequence of :ref:`QueryRuleConfiguration ` - List of rules executed after a cache miss +- **cache_settings**: :ref:`CacheSettingsConfiguration ` - Caching-related settings +- **console**: :ref:`ConsoleConfiguration ` - Console-related settings +- **dynamic_rules**: Sequence of :ref:`DynamicRulesConfiguration ` - List of dynamic rules +- **dynamic_rules_settings**: :ref:`DynamicRulesSettingsConfiguration ` - Dynamic rules-related settings +- **ebpf**: :ref:`EbpfConfiguration ` - EBPF settings +- **edns_client_subnet**: :ref:`EdnsClientSubnetConfiguration ` - EDNS Client Subnet-related settings +- **general**: :ref:`GeneralConfiguration ` - General settings +- **key_value_stores**: :ref:`KeyValueStoresConfiguration ` - Key-Value stores +- **load_balancing_policies**: :ref:`LoadBalancingPoliciesConfiguration ` - Load-balancing policies +- **logging**: :ref:`LoggingConfiguration ` - Logging settings +- **metrics**: :ref:`MetricsConfiguration ` - Metrics-related settings +- **packet_caches**: Sequence of :ref:`PacketCacheConfiguration ` - Packet-cache definitions +- **pools**: Sequence of :ref:`PoolConfiguration ` - Pools of backends +- **proxy_protocol**: :ref:`ProxyProtocolConfiguration ` - Proxy-protocol-related settings +- **query_count**: :ref:`QueryCountConfiguration ` - Queries counting-related settings +- **query_rules**: Sequence of :ref:`QueryRuleConfiguration ` - List of rules executed when a query is received +- **remote_logging**: :ref:`RemoteLoggingConfiguration ` - Remote logging-related settings +- **response_rules**: Sequence of :ref:`ResponseRuleConfiguration ` - List of rules executed when a response is received +- **ring_buffers**: :ref:`RingBuffersConfiguration ` - In-memory ring buffer settings +- **security_polling**: :ref:`SecurityPollingConfiguration ` - Automatic checking of outdated version +- **selectors**: Sequence of :ref:`Selector ` - List of selectors that can be reused in rules +- **self_answered_response_rules**: Sequence of :ref:`ResponseRuleConfiguration ` - List of rules executed when a response is generated by DNSdist itself +- **snmp**: :ref:`SnmpConfiguration ` - SNMP-related settings +- **tuning**: :ref:`TuningConfiguration ` - Performance-related settings +- **webserver**: :ref:`WebserverConfiguration ` - Internal web server configuration +- **xfr_response_rules**: Sequence of :ref:`ResponseRuleConfiguration ` - List of rules executed when a XFR response is received +- **xsk**: Sequence of :ref:`XskConfiguration ` - List of AF_XDP / XSK objects + + + +.. _yaml-settings-BackendConfiguration: + +BackendConfiguration +-------------------- + +Generic settings for backends + +- **address**: String - ``ip``:``port`` of the backend server (if unset, port defaults to 53 for Do53 backends, 853 for DoT and DoQ, and 443 for DoH and DoH3 ones +- **id**: String ``("")`` - Use a pre-defined UUID instead of a random one +- **name**: String ``("")`` - The name associated to this backend, for display purpose +- **protocol**: String - The DNS protocol to use to contact this backend. Supported values are: Do53, DoT, DoH +- **tls**: :ref:`OutgoingTlsConfiguration ` - TLS-related settings for DoT and DoH backends +- **doh**: :ref:`OutgoingDohConfiguration ` - DoH-related settings for DoH backends +- **use_client_subnet**: Boolean ``(false)`` - Whether to add (or override, see :ref:`yaml-settings-EdnsClientSubnetConfiguration`) an EDNS Client Subnet to the DNS payload before forwarding it to the backend. Please see :doc:`../advanced/passing-source-address` for more information +- **use_proxy_protocol**: Boolean ``(false)`` - Add a proxy protocol header to the query, passing along the client's IP address and port along with the original destination address and port +- **queries_per_second**: Unsigned integer ``(0)`` - Limit the number of queries per second to ``number``, when using the ``firstAvailable`` policy +- **order**: Unsigned integer ``(1)`` - The order of this server, used by the `leastOutstanding` and `firstAvailable` policies +- **weight**: Unsigned integer ``(1)`` - The weight of this server, used by the `wrandom`, `whashed` and `chashed` policies, default: 1. Supported values are a minimum of 1, and a maximum of 2147483647 +- **pools**: Sequence of String ``("")`` - List of pools to place this backend into. By default a server is placed in the default ("") pool +- **tcp**: :ref:`OutgoingTcpConfiguration ` - TCP-related settings for a backend +- **ip_bind_addr_no_port**: Boolean ``(true)`` - Whether to enable ``IP_BIND_ADDRESS_NO_PORT`` if available +- **health_checks**: :ref:`HealthCheckConfiguration ` - Health-check settings +- **source**: String ``("")`` - The source address or interface to use for queries to this backend, by default this is left to the kernel's address selection. + The following formats are supported: + + - address, e.g. ``""192.0.2.2""`` + - interface name, e.g. ``""eth0""`` + - address@interface, e.g. ``""192.0.2.2@eth0""`` + +- **sockets**: Unsigned integer ``(1)`` - Number of UDP sockets (and thus source ports) used toward the backend server, defaults to a single one. Note that for backends which are multithreaded, this setting will have an effect on the number of cores that will be used to process traffic from dnsdist. For example you may want to set ``sockets`` to a number somewhat greater than the number of worker threads configured in the backend, particularly if the Linux kernel is being used to distribute traffic to multiple threads listening on the same socket (via ``reuseport``). See also ``randomize_outgoing_sockets_to_backend`` in :ref:`yaml-settings-UdpTuningConfiguration` +- **disable_zero_scope**: Boolean ``(false)`` - Disable the EDNS Client Subnet :doc:`../advanced/zero-scope` feature, which does a cache lookup for an answer valid for all subnets (ECS scope of 0) before adding ECS information to the query and doing the regular lookup. Default is false. This requires the ``parse_ecs`` option of the corresponding cache to be set to true +- **reconnect_on_up**: Boolean ``(false)`` - Close and reopen the sockets when a server transits from Down to Up. This helps when an interface is missing when dnsdist is started +- **max_in_flight**: Unsigned integer ``(1)`` - Maximum number of in-flight queries. The default is 0, which disables out-of-order processing. It should only be enabled if the backend does support out-of-order processing. Out-of-order processing needs to be enabled on the frontend as well +- **tcp_only**: Boolean ``(false)`` - Always forward queries to that backend over TCP, never over UDP. Always enabled for TLS backends +- **auto_upgrade**: :ref:`OutgoingAutoUpgradeConfiguration ` - Auto-upgrade related settings +- **max_concurrent_tcp_connections**: Unsigned integer ``(0)`` - Maximum number of TCP connections to that backend. When that limit is reached, queries routed to that backend that cannot be forwarded over an existing connection will be dropped. Default is 0 which means no limit +- **proxy_protocol_advertise_tls**: Boolean ``(false)`` - Whether to set the SSL Proxy Protocol TLV in the proxy protocol payload sent to the backend if the query was received over an encrypted channel (DNSCrypt, DoQ, DoH or DoT). Requires ``use_proxy_protocol`` +- **mac_address**: String ``("")`` - When the ``xsk`` option is set, this parameter can be used to specify the destination MAC address to use to reach the backend. If this options is not specified, dnsdist will try to get it from the IP of the backend by looking into the system's MAC address table, but it will fail if the corresponding MAC address is not present +- **cpus**: String ``("")`` - Set the CPU affinity for this thread, asking the scheduler to run it on a single CPU id, or a set of CPU ids. This parameter is only available if the OS provides the ``pthread_setaffinity_np()`` function +- **xsk**: String ``("")`` - The name of an XSK sockets map to attach to this frontend, if any + + +.. _yaml-settings-BindConfiguration: + +BindConfiguration +----------------- + +General settings for frontends + +- **listen_address**: String - Address and port to listen to +- **reuseport**: Boolean ``(false)`` - Set the ``SO_REUSEPORT`` socket option, allowing several sockets to be listening on this address and port +- **protocol**: String ``(Do53)`` - The DNS protocol for this frontend. Supported values are: Do53, DoT, DoH, DoQ, DoH3, DNSCrypt +- **threads**: Unsigned integer ``(1)`` - Number of listening threads to create for this frontend +- **interface**: String ``("")`` - Set the network interface to use +- **cpus**: String ``("")`` - Set the CPU affinity for this listener thread, asking the scheduler to run it on a single CPU id, or a set of CPU ids. This parameter is only available if the OS provides the ``pthread_setaffinity_np()`` function +- **enable_proxy_protocol**: Boolean ``(false)`` - Whether to expect a proxy protocol v2 header in front of incoming queries coming from an address allowed by the ACL in :ref:`yaml-settings-ProxyProtocolConfiguration`. Default is ``true``, meaning that queries are expected to have a proxy protocol payload if they come from an address present in the proxy protocol ACL +- **tcp**: :ref:`IncomingTcpConfiguration ` - TCP-specific settings +- **tls**: :ref:`IncomingTlsConfiguration ` - TLS-specific settings +- **doh**: :ref:`IncomingDohConfiguration ` - DNS over HTTPS-specific settings +- **doq**: :ref:`IncomingDoqConfiguration ` - DNS over QUIC-specific settings +- **quic**: :ref:`IncomingQuicConfiguration ` - QUIC-specific settings +- **dnscrypt**: :ref:`IncomingDnscryptConfiguration ` - DNSCrypt-specific settings +- **additional_addresses**: Sequence of String ``("")`` - List of additional addresses (with port) to listen on. Using this option instead of creating a new frontend for each address avoids the creation of new thread and Frontend objects, reducing the memory usage. The drawback is that there will be a single set of metrics for all addresses +- **xsk**: String ``("")`` - The name of an XSK sockets map to attach to this frontend, if any + + +.. _yaml-settings-CacheSettingsConfiguration: + +CacheSettingsConfiguration +-------------------------- + +- **stale_entries_ttl**: Unsigned integer ``(0)`` +- **cleaning_delay**: Unsigned integer ``(60)`` +- **cleaning_percentage**: Unsigned integer ``(100)`` + + +.. _yaml-settings-CarbonConfiguration: + +CarbonConfiguration +------------------- + +Carbon endpoint to send metrics to + +- **address**: String - Indicates the IP address where the statistics should be sent +- **name**: String ``("")`` - An optional string specifying the hostname that should be used. If left empty, the system hostname is used +- **interval**: Unsigned integer ``(30)`` - An optional unsigned integer indicating the interval in seconds between exports +- **namespace**: String ``("")`` - An optional string specifying the namespace name that should be used +- **instance**: String ``("")`` - An optional string specifying the instance name that should be used + + +.. _yaml-settings-CdbKvStoreConfiguration: + +CdbKvStoreConfiguration +----------------------- + +CDB-based key-value store + +- **name**: String - The name of this object +- **file_name**: String - The path to an existing CDB database +- **refresh_delay**: Unsigned integer - The delay in seconds between two checks of the database modification time. 0 means disabled + + +.. _yaml-settings-ConsoleConfiguration: + +ConsoleConfiguration +-------------------- + +Console-related settings + +- **listen_address**: String ``("")`` - IP address and port to listen on for console connections +- **key**: String ``("")`` - The shared secret used to secure connections between the console client and the server, generated via ``makeKey()`` +- **acl**: Sequence of String ``(127.0.0.1, ::1)`` - List of network masks or IP addresses that are allowed to open a connection to the console server +- **maximum_output_size**: Unsigned integer ``(10000000)`` - Set the maximum size, in bytes, of a single console message +- **log_connections**: Boolean ``(true)`` - Whether to log the opening and closing of console connections +- **max_concurrent_connections**: Unsigned integer ``(0)`` - Set the maximum number of concurrent console connection + + +.. _yaml-settings-CustomLoadBalancingPolicyConfiguration: + +CustomLoadBalancingPolicyConfiguration +-------------------------------------- + +Settings for a custom load-balancing policy + +- **name**: String - The name of this load-balancing policy +- **function_name**: String ``("")`` - The name of a Lua function implementing the custom load-balancing policy. If ``ffi`` is false, this function takes a table of :class:`Server` objects and a :class:`DNSQuestion` representing the current query, and must return the index of the selected server in the supplied table. If ``ffi`` is true, this function takes a ``const dnsdist_ffi_servers_list_t*`` and a ``dnsdist_ffi_dnsquestion_t*`` +- **function_code**: String ``("")`` - Same than ``function_name`` but contain actual Lua code returning a function instead of a name +- **function_file**: String ``("")`` - Same than ``function_name`` but contain the path to a file containing actual Lua code returning a function instead of a name +- **ffi**: Boolean ``(false)`` - Whether the function uses the faster but more complicated Lua FFI API +- **per_thread**: Boolean ``(false)`` - If set, the resulting policy will be executed in a lock-free per-thread context, instead of running in the global Lua context. Note that ``function_name`` cannot be used, since this needs the Lua code to create the function in a new Lua context instead of just a function + + +.. _yaml-settings-DnstapLoggerConfiguration: + +DnstapLoggerConfiguration +------------------------- + +Endpoint to send queries and/or responses data to, using the dnstap format + +- **name**: String - Name of this endpoint +- **transport**: String - The dnstap transport to use. Supported values are: unix, tcp +- **address**: String - The address of the endpoint. If the transport is set to 'unix', the address should be local ``AF_UNIX`` socket path. Note that most platforms have a rather short limit on the length. Otherwise the address should be an IP:port +- **buffer_hint**: Unsigned integer ``(0)`` - The threshold number of bytes to accumulate in the output buffer before forcing a buffer flush. According to the libfstrm library, the minimum is 1024, the maximum is 65536, and the default is 8192 +- **flush_timeout**: Unsigned integer ``(0)`` - The number of seconds to allow unflushed data to remain in the output buffer. According to the libfstrm library, the minimum is 1 second, the maximum is 600 seconds (10 minutes), and the default is 1 second +- **input_queue_size**: Unsigned integer ``(0)`` - The number of queue entries to allocate for each input queue. This value must be a power of 2. According to the fstrm library, the minimum is 2, the maximum is 16384, and the default is 512 +- **output_queue_size**: Unsigned integer ``(0)`` - The number of queue entries to allocate for each output queue. According to the libfstrm library, the minimum is 2, the maximum is system-dependent and based on ``IOV_MAX``, and the default is 64 +- **queue_notify_threshold**: Unsigned integer ``(0)`` - The number of outstanding queue entries to allow on an input queue before waking the I/O thread. According to the libfstrm library, the minimum is 1 and the default is 32 +- **reopen_interval**: Unsigned integer ``(0)`` - The number of queue entries to allocate for each output queue. According to the libfstrm library, the minimum is 2, the maximum is system-dependent and based on IOV_MAX, and the default is 64 + + +.. _yaml-settings-DohTuningConfiguration: + +DohTuningConfiguration +---------------------- + +- **outgoing_worker_threads**: Unsigned integer ``(10)`` +- **outgoing_max_idle_time**: Unsigned integer ``(300)`` +- **outgoing_cleanup_interval**: Unsigned integer ``(60)`` +- **outgoing_max_idle_connection_per_backend**: Unsigned integer ``(10)`` + + +.. _yaml-settings-DynamicRuleConfiguration: + +DynamicRuleConfiguration +------------------------ + +Dynamic rule settings + +- **type**: String - The type of this rule. Supported values are: query-rate, rcode-rate, rcode-ratio, qtype-rate, cache-miss-ratio, response-byte-rate +- **seconds**: Unsigned integer - Number of seconds the rule has been exceeded +- **action_duration**: Unsigned integer - How long the action is going to be enforced +- **comment**: String - Comment describing why the action why taken +- **rate**: Unsigned integer ``(0)`` - For ``query-rate``, ``rcode-rate``, ``qtype-rate`` and ``response-byte-rate``, the rate that should be exceeded +- **ratio**: Double ``(0.0)`` - For ``rcode-ratio``, ``qtype-ratio`` and ``cache-miss-ratio``, the ratio that should be exceeded +- **action**: String ``(drop)`` - The action that will be taken once the rate or ratio is exceeded. Supported values are: Drop, NoNop, NoRecurse, NXDomain, SetTag, Truncate, Refused +- **warning_rate**: Unsigned integer ``(0)`` - For ``query-rate``, ``rcode-rate``, ``qtype-rate`` and ``response-byte-rate``, the rate that should be exceeded for a warning to be logged, but no action enforced +- **warning_ratio**: Double ``(0.0)`` - For ``rcode-ratio`` and ``cache-miss-ratio``, the ratio that should be exceeded for a warning to be logged, but no action enforced +- **tag_name**: String ``("")`` +- **tag_value**: String ``(0)`` - If ``action`` is set to ``SetTag``, the value that will be set +- **visitor_function_name**: String ``("")`` - For ``suffix-match`` and ``suffix-match-ffi``, the name of the Lua visitor function to call for each label of every domain seen in recent queries and responses +- **visitor_function_code**: String ``("")`` - For ``suffix-match`` and ``suffix-match-ffi``, the code of Lua visitor function for each label of every domain seen in recent queries and responses +- **visitor_function_file**: String ``("")`` - For ``suffix-match`` and ``suffix-match-ffi``, a path to a file containing the code of Lua visitor function for each label of every domain seen in recent queries and responses +- **rcode**: String ``("")`` - For ``rcode-rate`` and ``rcode-ratio``, the response code to match +- **qtype**: String ``("")`` - For ``qtype-rate``, the query type to match +- **minimum_number_of_responses**: Unsigned integer ``(0)`` - For ``cache-miss-ratio`` and ``rcode-ratio``, the minimum number of responses to have received for this rule to apply +- **minimum_global_cache_hit_ratio**: Double ``(0.0)`` - The minimum global cache-hit ratio (over all pools, so ``cache-hits`` / (``cache-hits`` + ``cache-misses``)) for a ``cache-miss-ratio`` rule to be applied + + +.. _yaml-settings-DynamicRulesConfiguration: + +DynamicRulesConfiguration +------------------------- + +Group of dynamic rules + +- **name**: String - The name of this group of dynamic rules +- **mask_ipv4**: Unsigned integer ``(32)`` - Number of bits to keep for IPv4 addresses +- **mask_ipv6**: Unsigned integer ``(64)`` - Number of bits to keep for IPv6 addresses. In some scenarios it might make sense to block a whole /64 IPv6 range instead of a single address, for example +- **mask_port**: Unsigned integer ``(0)`` - Number of bits of port to consider over IPv4, for CGNAT deployments. Default is 0 meaning that the port is not taken into account. For example passing ``2`` here, which only makes sense if the IPv4 parameter is set to ``32``, will split a given IPv4 address into four port ranges: ``0-16383``, ``16384-32767``, ``32768-49151`` and ``49152-65535`` +- **exclude_ranges**: Sequence of String ``("")`` - Exclude this list of ranges, meaning that no dynamic block will ever be inserted for clients in that range. Default to empty, meaning rules are applied to all ranges. When used in combination with ``include_ranges`` the more specific entry wins +- **include_ranges**: Sequence of String ``("")`` - Include this list of ranges, meaning that dynamic rules will be inserted for clients in that range. When used in combination with ``exclude_ranges`` the more specific entry wins +- **exclude_domains**: Sequence of String ``("")`` - Exclude this list of domains, meaning that no dynamic rules will ever be inserted for this domain via ``suffix-match`` or ``suffix-match-ffi`` rules. Default to empty, meaning rules are applied to all domains +- **rules**: Sequence of :ref:`DynamicRuleConfiguration ` - List of dynamic rules in this group + + +.. _yaml-settings-DynamicRulesSettingsConfiguration: + +DynamicRulesSettingsConfiguration +--------------------------------- + +Dynamic rules-related settings + +- **purge_interval**: Unsigned integer ``(60)`` - Set at which interval, in seconds, the expired dynamic blocks entries will be effectively removed from the tree. Entries are not applied anymore as soon as they expire, but they remain in the tree for a while for performance reasons. Removing them makes the addition of new entries faster and frees up the memory they use. Setting this value to 0 disables the purging mechanism, so entries will remain in the tree +- **default_action**: String ``(Drop)`` - Set which action is performed when a query is blocked. Supported values are: Drop, NoOp, NoRecurse, NXDomain, Refused, Truncate + + +.. _yaml-settings-EbpfConfiguration: + +EbpfConfiguration +----------------- + +``eBPF`` and ``XDP`` related settings + +- **ipv4**: :ref:`EbpfMapConfiguration ` - IPv4 map +- **ipv6**: :ref:`EbpfMapConfiguration ` - IPv6 map +- **cidr_ipv4**: :ref:`EbpfMapConfiguration ` - IPv4 subnets map +- **cidr_ipv6**: :ref:`EbpfMapConfiguration ` - IPv6 subnets map +- **qnames**: :ref:`EbpfMapConfiguration ` - DNS names map +- **external**: Boolean ``(false)`` - If set to true, :program:`dnsdist` does not load the internal ``eBPF`` program. This is useful for ``AF_XDP`` and ``XDP`` maps + + +.. _yaml-settings-EbpfMapConfiguration: + +EbpfMapConfiguration +-------------------- + +An ``eBPF`` map that is used to share data with kernel-land ``AF_XDP``/``XSK``, ``socket filter`` or ``XDP`` programs. Maps can be pinned to a filesystem path, which makes their content persistent across restarts and allows external programs to read their content and to add new entries. :program:`dnsdist` will try to load maps that are pinned to a filesystem path on startups, inheriting any existing entries, and fall back to creating them if they do not exist yet. Note that the user :program`dnsdist` is running under must have the right privileges to read and write to the given file, and to go through all the directories in the path leading to that file. The pinned path must be on a filesystem of type ``BPF``, usually below ``/sys/fs/bpf/`` + +- **max_entries**: Unsigned integer ``(0)`` - Maximum number of entries in this map. 0 means no entry at all +- **pinned_path**: String ``("")`` - The filesystem path this map should be pinned to + + +.. _yaml-settings-EdnsClientSubnetConfiguration: + +EdnsClientSubnetConfiguration +----------------------------- + +EDNS Client Subnet-related settings + +- **override_existing**: Boolean ``(false)`` - When ``useClientSubnet`` in :func:`newServer()` or ``use_client_subnet`` in :ref:`yaml-settings-BackendConfiguration` are set, and :program:`dnsdist` adds an EDNS Client Subnet Client option to the query, override an existing option already present in the query, if any. Please see Passing the source address to the backend for more information. Note that it’s not recommended to enable this option in front of an authoritative server responding with EDNS Client Subnet information as mismatching data (ECS scopes) can confuse clients and lead to SERVFAIL responses on downstream nameservers +- **source_prefix_v4**: Unsigned integer ``(32)`` - When ``useClientSubnet`` in :func:`newServer()` or ``use_client_subnet`` in :ref:`yaml-settings-BackendConfiguration` are set, and :program:`dnsdist` adds an EDNS Client Subnet Client option to the query, truncate the requestor's IPv4 address to this number of bits +- **source_prefix_v6**: Unsigned integer ``(56)`` - When ``useClientSubnet`` in :func:`newServer()` or ``use_client_subnet`` in :ref:`yaml-settings-BackendConfiguration` are set, and :program:`dnsdist` adds an EDNS Client Subnet Client option to the query, truncate the requestor's IPv6 address to this number of bits + + +.. _yaml-settings-GeneralConfiguration: + +GeneralConfiguration +-------------------- + +General settings + +- **edns_udp_payload_size_self_generated_answers**: Unsigned integer ``(1232)`` - Set the UDP payload size advertised via EDNS on self-generated responses. In accordance with :rfc:`RFC 6891 <6891#section-6.2.5>`, values lower than 512 will be treated as equal to 512 +- **add_edns_to_self_generated_answers**: Boolean ``(true)`` - Whether to add EDNS to self-generated responses, provided that the initial query had EDNS +- **truncate_tc_answers**: Boolean ``(false)`` - Remove any left-over records in responses with the TC bit set, in accordance with :rfc:`RFC 6891 <6891#section-7>` +- **fixup_case**: Boolean ``(false)`` - If set, ensure that the case of the DNS qname in the response matches the one from the query +- **allow_empty_responses**: Boolean ``(false)`` - Set to true (defaults to false) to allow empty responses (qdcount=0) with a NoError or NXDomain rcode (default) from backends. dnsdist drops these responses by default because it can't match them against the initial query since they don't contain the qname, qtype and qclass, and therefore the risk of collision is much higher than with regular responses +- **drop_empty_queries**: Boolean ``(false)`` - Set to true (defaults to false) to drop empty queries (qdcount=0) right away, instead of answering with a NotImp rcode. dnsdist used to drop these queries by default because most rules and existing Lua code expects a query to have a qname, qtype and qclass. However :rfc:`7873` uses these queries to request a server cookie, and :rfc:`8906` as a conformance test, so answering these queries with NotImp is much better than not answering at all +- **capabilities_to_retain**: Sequence of String ``("")`` - Accept a Linux capability as a string, or a list of these, to retain after startup so that privileged operations can still be performed at runtime. + Keeping ``CAP_SYS_ADMIN`` on kernel 5.8+ for example allows loading eBPF programs and altering eBPF maps at runtime even if the ``kernel.unprivileged_bpf_disabled`` sysctl is set. + Note that this does not grant the capabilities to the process, doing so might be done by running it as root which we don't advise, or by adding capabilities via the systemd unit file, for example. + Please also be aware that switching to a different user via ``--uid`` will still drop all capabilities." + + + +.. _yaml-settings-HealthCheckConfiguration: + +HealthCheckConfiguration +------------------------ + +Health-checks related settings for backends + +- **mode**: String ``(auto)`` - The health-check mode to use: 'auto' which sends health-check queries every ``check_interval`` seconds, 'up' which considers that the backend is always available, 'down' that it is always not available, and 'lazy' which only sends health-check queries after a configurable amount of regular queries have failed (see :ref:`yaml-settings-LazyHealthCheckConfiguration` for more information). Default is 'auto'. See :ref:`Healthcheck` for a more detailed explanation. Supported values are: auto, down, lazy, up +- **qname**: String ``("")`` - The DNS name to use as QNAME in health-check queries +- **qclass**: String ``(IN)`` - The DNS class to use in health-check queries +- **qtype**: String ``(A)`` - The DNS type to use in health-check queries +- **function**: String ``("")`` - The name of an optional Lua function to call to dynamically set the QNAME, QTYPE and QCLASS to use in the health-check query (see :ref:`Healthcheck`) +- **lua**: String ``("")`` - The code of an optional Lua function to call to dynamically set the QNAME, QTYPE and QCLASS to use in the health-check query (see :ref:`Healthcheck`) +- **lua_file**: String ``("")`` - A path to a file containing the code of an optional Lua function to call to dynamically set the QNAME, QTYPE and QCLASS to use in the health-check query (see :ref:`Healthcheck`) +- **timeout**: Unsigned integer ``(1000)`` - The timeout (in milliseconds) of a health-check query, default: 1000 (1s) +- **set_cd**: Boolean ``(false)`` - Set the CD (Checking Disabled) flag in the health-check query +- **max_failures**: Unsigned integer ``(1)`` - Allow this many check failures before declaring the backend down +- **rise**: Unsigned integer ``(1)`` - Require ``number`` consecutive successful checks before declaring the backend up +- **interval**: Unsigned integer ``(1)`` - The time in seconds between health checks +- **must_resolve**: Boolean ``(false)`` - Set to true when the health check MUST return a RCODE different from NXDomain, ServFail and Refused. Default is false, meaning that every RCODE except ServFail is considered valid +- **use_tcp**: Boolean ``(false)`` - Whether to do healthcheck queries over TCP, instead of UDP. Always enabled for TCP-only, DNS over TLS and DNS over HTTPS backends +- **lazy**: :ref:`LazyHealthCheckConfiguration ` - Settings for lazy health-checks + + +.. _yaml-settings-HttpCustomResponseHeaderConfiguration: + +HttpCustomResponseHeaderConfiguration +------------------------------------- + +List of custom HTTP headers + +- **key**: String - The key, or name, part of the header +- **value**: String - The value part of the header + + +.. _yaml-settings-HttpResponsesMapConfiguration: + +HttpResponsesMapConfiguration +----------------------------- + +An entry of an HTTP response map. Every query that matches the regular expression supplied in ``expression`` will be immediately answered with a HTTP response. +The status of the HTTP response will be the one supplied by ``status``, and the content set to the one supplied by ``content``, except if the status is a redirection (3xx) in which case the content is expected to be the URL to redirect to. + + +- **expression**: String - A regular expression to match the path against +- **status**: Unsigned integer - The HTTP code to answer with +- **content**: String - The content of the HTTP response, or a URL if the status is a redirection (3xx) +- **headers**: Sequence of :ref:`HttpCustomResponseHeaderConfiguration ` - The custom headers to set for the HTTP response, if any. The default is to use the value of the ``custom_response_headers`` parameter of the frontend + + +.. _yaml-settings-IncomingDnscryptCertificateKeyPairConfiguration: + +IncomingDnscryptCertificateKeyPairConfiguration +----------------------------------------------- + +Certificate and associated key for DNSCrypt frontends + +- **certificate**: String - The path to a DNSCrypt certificate file +- **key**: String - The path to the private key file corresponding to the certificate, or a list of paths to such files, whose order should match the certFile(s) ones + + +.. _yaml-settings-IncomingDnscryptConfiguration: + +IncomingDnscryptConfiguration +----------------------------- + +Settings for DNSCrypt frontends + +- **provider_name**: String ``("")`` - The DNSCrypt provider name for this frontend +- **certificates**: Sequence of :ref:`IncomingDnscryptCertificateKeyPairConfiguration ` - List of certificates and associated keys + + +.. _yaml-settings-IncomingDohConfiguration: + +IncomingDohConfiguration +------------------------ + +The DNS over HTTP(s) parameters of a frontend + +- **provider**: String ``(nghttp2)``. Supported values are: nghttp2, h2o +- **paths**: Sequence of String ``(/dns-query)`` - The path part of a URL, or a list of paths, to accept queries on. Any query with a path matching exactly one of these will be treated as a DoH query (sub-paths can be accepted by setting the ``exact_path_matching`` setting to false) +- **idle_timeout**: Unsigned integer ``(30)`` - Set the idle timeout, in seconds +- **server_tokens**: String ``("")`` - The content of the Server: HTTP header returned by dnsdist. The default is ``h2o/dnsdist`` when ``h2o`` is used, ``nghttp2-/dnsdist`` when ``nghttp2`` is +- **send_cache_control_headers**: Boolean ``(true)`` - Whether to parse the response to find the lowest TTL and set a HTTP Cache-Control header accordingly +- **keep_incoming_headers**: Boolean ``(false)`` - Whether to retain the incoming headers in memory, to be able to use :func:`HTTPHeaderRule` or :meth:`DNSQuestion.getHTTPHeaders` +- **trust_forwarded_for_header**: Boolean ``(false)`` - Whether to parse any existing X-Forwarded-For header in the HTTP query and use the right-most value as the client source address and port, for ACL checks, rules, logging and so on +- **early_acl_drop**: Boolean ``(true)`` - Whether to apply the ACL right when the connection is established, immediately dropping queries that are not allowed by the ACL (true), or later when a query is received, sending a HTTP 403 response when it is not allowed +- **exact_path_matching**: Boolean ``(true)`` - Whether to do exact path matching of the query path against the paths configured in ``paths`` (true) or to accepts sub-paths (false) +- **internal_pipe_buffer_size**: Unsigned integer ``(1048576)`` - Set the size in bytes of the internal buffer of the pipes used internally to pass queries and responses between threads. Requires support for ``F_SETPIPE_SZ`` which is present in Linux since 2.6.35. The actual size might be rounded up to a multiple of a page size. 0 means that the OS default size is used. +- **custom_response_headers**: Sequence of :ref:`HttpCustomResponseHeaderConfiguration ` - Set custom HTTP header(s) returned by dnsdist +- **responses_map**: Sequence of :ref:`HttpResponsesMapConfiguration ` - Set a list of HTTP response rules allowing to intercept HTTP queries very early, before the DNS payload has been processed, and send custom responses including error pages, redirects and static content + + +.. _yaml-settings-IncomingDoqConfiguration: + +IncomingDoqConfiguration +------------------------ + +Settings for DNS over QUIC frontends + +- **max_concurrent_queries_per_connection**: Unsigned integer ``(65535)`` - Maximum number of in-flight queries on a single connection + + +.. _yaml-settings-IncomingQuicConfiguration: + +IncomingQuicConfiguration +------------------------- + +QUIC settings for DNS over QUIC and DNS over HTTP/3 frontends + +- **idle_timeout**: Unsigned integer ``(5)`` - Set the idle timeout, in seconds +- **congestion_control_algorithm**: String ``(reno)`` - The congestion control algorithm to be used. Supported values are: reno, cubic, bbr +- **internal_pipe_buffer_size**: Unsigned integer ``(1048576)`` - Set the size in bytes of the internal buffer of the pipes used internally to pass queries and responses between threads. Requires support for ``F_SETPIPE_SZ`` which is present in Linux since 2.6.35. The actual size might be rounded up to a multiple of a page size. 0 means that the OS default size is used + + +.. _yaml-settings-IncomingTcpConfiguration: + +IncomingTcpConfiguration +------------------------ + +TCP-related settings for frontends + +- **max_in_flight_queries**: Unsigned integer ``(0)`` - Maximum number of in-flight queries over a single TCP connection. The default is 0, which disables out-of-order processing +- **listen_queue_size**: Unsigned integer ``(0)`` - Set the size of the listen queue. Default is ``SOMAXCONN`` +- **fast_open_queue_size**: Unsigned integer ``(0)`` - Set the TCP Fast Open queue size, enabling TCP Fast Open when available and the value is larger than 0 +- **max_concurrent_connections**: Unsigned integer ``(0)`` - Maximum number of concurrent incoming TCP connections to this frontend. The default is 0 which means unlimited + + +.. _yaml-settings-IncomingTlsCertificateKeyPairConfiguration: + +IncomingTlsCertificateKeyPairConfiguration +------------------------------------------ + +A pair of TLS certificate and key, with an optional associated password + +- **certificate**: String - A path to a file containing the certificate, in ``PEM``, ``DER`` or ``PKCS12`` format +- **key**: String ``("")`` - A path to a file containing the key corresponding to the certificate, in ``PEM``, ``DER`` or ``PKCS12`` format +- **password**: String ``("")`` - Password protecting the PKCS12 file if appropriate + + +.. _yaml-settings-IncomingTlsConfiguration: + +IncomingTlsConfiguration +------------------------ + +TLS parameters for frontends + +- **provider**: String ``(OpenSSL)`` - . Supported values are: OpenSSL, GnuTLS +- **certificates**: Sequence of :ref:`IncomingTlsCertificateKeyPairConfiguration ` - List of TLS certificates and their associated keys +- **ciphers**: String ``("")`` - The TLS ciphers to use, in OpenSSL format. Note that ``ciphers_tls_13`` should be used for TLS 1.3 +- **ciphers_tls_13**: String ``("")`` - The TLS ciphers to use for TLS 1.3, in OpenSSL format +- **minimum_version**: String ``(tls1.0)`` - The minimum version of the TLS protocol to support. Supported values are: tls1.0, tls1.1, tls1.2, tls1.3 +- **ticket_key_file**: String ``("")`` - The path to a file from where TLS tickets keys should be loaded, to support :rfc:`5077`. These keys should be rotated often and never written to persistent storage to preserve forward secrecy. The default is to generate a random key. dnsdist supports several tickets keys to be able to decrypt existing sessions after the rotation. See :doc:`../advanced/tls-sessions-management` for more information +- **tickets_keys_rotation_delay**: Unsigned integer ``(43200)`` - Set the delay before the TLS tickets key is rotated, in seconds. Default is 43200 (12h). A value of 0 disables the automatic rotation, which might be useful when ``ticket_key_file`` is used +- **number_of_tickets_keys**: Unsigned integer ``(5)`` - The maximum number of tickets keys to keep in memory at the same time. Only one key is marked as active and used to encrypt new tickets while the remaining ones can still be used to decrypt existing tickets after a rotation +- **prefer_server_ciphers**: Boolean ``(true)`` - Whether to prefer the order of ciphers set by the server instead of the one set by the client. Default is true, meaning that the order of the server is used. For OpenSSL >= 1.1.1, setting this option also enables the temporary re-prioritization of the ChaCha20-Poly1305 cipher if the client prioritizes it +- **session_timeout**: Unsigned integer ``(0)`` - Set the TLS session lifetime in seconds, this is used both for TLS ticket lifetime and for sessions kept in memory +- **session_tickets**: Boolean ``(true)`` - Whether session resumption via session tickets is enabled. Default is true, meaning tickets are enabled +- **number_of_stored_sessions**: Unsigned integer ``(20480)`` - The maximum number of sessions kept in memory at the same time. Default is 20480. Setting this value to 0 disables stored session entirely +- **ocsp_response_files**: Sequence of String ``("")`` - List of files containing OCSP responses, in the same order than the certificates and keys, that will be used to provide OCSP stapling responses +- **key_log_file**: String ``("")`` - Write the TLS keys in the specified file so that an external program can decrypt TLS exchanges, in the format described in https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSS/Key_Log_Format. Note that this feature requires OpenSSL >= 1.1.1 +- **release_buffers**: Boolean ``(true)`` - Whether OpenSSL should release its I/O buffers when a connection goes idle, saving roughly 35 kB of memory per connection +- **enable_renegotiation**: Boolean ``(false)`` - Whether secure TLS renegotiation should be enabled. Disabled by default since it increases the attack surface and is seldom used for DNS +- **async_mode**: Boolean ``(false)`` - Whether to enable experimental asynchronous TLS I/O operations if the ``nghttp2`` library is used, ``OpenSSL`` is used as the TLS implementation and an asynchronous capable SSL engine (or provider) is loaded. See also :func:`loadTLSEngine` or :func:`loadTLSProvider` to load the engine (or provider) +- **ktls**: Boolean ``(false)`` - Whether to enable the experimental kernel TLS support on Linux, if both the kernel and the OpenSSL library support it +- **read_ahead**: Boolean ``(true)`` - When the TLS provider is set to OpenSSL, whether we tell the library to read as many input bytes as possible, which leads to better performance by reducing the number of syscalls +- **proxy_protocol_outside_tls**: Boolean ``(false)`` - When the use of incoming proxy protocol is enabled, whether the payload is prepended after the start of the TLS session (so inside, meaning it is protected by the TLS layer providing encryption and authentication) or not (outside, meaning it is in clear-text). Default is false which means inside. Note that most third-party software like HAproxy expect the proxy protocol payload to be outside, in clear-text +- **ignore_configuration_errors**: Boolean ``(false)`` - Ignore TLS configuration errors (such as invalid certificate path) and just issue a warning instead of aborting the whole process + + +.. _yaml-settings-KeyValueStoresConfiguration: + +KeyValueStoresConfiguration +--------------------------- + +List of key-value stores that can be used with :ref:`yaml-settings-KeyValueStoreLookupAction` or :ref:`yaml-settings-KeyValueStoreLookupSelector` + +- **lmdb**: Sequence of :ref:`LmdbKvStoreConfiguration ` - List of LMDB-based key-value stores +- **cdb**: Sequence of :ref:`CdbKvStoreConfiguration ` - List of CDB-based key-value stores +- **lookup_keys**: :ref:`KvsLookupKeysConfiguration ` - List of lookup keys + + +.. _yaml-settings-KvsLookupKeyQnameConfiguration: + +KvsLookupKeyQnameConfiguration +------------------------------ + +Lookup key that can be used with :ref:`yaml-settings-KeyValueStoreLookupAction` or :ref:`yaml-settings-KeyValueStoreLookupSelector`, will return the qname of the query in DNS wire format + +- **name**: String - The name of this lookup key +- **wire_format**: Boolean ``(true)`` - Whether to do the lookup in wire format (default) or in plain text + + +.. _yaml-settings-KvsLookupKeySourceIpConfiguration: + +KvsLookupKeySourceIpConfiguration +--------------------------------- + +Lookup key that can be used with :ref:`yaml-settings-KeyValueStoreLookupAction` or :ref:`yaml-settings-KeyValueStoreLookupSelector`, will return the source IP of the client in network byte-order + +- **name**: String - The name of this lookup key +- **v4_mask**: Unsigned integer ``(32)`` - Mask applied to IPv4 addresses. Default is 32 (the whole address) +- **v6_mask**: Unsigned integer ``(128)`` - Mask applied to IPv6 addresses. Default is 128 (the whole address) +- **include_port**: Boolean ``(false)`` - Whether to append the port (in network byte-order) after the address + + +.. _yaml-settings-KvsLookupKeySuffixConfiguration: + +KvsLookupKeySuffixConfiguration +------------------------------- + +Lookup key that can be used with :ref:`yaml-settings-KeyValueStoreLookupAction` or :ref:`yaml-settings-KeyValueStoreLookupSelector`, will return a vector of keys based on the labels of the qname in DNS wire format or plain text. For example if the qname is sub.domain.powerdns.com. the following keys will be returned: + +- ``\\3sub\\6domain\\8powerdns\\3com\\0`` +- ``\\6domain\\8powerdns\\3com\\0`` +- ``\\8powerdns\\3com\\0`` +- ``\\3com\\0`` +- ``\\0`` + +If ``min_labels`` is set to a value larger than ``0`` the lookup will only be done as long as there is at least ``min_labels`` labels remaining. Taking back our previous example, it means only the following keys will be returned if ``min_labels`` is set to ``2``: + +- ``\\3sub\\6domain\\8powerdns\\3com\\0`` +- ``\\6domain\\8powerdns\\3com\\0`` +- ``\\8powerdns\\3com\\0`` + + +- **name**: String - The name of this lookup key +- **minimum_labels**: Unsigned integer ``(0)`` - The minimum number of labels to do a lookup for. Default is 0 which means unlimited +- **wire_format**: Boolean ``(true)`` - Whether to do the lookup in wire format (default) or in plain text + + +.. _yaml-settings-KvsLookupKeyTagConfiguration: + +KvsLookupKeyTagConfiguration +---------------------------- + +Lookup key that can be used with :ref:`yaml-settings-KeyValueStoreLookupAction` or :ref:`yaml-settings-KeyValueStoreLookupSelector`, will return the value of the corresponding tag for this query, if it exists + +- **name**: String +- **tag**: String + + +.. _yaml-settings-KvsLookupKeysConfiguration: + +KvsLookupKeysConfiguration +-------------------------- + +List of look keys that can be used with :ref:`yaml-settings-KeyValueStoreLookupAction` or :ref:`yaml-settings-KeyValueStoreLookupSelector` + +- **source_ip_keys**: Sequence of :ref:`KvsLookupKeySourceIpConfiguration ` +- **qname_keys**: Sequence of :ref:`KvsLookupKeyQnameConfiguration ` +- **suffix_keys**: Sequence of :ref:`KvsLookupKeySuffixConfiguration ` +- **tag_keys**: Sequence of :ref:`KvsLookupKeyTagConfiguration ` + + +.. _yaml-settings-LazyHealthCheckConfiguration: + +LazyHealthCheckConfiguration +---------------------------- + +Lazy health-check related settings for backends + +- **interval**: Unsigned integer ``(30)`` - The interval, in seconds, between health-check queries in 'lazy' mode. Note that when ``use_exponential_back_off`` is set to true, the interval doubles between every queries. These queries are only sent when a threshold of failing regular queries has been reached, and until the backend is available again +- **min_sample_count**: Unsigned integer ``(1)`` - The minimum amount of regular queries that should have been recorded before the ``threshold`` threshold can be applied +- **mode**: String ``(TimeoutOrServFail)`` - The 'lazy' health-check mode: ``TimeoutOnly`` means that only timeout and I/O errors of regular queries will be considered for the ``threshold``, while ``TimeoutOrServFail`` will also consider ``Server Failure`` answers. Supported values are: TimeoutOnly, TimeoutOrServFail +- **sample_size**: Unsigned integer ``(100)`` - The maximum size of the sample of queries to record and consider for the ``threshold``. Default is 100, which means the result (failure or success) of the last 100 queries will be considered +- **threshold**: Unsigned integer ``(20)`` - The threshold, as a percentage, of queries that should fail for the 'lazy' health-check to be triggered. The default is 20 which means 20% of the last ``sample_size`` queries should fail for a health-check to be triggered +- **use_exponential_back_off**: Boolean ``(false)`` - Whether the 'lazy' health-check should use an exponential back-off instead of a fixed value, between health-check probes. The default is false which means that after a backend has been moved to the ``down`` state health-check probes are sent every ``interval`` seconds. When set to true, the delay between each probe starts at ``interval`` seconds and doubles between every probe, capped at ``max_back_off`` seconds +- **max_back_off**: Unsigned integer ``(3600)`` - This value, in seconds, caps the time between two health-check queries when ``use_exponential_back_off`` is set to true. The default is 3600 which means that at most one hour will pass between two health-check queries + + +.. _yaml-settings-LmdbKvStoreConfiguration: + +LmdbKvStoreConfiguration +------------------------ + +LMDB-based key-value store + +- **name**: String - The name of this object +- **file_name**: String - The path to an existing ``LMDB`` database created with ``MDB_NOSUBDIR`` +- **database_name**: String - The name of the database to use +- **no_lock**: Boolean ``(false)`` - Whether to open the database with the ``MDB_NOLOCK`` flag + + +.. _yaml-settings-LoadBalancingPoliciesConfiguration: + +LoadBalancingPoliciesConfiguration +---------------------------------- + +Setting for load-balancing policies + +- **default_policy**: String ``(leastOutstanding)`` - Set the default server selection policy +- **servfail_on_no_server**: Boolean ``(false)`` - If set, return a ServFail when no servers are available, instead of the default behaviour of dropping the query +- **round_robin_servfail_on_no_server**: Boolean ``(false)`` - 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 +- **weighted_balancing_factor**: Double ``(0.0)`` - Set the maximum imbalance between the number of outstanding queries intended for a given server, based on its weight, and the actual number, when using the ``whashed`` or ``wrandom`` load-balancing policy. Default is 0, which disables the bounded-load algorithm +- **consistent_hashing_balancing_factor**: Double ``(0.0)`` - Set the maximum imbalance between the number of outstanding queries intended for a given server, based on its weight, and the actual number, when using the ``chashed`` consistent hashing load-balancing policy. Default is 0, which disables the bounded-load algorithm +- **custom_policies**: Sequence of :ref:`CustomLoadBalancingPolicyConfiguration ` - Custom load-balancing policies implemented in Lua +- **hash_perturbation**: Unsigned integer ``(0)`` - 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 instances + + +.. _yaml-settings-LoggingConfiguration: + +LoggingConfiguration +-------------------- + +Logging settings + +- **verbose**: Boolean ``(false)`` - Set whether log messages issued at the verbose level should be logged +- **verbose_health_checks**: Boolean ``(false)`` - Set whether health check errors should be logged +- **verbose_log_destination**: String ``("")`` - Set a destination file to write the ‘verbose’ log messages to, instead of sending them to syslog and/or the standard output which is the default. Note that these messages will no longer be sent to syslog or the standard output once this option has been set. There is no rotation or file size limit. Only use this feature for debugging under active operator control +- **syslog_facility**: String ``("")`` - Set the syslog logging facility to the supplied value (values with or without the ``log_`` prefix are supported). Supported values are: local0, log_local0, local1, log_local1, local2, log_local2, local3, log_local3, local4, log_local4, local5, log_local5, local6, log_local6, local7, log_local7, kern, log_kern, user, log_user, mail, log_mail, daemon, log_daemon, auth, log_auth, syslog, log_syslog, lpr, log_lpr, news, log_news, uucp, log_uucp, cron, log_cron, authpriv, log_authpriv, ftp, log_ftp +- **structured**: :ref:`StructuredLoggingConfiguration ` + + +.. _yaml-settings-MetricsConfiguration: + +MetricsConfiguration +-------------------- + +Metrics-related settings + +- **carbon**: Sequence of :ref:`CarbonConfiguration ` - List of Carbon endpoints to send metrics to + + +.. _yaml-settings-OutgoingAutoUpgradeConfiguration: + +OutgoingAutoUpgradeConfiguration +-------------------------------- + +Setting for the automatically upgraded backend to a more secure version of the DNS protocol + +- **enabled**: Boolean ``(false)`` - Whether to use the 'Discovery of Designated Resolvers' mechanism to automatically upgrade a Do53 backend to DoT or DoH, depending on the priorities present in the SVCB record returned by the backend +- **interval**: Unsigned integer ``(3600)`` - If ``enabled`` is set, how often to check if an upgrade is available, in seconds +- **keep**: Boolean ``(false)`` - If ``enabled`` is set, whether to keep the existing Do53 backend around after an upgrade. Default is false which means the Do53 backend will be replaced by the upgraded one +- **pool**: String ``("")`` - If ``enabled`` is set, in which pool to place the newly upgraded backend. Default is empty which means the backend is placed in the default pool +- **doh_key**: Unsigned integer ``(7)`` - If ``enabled`` is set, the value to use for the SVC key corresponding to the DoH path. Default is 7 +- **use_lazy_health_check**: Boolean ``(false)`` - Whether the auto-upgraded version of this backend should use the lazy health-checking mode. Default is false, which means it will use the regular health-checking mode + + +.. _yaml-settings-OutgoingDohConfiguration: + +OutgoingDohConfiguration +------------------------ + +DNS over HTTPS specific settings for backends + +- **path**: String ``(/dns-query)`` - The HTTP path to send queries to +- **add_x_forwarded_headers**: Boolean ``(false)`` - Whether to add X-Forwarded-For, X-Forwarded-Port and X-Forwarded-Proto headers to the backend + + +.. _yaml-settings-OutgoingTcpConfiguration: + +OutgoingTcpConfiguration +------------------------ + +TCP-related settings for backends + +- **retries**: Unsigned integer ``(5)`` - The number of TCP connection attempts to the backend, for a given query +- **connect_timeout**: Unsigned integer ``(5)`` - The timeout (in seconds) of a TCP connection attempt +- **send_timeout**: Unsigned integer ``(30)`` - The timeout (in seconds) of a TCP write attempt +- **receive_timeout**: Unsigned integer ``(30)`` - The timeout (in seconds) of a TCP read attempt +- **fast_open**: Boolean ``(false)`` - Whether to enable TCP Fast Open + + +.. _yaml-settings-OutgoingTlsConfiguration: + +OutgoingTlsConfiguration +------------------------ + +TLS parameters for backends + +- **provider**: String ``(OpenSSL)`` - . Supported values are: OpenSSL, GnuTLS +- **subject_name**: String ``("")`` - The subject name passed in the SNI value of the TLS handshake, and against which to validate the certificate presented by the backend. Default is empty. If set this value supersedes any ``subject_addr`` one +- **subject_address**: String ``("")`` - The subject IP address passed in the SNI value of the TLS handshake, and against which to validate the certificate presented by the backend +- **validate_certificate**: Boolean ``(true)`` - Whether the certificate presented by the backend should be validated against the CA store (see ``ca_store``) +- **ca_store**: String ``("")`` - Specifies the path to the CA certificate file, in PEM format, to use to check the certificate presented by the backend. Default is an empty string, which means to use the system CA store. Note that this directive is only used if ``validate_certificates`` is set +- **ciphers**: String ``("")`` - The TLS ciphers to use. The exact format depends on the provider used. When the OpenSSL provider is used, ciphers for TLS 1.3 must be specified via ``ciphers_tls_13`` +- **ciphers_tls_13**: String ``("")`` - The ciphers to use for TLS 1.3, when the OpenSSL provider is used. When the GnuTLS provider is used, ``ciphers`` applies regardless of the TLS protocol and this setting is not used. +- **key_log_file**: String ``("")`` - Write the TLS keys in the specified file so that an external program can decrypt TLS exchanges, in the format described in https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSS/Key_Log_Format. Note that this feature requires OpenSSL >= 1.1.1 +- **release_buffers**: Boolean ``(true)`` - Whether OpenSSL should release its I/O buffers when a connection goes idle, saving roughly 35 kB of memory per connection +- **enable_renegotiation**: Boolean ``(false)`` - Whether secure TLS renegotiation should be enabled. Disabled by default since it increases the attack surface and is seldom used for DNS +- **ktls**: Boolean ``(false)`` - Whether to enable the experimental kernel TLS support on Linux, if both the kernel and the OpenSSL library support it. Default is false. Currently both DoT and DoH backend support this option + + +.. _yaml-settings-PacketCacheConfiguration: + +PacketCacheConfiguration +------------------------ + +Packet-cache settings + +- **name**: String - The name of the packet cache object +- **size**: Unsigned integer - The maximum number of entries in this cache +- **deferrable_insert_lock**: Boolean ``(true)`` - Whether the cache should give up insertion if the lock is held by another thread, or simply wait to get the lock +- **dont_age**: Boolean ``(false)`` - Don’t reduce TTLs when serving from the cache. Use this when dnsdist fronts a cluster of authoritative servers +- **keep_stale_data**: Boolean ``(false)`` - Whether to suspend the removal of expired entries from the cache when there is no backend available in at least one of the pools using this cache +- **max_negative_ttl**: Unsigned integer ``(3600)`` - Cache a NXDomain or NoData answer from the backend for at most this amount of seconds, even if the TTL of the SOA record is higher +- **max_ttl**: Unsigned integer ``(86400)`` - Cap the TTL for records to his number +- **min_ttl**: Unsigned integer ``(0)`` - Don’t cache entries with a TTL lower than this +- **shards**: Unsigned integer ``(20)`` - Number of shards to divide the cache into, to reduce lock contention +- **parse_ecs**: Boolean ``(false)`` - Whether any EDNS Client Subnet option present in the query should be extracted and stored to be able to detect hash collisions involving queries with the same qname, qtype and qclass but a different incoming ECS value. Enabling this option adds a parsing cost and only makes sense if at least one backend might send different responses based on the ECS value, so it's disabled by default. Enabling this option is required for the :doc:`../advanced/zero-scope` option to work +- **stale_ttl**: Unsigned integer ``(60)`` - When the backend servers are not reachable, and global configuration setStaleCacheEntriesTTL is set appropriately, TTL that will be used when a stale cache entry is returned +- **temporary_failure_ttl**: Unsigned integer ``(60)`` - On a SERVFAIL or REFUSED from the backend, cache for this amount of seconds +- **cookie_hashing**: Boolean ``(false)`` - If true, EDNS Cookie values will be hashed, resulting in separate entries for different cookies in the packet cache. This is required if the backend is sending answers with EDNS Cookies, otherwise a client might receive an answer with the wrong cookie +- **maximum_entry_size**: Unsigned integer ``(4096)`` - The maximum size, in bytes, of a DNS packet that can be inserted into the packet cache +- **options_to_skip**: Sequence of String ``("")`` - Extra list of EDNS option codes to skip when hashing the packet (if ``cookie_hashing`` above is false, EDNS cookie option number will be added to this list internally) + + +.. _yaml-settings-PoolConfiguration: + +PoolConfiguration +----------------- + +Settings for a pool of servers + +- **name**: String - The name of this pool +- **packet_cache**: String ``("")`` - The name of a packet cache object, if any +- **policy**: String ``("")`` - The name of the load-balancing policy associated to this pool. If left empty, the global policy will be used + + +.. _yaml-settings-ProtoBufMetaConfiguration: + +ProtoBufMetaConfiguration +------------------------- + +Meta-data entry to be added to a Protocol Buffer message + +- **key**: String - Name of the meta entry +- **value**: String - Value of the meta entry + + +.. _yaml-settings-ProtobufLoggerConfiguration: + +ProtobufLoggerConfiguration +--------------------------- + +Endpoint to send queries and/or responses data to, using the native PowerDNS format + +- **name**: String - Name of this endpoint +- **address**: String - An IP:PORT combination where the logger is listening +- **timeout**: Unsigned integer ``(2)`` - TCP connect timeout in seconds +- **max_queued_entries**: Unsigned integer ``(100)`` - Queue this many messages before dropping new ones (e.g. when the remote listener closes the connection) +- **reconnect_wait_time**: Unsigned integer ``(1)`` - Time in seconds between reconnection attempts + + +.. _yaml-settings-ProxyProtocolConfiguration: + +ProxyProtocolConfiguration +-------------------------- + +Proxy Protocol-related settings + +- **acl**: Sequence of String ``("")`` - Set the list of netmasks from which a Proxy Protocol header will be required, over UDP, TCP and DNS over TLS. The default is empty. Note that a proxy protocol payload will be required from these clients, regular DNS queries will no longer be accepted if they are not preceded by a proxy protocol payload. Be also aware that, if ``apply_acl_to_proxied_clients`` is set (default is false), the general ACL will be applied to the source IP address as seen by dnsdist first, but also to the source IP address provided in the Proxy Protocol header. +- **maximum_payload_size**: Unsigned integer ``(512)`` - Set the maximum size of a Proxy Protocol payload that dnsdist is willing to accept, in bytes. The default is 512, which is more than enough except for very large TLV data. This setting can’t be set to a value lower than 16 since it would deny of Proxy Protocol headers +- **apply_acl_to_proxied_clients**: Boolean ``(false)`` - Whether the general ACL should be applied to the source IP address provided in the Proxy Protocol header, in addition to being applied to the source IP address as seen by dnsdist first + + +.. _yaml-settings-ProxyProtocolValueConfiguration: + +ProxyProtocolValueConfiguration +------------------------------- + +A proxy protocol Type-Length Value entry + +- **key**: Unsigned integer - The type of the proxy protocol entry +- **value**: String - The value of the proxy protocol entry + + +.. _yaml-settings-QueryCountConfiguration: + +QueryCountConfiguration +----------------------- + +Per-record Carbon statistics of the amount of queries. See :doc:`../guides/carbon` + +- **enabled**: Boolean ``(false)`` - Enable per-record Carbon statistics of the amount of queries +- **filter_function_name**: String ``("")`` - The name of a Lua function to filter which query should be accounted for, and how +- **filter_function_code**: String ``("")`` - The code of a Lua function to filter which query should be accounted for, and how +- **filter_function_file**: String ``("")`` - The path to a file containing the code of a Lua function to filter which query should be accounted for, and how + + +.. _yaml-settings-QueryRuleConfiguration: + +QueryRuleConfiguration +---------------------- + +A rule that can applied on queries + +- **name**: String ``("")`` - The name to assign to this rule +- **uuid**: String - The UUID to assign to this rule, if any +- **selector**: :ref:`Selector ` - The selector to match queries against +- **action**: :ref:`Action ` - The action taken if the selector matches + + +.. _yaml-settings-RemoteLoggingConfiguration: + +RemoteLoggingConfiguration +-------------------------- + +Queries and/or responses remote logging settings + +- **protobuf_loggers**: Sequence of :ref:`ProtobufLoggerConfiguration ` - List of endpoints to send queries and/or responses data to, using the native PowerDNS format +- **dnstap_loggers**: Sequence of :ref:`DnstapLoggerConfiguration ` - List of endpoints to send queries and/or responses data to, using the dnstap format + + +.. _yaml-settings-ResponseRuleConfiguration: + +ResponseRuleConfiguration +------------------------- + +A rule that can applied on responses + +- **name**: String ``("")`` - The name to assign to this rule +- **uuid**: String ``("")`` - The UUID to assign to this rule, if any +- **selector**: :ref:`Selector ` - The selector to match responses against +- **action**: :ref:`ResponseAction ` - The action taken if the selector matches + + +.. _yaml-settings-RingBuffersConfiguration: + +RingBuffersConfiguration +------------------------ + +Settings for in-memory ring buffers, that are used for live traffic inspection and dynamic rules + +- **size**: Unsigned integer ``(10000)`` - The maximum amount of queries to keep in the ringbuffer +- **shards**: Unsigned integer ``(10)`` - The number of shards to use to limit lock contention +- **lock_retries**: Unsigned integer ``(5)`` - Set the number of shards to attempt to lock without blocking before giving up and simply blocking while waiting for the next shard to be available. Default to 5 if there is more than one shard, 0 otherwise +- **record_queries**: Boolean ``(true)`` - Whether to record queries in the ring buffers +- **record_responses**: Boolean ``(true)`` - Whether to record responses in the ring buffers + + +.. _yaml-settings-SecurityPollingConfiguration: + +SecurityPollingConfiguration +---------------------------- + +- **polling_interval**: Unsigned integer ``(3600)`` +- **suffix**: String ``(secpoll.powerdns.com.)`` + + +.. _yaml-settings-SnmpConfiguration: + +SnmpConfiguration +----------------- + +SNMP-related settings + +- **enabled**: Boolean ``(false)`` - Enable SNMP support +- **traps_enabled**: Boolean ``(false)`` - Enable the sending of SNMP traps for specific events +- **daemon_socket**: String ``("")`` - A string specifying how to connect to the daemon agent. This is usually the path to a UNIX socket, but e.g. ``tcp:localhost:705`` can be used as well. By default, SNMP agent’s default socket is used + + +.. _yaml-settings-StructuredLoggingConfiguration: + +StructuredLoggingConfiguration +------------------------------ + +Structured-like logging settings + +- **enabled**: Boolean ``(false)`` - Set whether log messages should be in a structured-logging-like format. This is turned off by default. + The resulting format looks like this (when timestamps are enabled via ``--log-timestamps`` and with ``level_prefix: prio`` and ``time_format: ISO8601``):: + + ts=\"2023-11-06T12:04:58+0100\" prio=\"Info\" msg=\"Added downstream server 127.0.0.1:53\" + + And with ``level_prefix: level`` and ``time_format: numeric``):: + + ts=\"1699268815.133\" level=\"Info\" msg=\"Added downstream server 127.0.0.1:53\" + +- **level_prefix**: String ``(prio)`` - Set the key name for the log level. There is unfortunately no standard name for this key, so in some setups it might be useful to set this value to a different name to have consistency across products +- **time_format**: String ``(numeric)`` - Set the time format. Supported values are: ISO8601, numeric + + +.. _yaml-settings-TcpTuningConfiguration: + +TcpTuningConfiguration +---------------------- + +- **worker_threads**: Unsigned integer ``(10)`` +- **receive_timeout**: Unsigned integer ``(2)`` +- **send_timeout**: Unsigned integer ``(2)`` +- **max_queries_per_connection**: Unsigned integer ``(0)`` +- **max_connection_duration**: Unsigned integer ``(0)`` +- **max_queued_connections**: Unsigned integer ``(10000)`` +- **internal_pipe_buffer_size**: Unsigned integer ``(1048576)`` +- **outgoing_max_idle_time**: Unsigned integer ``(300)`` +- **outgoing_cleanup_interval**: Unsigned integer ``(60)`` +- **outgoing_max_idle_connection_per_backend**: Unsigned integer ``(10)`` +- **max_connections_per_client**: Unsigned integer ``(0)`` +- **fast_open_key**: String ``("")`` + + +.. _yaml-settings-TlsEngineConfiguration: + +TlsEngineConfiguration +---------------------- + +OpenSSL engine settings + +- **name**: String - The engine name +- **default_string**: String ``("")`` - The default string to pass to the engine. The exact value depends on the engine but represents the algorithms to register with the engine, as a list of comma-separated keywords. For example 'RSA,EC,DSA,DH,PKEY,PKEY_CRYPTO,PKEY_ASN1' + + +.. _yaml-settings-TlsTuningConfiguration: + +TlsTuningConfiguration +---------------------- + +- **outgoing_tickets_cache_cleanup_delay**: Unsigned integer ``(60)`` +- **outgoing_tickets_cache_validity**: Unsigned integer ``(600)`` +- **max_outgoing_tickets_per_backend**: Unsigned integer ``(20)`` +- **providers**: Sequence of String ``("")`` - Load OpenSSL providers. Providers can be used to accelerate cryptographic operations, like for example Intel QAT. At the moment up to a maximum of 32 loaded providers are supported, and that support is experimental. Note that this feature is only available when building against OpenSSL version >= 3.0 and with the ``-–enable-tls-provider`` configure flag on. In other cases, ``engines`` should be used instead. Some providers might actually degrade performance unless the TLS asynchronous mode of OpenSSL is enabled. To enable it see the ``async_mode`` parameter of TLS frontends +- **engines**: Sequence of :ref:`TlsEngineConfiguration ` - Load OpenSSL engines. Engines can be used to accelerate cryptographic operations, like for example Intel QAT. At the moment up to a maximum of 32 loaded engines are supported, and that support is experimental. Some engines might actually degrade performance unless the TLS asynchronous mode of OpenSSL is enabled. To enable it see the ``async_mode`` parameter of TLS frontends + + +.. _yaml-settings-TuningConfiguration: + +TuningConfiguration +------------------- + +Tuning settings + +- **doh**: :ref:`DohTuningConfiguration ` - DoH-related tuning settings +- **tcp**: :ref:`TcpTuningConfiguration ` - TCP-related tuning settings +- **tls**: :ref:`TlsTuningConfiguration ` - TLS-related tuning settings +- **udp**: :ref:`UdpTuningConfiguration ` - UDP-related tuning settings + + +.. _yaml-settings-UdpTuningConfiguration: + +UdpTuningConfiguration +---------------------- + +- **messages_per_round**: Unsigned integer ``(1)`` +- **send_buffer_size**: Unsigned integer ``(0)`` +- **receive_buffer_size**: Unsigned integer ``(0)`` +- **max_outstanding_per_backend**: Unsigned integer ``(65535)`` +- **timeout**: Unsigned integer ``(2)`` +- **randomize_outgoing_sockets_to_backend**: Boolean ``(false)`` +- **randomize_ids_to_backend**: Boolean ``(false)`` + + +.. _yaml-settings-WebserverConfiguration: + +WebserverConfiguration +---------------------- + +- **listen_address**: String ``("")`` - IP address and port to listen on +- **password**: String ``("")`` - The password used to access the internal webserver. Since 1.7.0 the password should be hashed and salted via the ``hashPassword()`` command +- **api_key**: String ``("")`` - The API Key (set to an empty string do disable it). Since 1.7.0 the key should be hashed and salted via the ``hashPassword()`` command +- **acl**: Sequence of String ``(127.0.0.1, ::1)`` - List of network masks or IP addresses that are allowed to open a connection to the web server +- **api_requires_authentication**: Boolean ``(true)`` - Whether access to the API (/api endpoints) requires a valid API key +- **stats_require_authentication**: Boolean ``(true)`` - Whether access to the statistics (/metrics and /jsonstat endpoints) requires a valid password or API key +- **dashboard_requires_authentication**: Boolean ``(true)`` - Whether access to the internal dashboard requires a valid password +- **max_concurrent_connections**: Unsigned integer ``(100)`` - The maximum number of concurrent web connections, or 0 which means an unlimited number +- **hash_plaintext_credentials**: Boolean ``(false)`` - Whether passwords and API keys provided in plaintext should be hashed during startup, to prevent the plaintext versions from staying in memory. Doing so increases significantly the cost of verifying credentials +- **custom_headers**: Sequence of :ref:`HttpCustomResponseHeaderConfiguration ` - List of custom HTTP headers to set in our responses +- **api_configuration_directory**: String ``("")`` - A valid directory where the configuration files will be written by the API +- **api_read_write**: Boolean ``(false)`` - Allow modifications via the API. Optionally saving these changes to disk. Modifications done via the API will not be written to the configuration by default and will not persist after a reload + + +.. _yaml-settings-XskConfiguration: + +XskConfiguration +---------------- + +An ``XSK`` / ``AF_XDP`` sockets map + +- **name**: String - The name to give to this map +- **interface**: String - The network interface to which the sockets will be associated +- **queues**: Unsigned integer - The number of queues the network interface has (can be retrieved by looking at the ``Combined`` line in the output of ``sudo ethtool -l ``). It should match the number of threads of the frontend or backend associated to this map +- **frames**: Unsigned integer ``(65536)`` - The number of frames to allocate for this map +- **map_path**: String ``(/sys/fs/bpf/dnsdist/xskmap)`` + + diff --git a/pdns/dnsdistdist/docs/reference/yaml-support-structures.rst b/pdns/dnsdistdist/docs/reference/yaml-support-structures.rst new file mode 100644 index 000000000000..ee33f158f053 --- /dev/null +++ b/pdns/dnsdistdist/docs/reference/yaml-support-structures.rst @@ -0,0 +1,50 @@ +.. raw:: latex + + \setcounter{secnumdepth}{-1} + +YAML support structures +======================= + +.. _yaml-settings-ResponseConfig: + +ResponseConfig +-------------- + +- **set_aa**: Boolean +- **set_ad**: Boolean +- **set_ra**: Boolean +- **ttl**: Unsigned integer + +.. _yaml-settings-SOAParams: + +SOAParams +--------- + +- **serial**: Unsigned integer +- **refresh**: Unsigned integer +- **retry**: Unsigned integer +- **expire**: Unsigned integer +- **minimum**: Unsigned integer + +.. _yaml-settings-SVCRecordAdditionalParams: + +SVCRecordAdditionalParams +------------------------- + +- **key**: Unsigned integer +- **value**: String + +.. _yaml-settings-SVCRecordParameters: + +SVCRecordParameters +------------------- + +- **mandatory_params**: Sequence of Unsigned integer +- **alpns**: Sequence of String +- **ipv4_hints**: Sequence of String +- **ipv6_hints**: Sequence of String +- **additional_params**: Sequence of :ref:`SVCRecordAdditionalParams ` +- **target**: String +- **port**: Unsigned integer +- **priority**: Unsigned integer +- **no_default_alpn**: Boolean diff --git a/pdns/dnsdistdist/docs/rules-actions.rst b/pdns/dnsdistdist/docs/rules-actions.rst index 8918ccc74e08..fa02bb53537a 100644 --- a/pdns/dnsdistdist/docs/rules-actions.rst +++ b/pdns/dnsdistdist/docs/rules-actions.rst @@ -9,7 +9,7 @@ Packet Policies It receives packets on one or several addresses it listens on, and determines whether it will process this packet based on the :doc:`advanced/acl`. Should the packet be processed, :program:`dnsdist` attempts to match any of the configured rules in order and when one matches, the associated action is performed. -These rule and action combinations are considered policies. The complete list of selectors (rules) can be found in :doc:`reference/selectors`, and the list of actions in :doc:`reference/actions`. +These rule and action combinations are considered policies. The complete list of selectors (rules) can be found in :doc:`reference/selectors` (:doc:`reference/yaml-selectors`), and the list of actions in :doc:`reference/actions` (:doc:`reference/yaml-actions` and :doc:`reference/yaml-response-actions`). Packet Actions -------------- diff --git a/pdns/dnsdistdist/docs/upgrade_guide.rst b/pdns/dnsdistdist/docs/upgrade_guide.rst index 40d64175aa09..427be84a3e24 100644 --- a/pdns/dnsdistdist/docs/upgrade_guide.rst +++ b/pdns/dnsdistdist/docs/upgrade_guide.rst @@ -4,6 +4,8 @@ Upgrade Guide 1.9.x to 2.0.0 -------------- +:program:`dnsdist` supports a new, optional ``yaml`` :doc:`configuration format `. To build :program:`dnsdist` with this feature enabled, a Rust compiler and a Python 3 interpreter are needed. + :func:`showTLSContexts` has been renamed to :func:`showTLSFrontends`. :func:`getTLSContext` and the associated :class:`TLSContext` have been removed, please use :func:`getTLSFrontend` and the associated :class:`TLSFrontend` instead. diff --git a/pdns/dnsdistdist/doh.cc b/pdns/dnsdistdist/doh.cc index 8b91a2bbe27f..739684477b82 100644 --- a/pdns/dnsdistdist/doh.cc +++ b/pdns/dnsdistdist/doh.cc @@ -8,6 +8,7 @@ #include #include #include +#include #include #include @@ -29,7 +30,6 @@ #include "dnsdist-ecs.hh" #include "dnsdist-metrics.hh" #include "dnsdist-proxy-protocol.hh" -#include "dnsdist-rules.hh" #include "libssl.hh" #include "threadname.hh" @@ -56,7 +56,7 @@ */ /* 'Intermediate' compatibility from https://wiki.mozilla.org/Security/Server_Side_TLS#Intermediate_compatibility_.28default.29 */ -static constexpr string_view DOH_DEFAULT_CIPHERS = "ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES256-SHA:ECDHE-ECDSA-DES-CBC3-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:DES-CBC3-SHA:!DSS"; +static constexpr std::string_view DOH_DEFAULT_CIPHERS = "ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES256-SHA:ECDHE-ECDSA-DES-CBC3-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:DES-CBC3-SHA:!DSS"; class DOHAcceptContext { diff --git a/pdns/dnsdistdist/m4/ax_compare_version.m4 b/pdns/dnsdistdist/m4/ax_compare_version.m4 new file mode 120000 index 000000000000..e9020c183c67 --- /dev/null +++ b/pdns/dnsdistdist/m4/ax_compare_version.m4 @@ -0,0 +1 @@ +../../../m4/ax_compare_version.m4 \ No newline at end of file diff --git a/pdns/dnsdistdist/m4/dnsdist_enable_yaml.m4 b/pdns/dnsdistdist/m4/dnsdist_enable_yaml.m4 new file mode 100644 index 000000000000..7705d857ac59 --- /dev/null +++ b/pdns/dnsdistdist/m4/dnsdist_enable_yaml.m4 @@ -0,0 +1,14 @@ +AC_DEFUN([DNSDIST_ENABLE_YAML], [ + AC_MSG_CHECKING([whether to enable YAML configuration]) + AC_ARG_ENABLE([yaml], + AS_HELP_STRING([--enable-yaml], [enable YAML configuration (requires Rust and Cargo) @<:@default=no@:>@]), + [enable_yaml=$enableval], + [enable_yaml=no] + ) + AC_MSG_RESULT([$enable_yaml]) + AM_CONDITIONAL([HAVE_YAML_CONFIGURATION], [test "x$enable_yaml" != "xno"]) + + AM_COND_IF([HAVE_YAML_CONFIGURATION], [ + AC_DEFINE([HAVE_YAML_CONFIGURATION], [1], [Define to 1 if you enable YAML configuration support]) + ]) +]) diff --git a/pdns/dnsdistdist/m4/pdns_check_cargo.m4 b/pdns/dnsdistdist/m4/pdns_check_cargo.m4 new file mode 120000 index 000000000000..114702d2bfdc --- /dev/null +++ b/pdns/dnsdistdist/m4/pdns_check_cargo.m4 @@ -0,0 +1 @@ +../../recursordist/m4/pdns_check_cargo.m4 \ No newline at end of file diff --git a/pdns/dnsdistdist/test-dnsdist-lua-ffi.cc b/pdns/dnsdistdist/test-dnsdist-lua-ffi.cc index 3de6749da9f5..014764974a3b 100644 --- a/pdns/dnsdistdist/test-dnsdist-lua-ffi.cc +++ b/pdns/dnsdistdist/test-dnsdist-lua-ffi.cc @@ -59,6 +59,7 @@ BOOST_AUTO_TEST_CASE(test_Query) DNSQuestion dq(ids, query); dnsdist_ffi_dnsquestion_t lightDQ(&dq); + const auto initialData = dq.getData(); { // dnsdist_ffi_dnsquestion_get_qtype @@ -260,14 +261,15 @@ BOOST_AUTO_TEST_CASE(test_Query) } { -#if 0 - // SpoofAction::operator() is a stub in the test runner - auto oldData = dq.getData(); + dq.getMutableData() = initialData; + const auto oldData = dq.getData(); std::vector values; ComboAddress v4("192.0.2.1"); ComboAddress v6("[2001:db8::42]"); - values.push_back({ reinterpret_cast(&v4.sin4.sin_addr.s_addr), sizeof(v4.sin4.sin_addr.s_addr)}); - values.push_back({ reinterpret_cast(&v6.sin6.sin6_addr.s6_addr), sizeof(v6.sin6.sin6_addr.s6_addr)}); + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) + values.push_back({reinterpret_cast(&v4.sin4.sin_addr.s_addr), sizeof(v4.sin4.sin_addr.s_addr)}); + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) + values.push_back({reinterpret_cast(&v6.sin6.sin6_addr.s6_addr), sizeof(v6.sin6.sin6_addr.s6_addr)}); dnsdist_ffi_dnsquestion_spoof_addrs(&lightDQ, values.data(), values.size()); BOOST_CHECK(dq.getData().size() > oldData.size()); @@ -275,20 +277,17 @@ BOOST_AUTO_TEST_CASE(test_Query) MOADNSParser mdp(false, reinterpret_cast(dq.getData().data()), dq.getData().size()); BOOST_CHECK_EQUAL(mdp.d_qname, ids.qname); BOOST_CHECK_EQUAL(mdp.d_header.qdcount, 1U); - BOOST_CHECK_EQUAL(mdp.d_header.ancount, values.size()); + /* only the A has been added since the query was not ANY */ + BOOST_CHECK_EQUAL(mdp.d_header.ancount, 1U); BOOST_CHECK_EQUAL(mdp.d_header.nscount, 0U); BOOST_CHECK_EQUAL(mdp.d_header.arcount, 0U); BOOST_REQUIRE_EQUAL(mdp.d_answers.size(), 1U); - BOOST_CHECK_EQUAL(mdp.d_answers.at(0).first.d_type, static_cast(QType::A)); - BOOST_CHECK_EQUAL(mdp.d_answers.at(0).first.d_class, QClass::IN); - BOOST_CHECK_EQUAL(mdp.d_answers.at(0).first.d_name, ids.qname); - BOOST_CHECK_EQUAL(mdp.d_answers.at(1).first.d_type, static_cast(QType::AAAA)); - BOOST_CHECK_EQUAL(mdp.d_answers.at(1).first.d_class, QClass::IN); - BOOST_CHECK_EQUAL(mdp.d_answers.at(1).first.d_name, ids.qname); + BOOST_CHECK_EQUAL(mdp.d_answers.at(0).d_type, static_cast(QType::A)); + BOOST_CHECK_EQUAL(mdp.d_answers.at(0).d_class, QClass::IN); + BOOST_CHECK_EQUAL(mdp.d_answers.at(0).d_name, ids.qname); dq.getMutableData() = oldData; -#endif } { diff --git a/pdns/dnsdistdist/test-dnsdistlbpolicies_cc.cc b/pdns/dnsdistdist/test-dnsdistlbpolicies_cc.cc index e641dc637154..9cdaa0b2a6c4 100644 --- a/pdns/dnsdistdist/test-dnsdistlbpolicies_cc.cc +++ b/pdns/dnsdistdist/test-dnsdistlbpolicies_cc.cc @@ -42,11 +42,6 @@ void setLuaNoSideEffect() { } -DNSAction::Action SpoofAction::operator()(DNSQuestion* dnsQuestion, std::string* ruleresult) const -{ - return DNSAction::Action::None; -} - bool setupDoTProtocolNegotiation(std::shared_ptr& tlsCtx) { (void)tlsCtx; diff --git a/pdns/dnsdistdist/test-dnsdistrules_cc.cc b/pdns/dnsdistdist/test-dnsdistrules_cc.cc index 7457fb3bb16f..692b50dbfb6e 100644 --- a/pdns/dnsdistdist/test-dnsdistrules_cc.cc +++ b/pdns/dnsdistdist/test-dnsdistrules_cc.cc @@ -9,6 +9,7 @@ #include #include "dnsdist-rules.hh" +#include "dnsdist-rules-factory.hh" void checkParameterBound(const std::string& parameter, uint64_t value, size_t max) { @@ -17,6 +18,74 @@ void checkParameterBound(const std::string& parameter, uint64_t value, size_t ma } } +struct RuleParameter +{ + std::string name; + std::variant value; +}; + +template +ParameterType getRequiredRuleParameter(const std::string& ruleName, std::vector& parameters, const std::string& parameterName) +{ + for (auto paramIt = parameters.begin(); paramIt != parameters.end(); ) { + if (paramIt->name != parameterName) { + ++paramIt; + continue; + } + auto value = std::get(paramIt->value); + parameters.erase(paramIt); + return value; + } + + throw std::runtime_error("Missing required parameter '" + parameterName + "' for selector '" + ruleName + "'"); +} + +template +ParameterType getOptionalRuleParameter(const std::string& ruleName, std::vector& parameters, const std::string& parameterName, ParameterType defaultValue) +{ + for (auto paramIt = parameters.begin(); paramIt != parameters.end(); ) { + if (paramIt->name != parameterName) { + ++paramIt; + continue; + } + auto value = std::get(paramIt->value); + parameters.erase(paramIt); + return value; + } + + return defaultValue; +} + +class TestMaxQPSIPRule : public DNSRule +{ +public: + TestMaxQPSIPRule(const std::string& ruleName, std::vector& parameters): + d_qps(getRequiredRuleParameter(ruleName, parameters, "qps")), + d_burst(getOptionalRuleParameter(ruleName, parameters, "burst", d_qps)), + d_ipv4trunc(getOptionalRuleParameter(ruleName, parameters, "ipv4-truncation", 32)) + { + } + + bool matches(const DNSQuestion* dnsQuestion) const override + { + return true; + } + + string toString() const override + { + return ""; + } +private: + unsigned int d_qps; + unsigned int d_burst; + unsigned int d_ipv4trunc; +}; + +static std::shared_ptr buildSelector(const std::string& type, std::vector& parameters) +{ + return std::make_shared(type, parameters); +} + static DNSQuestion getDQ(const DNSName* providedName = nullptr) { static const DNSName qname("powerdns.com."); @@ -42,7 +111,7 @@ BOOST_AUTO_TEST_CASE(test_MaxQPSIPRule) { unsigned int expiration = 300; unsigned int cleanupDelay = 60; unsigned int scanFraction = 10; - MaxQPSIPRule rule(maxQPS, maxBurst, 32, 64, expiration, cleanupDelay, scanFraction); + auto rule = dnsdist::selectors::getMaxQPSIPSelector(maxQPS, 32, 64, maxBurst, expiration, cleanupDelay, scanFraction, 1); InternalQueryState ids; ids.qname = DNSName("powerdns.com."); @@ -62,35 +131,35 @@ BOOST_AUTO_TEST_CASE(test_MaxQPSIPRule) { for (size_t idx = 0; idx < maxQPS; idx++) { /* let's use different source ports, it shouldn't matter */ ids.origRemote = ComboAddress("192.0.2.1:" + std::to_string(idx)); - BOOST_CHECK_EQUAL(rule.matches(&dq), false); - BOOST_CHECK_EQUAL(rule.getEntriesCount(), 1U); + BOOST_CHECK_EQUAL(rule->matches(&dq), false); + BOOST_CHECK_EQUAL(rule->getEntriesCount(), 1U); } /* maxQPS + 1, we should be blocked */ - BOOST_CHECK_EQUAL(rule.matches(&dq), true); - BOOST_CHECK_EQUAL(rule.getEntriesCount(), 1U); + BOOST_CHECK_EQUAL(rule->matches(&dq), true); + BOOST_CHECK_EQUAL(rule->getEntriesCount(), 1U); /* remove all entries that have not been updated since 'now' + 1, so all of them */ expiredTime.tv_sec += 1; - rule.cleanup(expiredTime); + rule->cleanup(expiredTime); /* we should have been cleaned up */ - BOOST_CHECK_EQUAL(rule.getEntriesCount(), 0U); + BOOST_CHECK_EQUAL(rule->getEntriesCount(), 0U); struct timespec beginInsertionTime; gettime(&beginInsertionTime); /* we should not be blocked anymore */ - BOOST_CHECK_EQUAL(rule.matches(&dq), false); + BOOST_CHECK_EQUAL(rule->matches(&dq), false); /* and we be back */ - BOOST_CHECK_EQUAL(rule.getEntriesCount(), 1U); + BOOST_CHECK_EQUAL(rule->getEntriesCount(), 1U); /* Let's insert a lot of different sources now */ for (size_t idxByte3 = 0; idxByte3 < 256; idxByte3++) { for (size_t idxByte4 = 0; idxByte4 < 256; idxByte4++) { ids.origRemote = ComboAddress("10.0." + std::to_string(idxByte3) + "." + std::to_string(idxByte4)); - BOOST_CHECK_EQUAL(rule.matches(&dq), false); + BOOST_CHECK_EQUAL(rule->matches(&dq), false); } } struct timespec endInsertionTime; @@ -98,32 +167,32 @@ BOOST_AUTO_TEST_CASE(test_MaxQPSIPRule) { /* don't forget the existing entry */ size_t total = 1 + 256 * 256; - BOOST_CHECK_EQUAL(rule.getEntriesCount(), total); + BOOST_CHECK_EQUAL(rule->getEntriesCount(), total); /* make sure all entries are still valid */ struct timespec notExpiredTime = beginInsertionTime; notExpiredTime.tv_sec -= 1; size_t scanned = 0; - auto removed = rule.cleanup(notExpiredTime, &scanned); + auto removed = rule->cleanup(notExpiredTime, &scanned); BOOST_CHECK_EQUAL(removed, 0U); /* the first entry should still have been valid, we should not have scanned more */ - BOOST_CHECK_EQUAL(scanned, rule.getNumberOfShards()); - BOOST_CHECK_EQUAL(rule.getEntriesCount(), total); + BOOST_CHECK_EQUAL(scanned, rule->getNumberOfShards()); + BOOST_CHECK_EQUAL(rule->getEntriesCount(), total); /* make sure all entries are _not_ valid anymore */ expiredTime = endInsertionTime; expiredTime.tv_sec += 1; - removed = rule.cleanup(expiredTime, &scanned); - BOOST_CHECK_EQUAL(removed, (total / scanFraction) + 1 + rule.getNumberOfShards()); + removed = rule->cleanup(expiredTime, &scanned); + BOOST_CHECK_EQUAL(removed, (total / scanFraction) + 1 + rule->getNumberOfShards()); /* we should not have scanned more than scanFraction */ BOOST_CHECK_EQUAL(scanned, removed); - BOOST_CHECK_EQUAL(rule.getEntriesCount(), total - removed); + BOOST_CHECK_EQUAL(rule->getEntriesCount(), total - removed); - rule.clear(); - BOOST_CHECK_EQUAL(rule.getEntriesCount(), 0U); - removed = rule.cleanup(expiredTime, &scanned); + rule->clear(); + BOOST_CHECK_EQUAL(rule->getEntriesCount(), 0U); + removed = rule->cleanup(expiredTime, &scanned); BOOST_CHECK_EQUAL(removed, 0U); BOOST_CHECK_EQUAL(scanned, 0U); } @@ -223,6 +292,12 @@ BOOST_AUTO_TEST_CASE(test_payloadSizeRule) { } BOOST_CHECK_THROW(PayloadSizeRule("invalid", 42U), std::runtime_error); + + std::vector parameters{ + RuleParameter{ "qps", 5U }, + RuleParameter{ "ipv4-truncation", 24U }, + }; + auto got = buildSelector("TestMaxQPSIPRule", parameters); } BOOST_AUTO_TEST_SUITE_END() diff --git a/pdns/dnstap.hh b/pdns/dnstap.hh index 357319b9a510..d82ce9a7d73b 100644 --- a/pdns/dnstap.hh +++ b/pdns/dnstap.hh @@ -30,7 +30,6 @@ #include "iputils.hh" #ifndef DISABLE_PROTOBUF -#include "protozero.hh" class DnstapMessage { diff --git a/pdns/fstrm_logger.hh b/pdns/fstrm_logger.hh index 44657247d86a..41d00740eb31 100644 --- a/pdns/fstrm_logger.hh +++ b/pdns/fstrm_logger.hh @@ -36,7 +36,7 @@ class FrameStreamLogger : public RemoteLoggerInterface { public: - FrameStreamLogger(int family, std::string address, bool connect, const std::unordered_map& options = std::unordered_map()); + FrameStreamLogger(int family, std::string address, bool connect, const std::unordered_map& options = std::unordered_map()); FrameStreamLogger(const FrameStreamLogger&) = delete; FrameStreamLogger(FrameStreamLogger&&) = delete; FrameStreamLogger& operator=(const FrameStreamLogger&) = delete; diff --git a/pdns/libssl.cc b/pdns/libssl.cc index 8672ffc97b6b..4bb47d0ec2ca 100644 --- a/pdns/libssl.cc +++ b/pdns/libssl.cc @@ -551,7 +551,7 @@ LibsslTLSVersion libssl_tls_version_from_string(const std::string& str) if (str == "tls1.3") { return LibsslTLSVersion::TLS13; } - throw std::runtime_error("Unknown TLS version '" + str); + throw std::runtime_error("Unknown TLS version '" + str + "'"); } const std::string& libssl_tls_version_to_string(LibsslTLSVersion version) diff --git a/pdns/misc.cc b/pdns/misc.cc index 29adfc51fa1c..1cc6d5114b2c 100644 --- a/pdns/misc.cc +++ b/pdns/misc.cc @@ -729,6 +729,62 @@ int logFacilityToLOG(unsigned int facility) } } +std::optional logFacilityFromString(std::string facilityStr) +{ + static std::unordered_map const s_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 us, 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} + }; + + toLowerInPlace(facilityStr); + auto facilityIt = s_facilities.find(facilityStr); + if (facilityIt == s_facilities.end()) { + return std::nullopt; + } + + return facilityIt->second; +} + string stripDot(const string& dom) { if(dom.empty()) diff --git a/pdns/misc.hh b/pdns/misc.hh index 39fdb6c08680..66dd0ccf3e33 100644 --- a/pdns/misc.hh +++ b/pdns/misc.hh @@ -109,6 +109,7 @@ bool getTSIGHashEnum(const DNSName& algoName, TSIGHashEnum& algoEnum); DNSName getTSIGAlgoName(TSIGHashEnum& algoEnum); int logFacilityToLOG(unsigned int facility); +std::optional logFacilityFromString(std::string facilityStr); template void diff --git a/pdns/qtype.cc b/pdns/qtype.cc index a0d5732cd10e..710421f97770 100644 --- a/pdns/qtype.cc +++ b/pdns/qtype.cc @@ -166,7 +166,23 @@ QType &QType::operator=(const string &s) return *this; } -const std::string QClass::toString() const +static const std::map s_classMap = { + {"IN", QClass::IN}, + {"CHAOS", QClass::CHAOS}, + {"NONE", QClass::NONE}, + {"ANY", QClass::ANY}, +}; + +QClass::QClass(const std::string& code) +{ + auto mapIt = s_classMap.find(code); + if (mapIt == s_classMap.end()) { + throw std::runtime_error("Invalid QClass '" + code + "'"); + } + qclass = mapIt->second; +} + +std::string QClass::toString() const { switch (qclass) { case IN: diff --git a/pdns/qtype.hh b/pdns/qtype.hh index f5a879bef8f5..dd2761a65f29 100644 --- a/pdns/qtype.hh +++ b/pdns/qtype.hh @@ -178,6 +178,7 @@ inline size_t hash_value(const QType qtype) { struct QClass { constexpr QClass(uint16_t code = 0) : qclass(code) {} + explicit QClass(const std::string& code); constexpr operator uint16_t() const { return qclass; @@ -186,7 +187,7 @@ struct QClass { return qclass; } - const std::string toString() const; + std::string toString() const; static const QClass IN; static const QClass CHAOS; diff --git a/regression-tests.dnsdist/dnsdisttests.py b/regression-tests.dnsdist/dnsdisttests.py index 70e5be984cd0..5797d4bb2c1f 100644 --- a/regression-tests.dnsdist/dnsdisttests.py +++ b/regression-tests.dnsdist/dnsdisttests.py @@ -81,6 +81,8 @@ class DNSDistTest(AssertEqualDNSMessageMixin, unittest.TestCase): _config_template = """ """ _config_params = ['_testServerPort'] + _yaml_config_template = None + _yaml_config_params = [] _acl = ['127.0.0.1/32'] _consoleKey = None _healthCheckName = 'a.root-servers.net.' @@ -134,13 +136,24 @@ def startDNSDist(cls): cls._consolePort = pickAvailablePort() print("Launching dnsdist..") - confFile = os.path.join('configs', 'dnsdist_%s.conf' % (cls.__name__)) + if cls._yaml_config_template: + if 'SKIP_YAML_TESTS' in os.environ: + raise unittest.SkipTest('YAML tests are disabled') + + params = tuple([getattr(cls, param) for param in cls._yaml_config_params]) + confFile = os.path.join('configs', 'dnsdist_%s.yml' % (cls.__name__)) + with open(confFile, 'w') as conf: + conf.write(cls._yaml_config_template % params) + params = tuple([getattr(cls, param) for param in cls._config_params]) print(params) + extension = 'lua' if cls._yaml_config_template else 'conf' + confFile = os.path.join('configs', 'dnsdist_%s.%s' % (cls.__name__, extension)) with open(confFile, 'w') as conf: conf.write("-- Autogenerated by dnsdisttests.py\n") - conf.write(f"-- dnsdist will listen on {cls._dnsDistPort}") + conf.write(f"-- dnsdist will listen on {cls._dnsDistPort}\n") conf.write(cls._config_template % params) + conf.write("\n") conf.write("setSecurityPollSuffix('')") if cls._skipListeningOnCL: diff --git a/regression-tests.dnsdist/test_CDB.py b/regression-tests.dnsdist/test_CDB.py index 2b4f0a06ad98..50f8474c5485 100644 --- a/regression-tests.dnsdist/test_CDB.py +++ b/regression-tests.dnsdist/test_CDB.py @@ -26,6 +26,9 @@ class CDBTest(DNSDistTest): newServer{address="127.0.0.1:%d"} kvs = newCDBKVStore('%s', %d) + kvs:reload() + kvs:lookup('does not exist, just testing that the lookup binding exists') + kvs:lookupSuffix(newDNSName('dummy')) -- KVS lookups follow -- does a lookup in the CDB database using the source IP as key, and store the result into the 'kvs-sourceip-result' tag diff --git a/regression-tests.dnsdist/test_DNSCrypt.py b/regression-tests.dnsdist/test_DNSCrypt.py index c5e150e8d9b2..9a9a91b23fe0 100644 --- a/regression-tests.dnsdist/test_DNSCrypt.py +++ b/regression-tests.dnsdist/test_DNSCrypt.py @@ -265,6 +265,56 @@ def testProtocolTCP(self): self.doDNSCryptQuery(client, query, response, True) +class TestDNSCryptYaml(TestDNSCrypt): + _config_template = """ + function checkDNSCryptUDP(dq) + if dq:getProtocol() ~= "DNSCrypt UDP" then + return DNSAction.Spoof, '1.2.3.4' + end + return DNSAction.None + end + + function checkDNSCryptTCP(dq) + if dq:getProtocol() ~= "DNSCrypt TCP" then + return DNSAction.Spoof, '1.2.3.4' + end + return DNSAction.None + end +""" + _yaml_config_template = """ +console: + key: "%s" + listen_address: "127.0.0.1:%d" + acl: + - 127.0.0.0/8 +binds: + - listen_address: "127.0.0.1:%d" + protocol: "DNSCrypt" + dnscrypt: + provider_name: "%s" + certificates: + - certificate: "DNSCryptResolver.cert" + key: "DNSCryptResolver.key" +backends: + - address: "127.0.0.1:%d" + protocol: Do53 +query_rules: + - selector: + type: "QName" + qname: "udp.protocols.dnscrypt.tests.powerdns.com." + action: + type: "Lua" + function_name: "checkDNSCryptUDP" + - selector: + type: "QName" + qname: "tcp.protocols.dnscrypt.tests.powerdns.com." + action: + type: "Lua" + function_name: "checkDNSCryptTCP" +""" + _config_params = [] + _yaml_config_params = ['_consoleKeyB64', '_consolePort', '_dnsDistPortDNSCrypt', '_providerName', '_testServerPort'] + class TestDNSCryptWithCache(DNSCryptTest): _config_params = ['_resolverCertificateSerial', '_resolverCertificateValidFrom', '_resolverCertificateValidUntil', '_dnsDistPortDNSCrypt', '_providerName', '_testServerPort'] diff --git a/regression-tests.dnsdist/test_DOH.py b/regression-tests.dnsdist/test_DOH.py index c9554846ea86..e9da4d46cfcc 100644 --- a/regression-tests.dnsdist/test_DOH.py +++ b/regression-tests.dnsdist/test_DOH.py @@ -761,6 +761,149 @@ class TestDoHNGHTTP2(DOHTests, DNSDistDOHTest): class TestDoHH2O(DOHTests, DNSDistDOHTest): _dohLibrary = 'h2o' +class TestDoHNGHTTP2Yaml(DOHTests, DNSDistDOHTest): + _dohLibrary = 'nghttp2' + _yaml_config_template = """--- +console: + key: "%s" + listen_address: "127.0.0.1:%d" + acl: + - 127.0.0.0/8 +backends: + - address: "127.0.0.1:%d" + protocol: "Do53" +binds: + - listen_address: "127.0.0.1:%d" + reuseport: true + protocol: "DoH" + tls: + certificates: + - certificate: "%s" + key: "%s" + doh: + provider: "%s" + paths: + - "/" + - "/coffee" + - "/PowerDNS" + - "/PowerDNS2" + - "/PowerDNS-999" + custom_response_headers: + - key: "access-control-allow-origin" + value: "*" + - key: "user-agent" + value: "derp" + - key: "UPPERCASE" + value: "VaLuE" + keep_incoming_headers: true + responses_map: + - expression: "^/coffee$" + status: 418 + content: 'C0FFEE' + headers: + - key: "FoO" + value: "bar" +query_rules: + - name: "Drop" + selector: + type: "QName" + qname: "drop.doh.tests.powerdns.com." + action: + type: "Drop" + - name: "Refused" + selector: + type: "QName" + qname: "refused.doh.tests.powerdns.com." + action: + type: "RCode" + rcode: 5 + - name: "Spoof" + selector: + type: "QName" + qname: "spoof.doh.tests.powerdns.com." + action: + type: "Spoof" + ips: + - "1.2.3.4" + - name: "HTTP header" + selector: + type: "HTTPHeader" + header: "X-PowerDNS" + expression: "^[a]{5}$" + action: + type: "Spoof" + ips: + - "2.3.4.5" + - name: "HTTP path" + selector: + type: "HTTPPath" + path: "/PowerDNS" + action: + type: "Spoof" + ips: + - "3.4.5.6" + - name: "HTTP regex" + selector: + type: "HTTPPathRegex" + expression: "^/PowerDNS-[0-9]" + action: + type: "Spoof" + ips: + - "6.7.8.9" + - name: "HTTP status" + selector: + type: "QName" + qname: "http-status-action.doh.tests.powerdns.com." + action: + type: "HTTPStatus" + status: 200 + body: "Plaintext answer" + content_type: "text/plain" + - name: "HTTP status redirect" + selector: + type: "QName" + qname: "http-status-action-redirect.doh.tests.powerdns.com." + action: + type: "HTTPStatus" + status: 307 + body: "https://doh.powerdns.org" + - name: "No backend" + selector: + type: "QName" + qname: "no-backend.doh.tests.powerdns.com." + action: + type: "Pool" + pool_name: "this-pool-has-no-backend" + - name: "HTTP Lua" + selector: + type: "QName" + qname: "http-lua.doh.tests.powerdns.com." + action: + type: "Lua" + function_name: "dohHandler" +""" + _yaml_config_params = ['_consoleKeyB64', '_consolePort', '_testServerPort', '_dohServerPort', '_serverCert', '_serverKey', '_dohLibrary'] + _config_template = """ + function dohHandler(dq) + if dq:getHTTPScheme() == 'https' and dq:getHTTPHost() == '%s:%d' and dq:getHTTPPath() == '/' and dq:getHTTPQueryString() == '' then + local foundct = false + for key,value in pairs(dq:getHTTPHeaders()) do + if key == 'content-type' and value == 'application/dns-message' then + foundct = true + break + end + end + if foundct then + dq:setHTTPResponse(200, 'It works!', 'text/plain') + dq.dh:setQR(true) + return DNSAction.HeaderModify + end + end + return DNSAction.None + end + """ + _config_params = ['_serverName', '_dohServerPort'] + class DOHSubPathsTests(object): _serverKey = 'server.key' _serverCert = 'server.chain' diff --git a/regression-tests.dnsdist/test_DOH3.py b/regression-tests.dnsdist/test_DOH3.py index 89d7e0c01695..7957fe860731 100644 --- a/regression-tests.dnsdist/test_DOH3.py +++ b/regression-tests.dnsdist/test_DOH3.py @@ -224,6 +224,65 @@ def testHTTPLuaBindings(self): self.assertIn(b'content-type', receivedHeaders) self.assertEqual(receivedHeaders[b'content-type'], b'text/plain') +class TestDOH3Yaml(QUICTests, DNSDistTest): + _serverKey = 'server.key' + _serverCert = 'server.chain' + _serverName = 'tls.tests.dnsdist.org' + _caCert = 'ca.pem' + _doqServerPort = pickAvailablePort() + _dohBaseURL = ("https://%s:%d/" % (_serverName, _doqServerPort)) + _config_template = "" + _config_params = [] + _yaml_config_template = """--- +backends: + - address: "127.0.0.1:%d" + protocol: "Do53" +binds: + - listen_address: "127.0.0.1:%d" + reuseport: true + protocol: "DoH3" + tls: + certificates: + - certificate: "%s" + key: "%s" +query_rules: + - name: "Drop" + selector: + type: "QName" + qname: "drop.doq.tests.powerdns.com." + action: + type: "Drop" + - name: "Refused" + selector: + type: "QName" + qname: "refused.doq.tests.powerdns.com." + action: + type: "RCode" + rcode: 5 + - name: "Spoof" + selector: + type: "QName" + qname: "spoof.doq.tests.powerdns.com." + action: + type: "Spoof" + ips: + - "1.2.3.4" + - name: "No backend" + selector: + type: "QName" + qname: "no-backend.doq.tests.powerdns.com." + action: + type: "Pool" + pool_name: "this-pool-has-no-backend" + """ + _yaml_config_params = ['_testServerPort', '_doqServerPort','_serverCert', '_serverKey'] + + def getQUICConnection(self): + return self.getDOQConnection(self._doqServerPort, self._caCert) + + def sendQUICQuery(self, query, response=None, useQueue=True, connection=None): + return self.sendDOH3Query(self._doqServerPort, self._dohBaseURL, query, response=response, caFile=self._caCert, useQueue=useQueue, serverName=self._serverName, connection=connection) + class TestDOH3ACL(QUICACLTests, DNSDistTest): _serverKey = 'server.key' _serverCert = 'server.chain' diff --git a/regression-tests.dnsdist/test_DOQ.py b/regression-tests.dnsdist/test_DOQ.py index 5a817747e04c..98d7711761c3 100644 --- a/regression-tests.dnsdist/test_DOQ.py +++ b/regression-tests.dnsdist/test_DOQ.py @@ -63,6 +63,64 @@ def getQUICConnection(self): def sendQUICQuery(self, query, response=None, useQueue=True, connection=None): return self.sendDOQQuery(self._doqServerPort, query, response=response, caFile=self._caCert, useQueue=useQueue, serverName=self._serverName, connection=connection) +class TestDOQYaml(QUICTests, DNSDistTest): + _serverKey = 'server.key' + _serverCert = 'server.chain' + _serverName = 'tls.tests.dnsdist.org' + _caCert = 'ca.pem' + _doqServerPort = pickAvailablePort() + _config_template = "" + _config_params = [] + _yaml_config_template = """--- +backends: + - address: "127.0.0.1:%d" + protocol: "Do53" +binds: + - listen_address: "127.0.0.1:%d" + reuseport: true + protocol: "DoQ" + tls: + certificates: + - certificate: "%s" + key: "%s" +query_rules: + - name: "Drop" + selector: + type: "QName" + qname: "drop.doq.tests.powerdns.com." + action: + type: "Drop" + - name: "Refused" + selector: + type: "QName" + qname: "refused.doq.tests.powerdns.com." + action: + type: "RCode" + rcode: 5 + - name: "Spoof" + selector: + type: "QName" + qname: "spoof.doq.tests.powerdns.com." + action: + type: "Spoof" + ips: + - "1.2.3.4" + - name: "No backend" + selector: + type: "QName" + qname: "no-backend.doq.tests.powerdns.com." + action: + type: "Pool" + pool_name: "this-pool-has-no-backend" + """ + _yaml_config_params = ['_testServerPort', '_doqServerPort','_serverCert', '_serverKey'] + + def getQUICConnection(self): + return self.getDOQConnection(self._doqServerPort, self._caCert) + + def sendQUICQuery(self, query, response=None, useQueue=True, connection=None): + return self.sendDOQQuery(self._doqServerPort, query, response=response, caFile=self._caCert, useQueue=useQueue, serverName=self._serverName, connection=connection) + class TestDOQWithCache(QUICWithCacheTests, DNSDistTest): _serverKey = 'server.key' _serverCert = 'server.chain' diff --git a/regression-tests.dnsdist/test_LMDB.py b/regression-tests.dnsdist/test_LMDB.py index 5e308d7d32bc..97c37b6c604c 100644 --- a/regression-tests.dnsdist/test_LMDB.py +++ b/regression-tests.dnsdist/test_LMDB.py @@ -17,6 +17,9 @@ class TestLMDB(DNSDistTest): newServer{address="127.0.0.1:%d"} kvs = newLMDBKVStore('%s', '%s') + kvs:reload() + kvs:lookup('does not exist, just testing that the lookup binding exists') + kvs:lookupSuffix(newDNSName('dummy')) -- KVS lookups follow -- if the qname is 'kvs-rule.lmdb.tests.powerdns.com.', does a lookup in the LMDB database using the qname as key, and spoof an answer if it matches @@ -195,6 +198,144 @@ def testLMDBKeyValueStoreLookupRule(self): self.assertTrue(receivedResponse) self.assertEqual(expectedResponse, receivedResponse) +class TestLMDBYaml(TestLMDB): + + _lmdbFileName = '/tmp/test-lmdb-db' + _lmdbDBName = 'db-name' + _config_template = "" + _config_params = [] + _yaml_config_template = """--- +backends: + - address: "127.0.0.1:%d" + protocol: Do53 +key_value_stores: + lmdb: + - name: "lmdb-kvs" + file_name: "%s" + database_name: "%s" + lookup_keys: + source_ip_keys: + - name: "lookup-source-ip" + qname_keys: + - name: "lookup-qname" + - name: "lookup-qname-plaintext" + wire_format: false + suffix_keys: + - name: "lookup-suffix" + tag_keys: + - name: "lookup-tag-qname-result" + tag: "kvs-qname-result" + +query_rules: + - name: "qname as key" + selector: + type: "And" + selectors: + - type: "QName" + qname: "kvs-rule.lmdb.tests.powerdns.com." + - type: "KeyValueStoreLookup" + kvs_name: "lmdb-kvs" + lookup_key_name: "lookup-qname-plaintext" + action: + type: "Spoof" + ips: + - "13.14.15.16" + - name: "source IP as key" + selector: + type: "All" + action: + type: "KeyValueStoreLookup" + kvs_name: "lmdb-kvs" + lookup_key_name: "lookup-source-ip" + destination_tag: "kvs-sourceip-result" + - name: "plaintext qname as key" + selector: + type: "All" + action: + type: "KeyValueStoreLookup" + kvs_name: "lmdb-kvs" + lookup_key_name: "lookup-qname-plaintext" + destination_tag: "kvs-plain-text-result" + - name: "plaintext qname tag check" + selector: + type: "Tag" + tag: "kvs-plain-text-result" + value: "this is the value of the plaintext tag" + action: + type: "Spoof" + ips: + - "9.10.11.12" + - name: "wire qname as key" + selector: + type: "All" + action: + type: "KeyValueStoreLookup" + kvs_name: "lmdb-kvs" + lookup_key_name: "lookup-qname" + destination_tag: "kvs-qname-result" + - name: "wire qname tag check" + selector: + type: "Tag" + tag: "kvs-qname-result" + value: "this is the value of the qname tag" + action: + type: "KeyValueStoreLookup" + kvs_name: "lmdb-kvs" + lookup_key_name: "lookup-tag-qname-result" + destination_tag: "kvs-tag-result" + - name: "source IP as key" + selector: + type: "All" + action: + type: "KeyValueStoreLookup" + kvs_name: "lmdb-kvs" + lookup_key_name: "lookup-source-ip" + destination_tag: "kvs-sourceip-result" + - name: "qname suffix as key" + selector: + type: "All" + action: + type: "KeyValueStoreLookup" + kvs_name: "lmdb-kvs" + lookup_key_name: "lookup-suffix" + destination_tag: "kvs-suffix-result" + - name: "tag check" + selector: + type: "Tag" + tag: "kvs-tag-result" + value: "this is the value of the second tag" + action: + type: "Spoof" + ips: + - "1.2.3.4" + - name: "suffix tag check" + selector: + type: "Tag" + tag: "kvs-suffix-result" + value: "this is the value of the suffix tag" + action: + type: "Spoof" + ips: + - "42.42.42.42" + - name: "source IP tag check" + selector: + type: "Tag" + tag: "kvs-sourceip-result" + value: "this is the value of the source address tag" + action: + type: "Spoof" + ips: + - "5.6.7.8" + - name: "otherwise" + selector: + type: "All" + action: + type: "Spoof" + ips: + - "9.9.9.9" + """ + _yaml_config_params = ['_testServerPort', '_lmdbFileName', '_lmdbDBName'] + class TestLMDBIPInRange(DNSDistTest): _lmdbFileName = '/tmp/test-lmdb-range-1-db' diff --git a/regression-tests.dnsdist/test_OutgoingDOH.py b/regression-tests.dnsdist/test_OutgoingDOH.py index 7b7c31541d4b..d5e8faffb56c 100644 --- a/regression-tests.dnsdist/test_OutgoingDOH.py +++ b/regression-tests.dnsdist/test_OutgoingDOH.py @@ -341,6 +341,80 @@ def startResponders(cls): cls._DOHResponder.daemon = True cls._DOHResponder.start() +class TestOutgoingDOHOpenSSLYaml(DNSDistTest, OutgoingDOHTests): + _tlsBackendPort = pickAvailablePort() + _tlsProvider = 'openssl' + _consoleKey = DNSDistTest.generateConsoleKey() + _consoleKeyB64 = base64.b64encode(_consoleKey).decode('ascii') + _config_params = [] + _config_template = "" + _yaml_config_template = """--- +console: + key: "%s" + listen_address: "127.0.0.1:%d" + acl: + - 127.0.0.0/8 +backends: + - address: "127.0.0.1:%d" + protocol: "DoH" + pools: + - "" + - "cache" + tls: + provider: "%s" + validate_certificate: true + ca_store: "ca.pem" + subject_name: "powerdns.com" + doh: + path: "/dns-query" + health_checks: + mode: "UP" +webserver: + listen_address: "127.0.0.1:%d" + password: "%s" + api_key: "%s" + acl: + - 127.0.0.0/8 +tuning: + tcp: + worker_threads: 1 +pools: + - name: "cache" + packet_cache: "pc" +packet_caches: + - name: "pc" + size: 100 +query_rules: + - name: "suffix to pool" + selector: + type: "QNameSuffix" + suffixes: + - "cached.outgoing-doh.test.powerdns.com." + action: + type: "Pool" + pool_name: "cache" +""" + _yaml_config_params = ['_consoleKeyB64', '_consolePort', '_tlsBackendPort', '_tlsProvider', '_webServerPort', '_webServerBasicAuthPasswordHashed', '_webServerAPIKeyHashed'] + + @staticmethod + def sniCallback(sslSocket, sni, sslContext): + assert(sni == 'powerdns.com') + return None + + @classmethod + def startResponders(cls): + tlsContext = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER) + tlsContext.set_alpn_protocols(["h2"]) + tlsContext.load_cert_chain('server.chain', 'server.key') + # requires Python 3.7+ + if hasattr(tlsContext, 'sni_callback'): + tlsContext.sni_callback = cls.sniCallback + + print("Launching DOH responder..") + cls._DOHResponder = threading.Thread(name='DOH Responder', target=cls.DOHResponder, args=[cls._tlsBackendPort, cls._toResponderQueue, cls._fromResponderQueue, False, False, None, tlsContext]) + cls._DOHResponder.daemon = True + cls._DOHResponder.start() + class TestOutgoingDOHOpenSSLWrongCertName(DNSDistTest, BrokenOutgoingDOHTests): _tlsBackendPort = pickAvailablePort() _config_params = ['_tlsBackendPort', '_webServerPort', '_webServerBasicAuthPasswordHashed', '_webServerAPIKeyHashed'] diff --git a/regression-tests.dnsdist/test_OutgoingTLS.py b/regression-tests.dnsdist/test_OutgoingTLS.py index 534c26789e65..d701b2712fdc 100644 --- a/regression-tests.dnsdist/test_OutgoingTLS.py +++ b/regression-tests.dnsdist/test_OutgoingTLS.py @@ -16,7 +16,6 @@ class OutgoingTLSTests(object): _webServerAPIKey = 'apisecret' _webServerBasicAuthPasswordHashed = '$scrypt$ln=10,p=1,r=8$6DKLnvUYEeXWh3JNOd3iwg==$kSrhdHaRbZ7R74q3lGBqO1xetgxRxhmWzYJ2Qvfm7JM=' _webServerAPIKeyHashed = '$scrypt$ln=10,p=1,r=8$9v8JxDfzQVyTpBkTbkUqYg==$bDQzAOHeK1G9UvTPypNhrX48w974ZXbFPtRKS34+aso=' - _verboseMode = True def checkOnlyTLSResponderHit(self, numberOfTLSQueries=1): self.assertNotIn('UDP Responder', self._responsesCounter) @@ -171,6 +170,49 @@ def startResponders(cls): cls._TLSResponder.daemon = True cls._TLSResponder.start() +class TestOutgoingTLSOpenSSLYaml(DNSDistTest, OutgoingTLSTests): + _tlsBackendPort = pickAvailablePort() + _config_params = [] + _config_template = "" + _yaml_config_template = """--- +backends: + - address: "127.0.0.1:%d" + protocol: "DoT" + tls: + provider: "openssl" + validate_certificate: true + ca_store: "ca.pem" + subject_name: "powerdns.com" +webserver: + listen_address: "127.0.0.1:%d" + password: "%s" + api_key: "%s" + acl: + - 127.0.0.0/8 +tuning: + tcp: + worker_threads: 1 + """ + _yaml_config_params = ['_tlsBackendPort', '_webServerPort', '_webServerBasicAuthPasswordHashed', '_webServerAPIKeyHashed'] + + @staticmethod + def sniCallback(sslSocket, sni, sslContext): + assert(sni == 'powerdns.com') + return None + + @classmethod + def startResponders(cls): + tlsContext = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER) + tlsContext.load_cert_chain('server.chain', 'server.key') + # requires Python 3.7+ + if hasattr(tlsContext, 'sni_callback'): + tlsContext.sni_callback = cls.sniCallback + + print("Launching TLS responder..") + cls._TLSResponder = threading.Thread(name='TLS Responder', target=cls.TCPResponder, args=[cls._tlsBackendPort, cls._toResponderQueue, cls._fromResponderQueue, False, False, None, tlsContext]) + cls._TLSResponder.daemon = True + cls._TLSResponder.start() + class TestOutgoingTLSGnuTLS(DNSDistTest, OutgoingTLSTests): _tlsBackendPort = pickAvailablePort() _config_params = ['_tlsBackendPort', '_webServerPort', '_webServerBasicAuthPasswordHashed', '_webServerAPIKeyHashed'] diff --git a/regression-tests.dnsdist/test_Protobuf.py b/regression-tests.dnsdist/test_Protobuf.py index a7d119512036..e77d747b81c9 100644 --- a/regression-tests.dnsdist/test_Protobuf.py +++ b/regression-tests.dnsdist/test_Protobuf.py @@ -1001,3 +1001,77 @@ def testProtobufAXFR(self): self.assertEqual(socket.inet_ntop(socket.AF_INET6, rr.rdata), '2001:db8::1') self.assertEqual(count, len(responses)) + +class TestYamlProtobuf(DNSDistProtobufTest): + + _yaml_config_template = """--- +binds: + - listen_address: "127.0.0.1:%d" + reuseport: true + protocol: Do53 + threads: 2 + +backends: + - address: "127.0.0.1:%d" + protocol: Do53 + +remote_logging: + protobuf_loggers: + - name: "my-logger" + address: "127.0.0.1:%d" + timeout: 1 + +query_rules: + - name: "my-rule" + selector: + type: "All" + action: + type: "RemoteLog" + logger_name: "my-logger" + server_id: "%s" + export_tags: + - "tag-1" + - "tag-2" +""" + _dnsDistPort = pickAvailablePort() + _testServerPort = pickAvailablePort() + _yaml_config_params = ['_dnsDistPort', '_testServerPort', '_protobufServerPort', '_protobufServerID'] + _config_params = [] + + def testProtobuf(self): + """ + Yaml: Remote logging via protobuf + """ + name = 'remote-logging.protobuf.yaml.test.powerdns.com.' + query = dns.message.make_query(name, 'A', 'IN') + query.flags &= ~dns.flags.RD + response = dns.message.make_response(query) + rrset = dns.rrset.from_text(name, + 60, + dns.rdataclass.IN, + dns.rdatatype.A, + '127.0.0.1') + + response.answer.append(rrset) + + for method in ("sendUDPQuery", "sendTCPQuery"): + sender = getattr(self, method) + (receivedQuery, receivedResponse) = sender(query, response=response) + receivedQuery.id = query.id + self.assertEqual(receivedQuery, query) + self.assertEqual(receivedResponse, response) + + + if self._protobufQueue.empty(): + # let the protobuf messages the time to get there + time.sleep(1) + # check the protobuf message corresponding to the UDP query + msg = self.getFirstProtobufMessage() + self.checkProtobufQuery(msg, dnsmessage_pb2.PBDNSMessage.UDP, query, dns.rdataclass.IN, dns.rdatatype.A, name) + + if self._protobufQueue.empty(): + # let the protobuf messages the time to get there + time.sleep(1) + # TCP query + msg = self.getFirstProtobufMessage() + self.checkProtobufQuery(msg, dnsmessage_pb2.PBDNSMessage.TCP, query, dns.rdataclass.IN, dns.rdatatype.A, name) diff --git a/regression-tests.dnsdist/test_TLS.py b/regression-tests.dnsdist/test_TLS.py index c54f3dee410e..718b3c90c595 100644 --- a/regression-tests.dnsdist/test_TLS.py +++ b/regression-tests.dnsdist/test_TLS.py @@ -325,6 +325,58 @@ def setUpClass(cls): def testProvider(self): self.assertEqual(self.getTLSProvider(), "gnutls") +class TestOpenSSLYaml(DNSDistTest, TLSTests): + + _extraStartupSleep = 1 + _consoleKey = DNSDistTest.generateConsoleKey() + _consoleKeyB64 = base64.b64encode(_consoleKey).decode('ascii') + _serverKey = 'server-tls.key' + _serverCert = 'server-tls.chain' + _serverName = 'tls.tests.dnsdist.org' + _caCert = 'ca.pem' + _tlsServerPort = pickAvailablePort() + _config_template = "" + _config_params = [] + _yaml_config_template = """--- +console: + key: "%s" + listen_address: "127.0.0.1:%d" + acl: + - 127.0.0.0/8 +backends: + - address: "127.0.0.1:%d" + protocol: "Do53" +binds: + - listen_address: "127.0.0.1:%d" + reuseport: true + protocol: "DoT" + tls: + certificates: + - certificate: "%s" + key: "%s" + provider: "openssl" +query_rules: + - name: "SNI" + selector: + type: "SNI" + server_name: "powerdns.com" + action: + type: "Spoof" + ips: + - "1.2.3.4" + """ + _yaml_config_params = ['_consoleKeyB64', '_consolePort', '_testServerPort', '_tlsServerPort', '_serverCert', '_serverKey'] + + @classmethod + def setUpClass(cls): + cls.generateNewCertificateAndKey('server-tls') + cls.startResponders() + cls.startDNSDist() + cls.setUpSockets() + + def testProvider(self): + self.assertEqual(self.getTLSProvider(), "openssl") + class TestDOTWithCache(DNSDistTest): _serverKey = 'server.key' _serverCert = 'server.chain' diff --git a/regression-tests.dnsdist/test_Yaml.py b/regression-tests.dnsdist/test_Yaml.py new file mode 100644 index 000000000000..597a11b21086 --- /dev/null +++ b/regression-tests.dnsdist/test_Yaml.py @@ -0,0 +1,196 @@ +#!/usr/bin/env python +import base64 +import dns +from dnsdisttests import DNSDistTest, pickAvailablePort + +class TestYaml(DNSDistTest): + + _yaml_config_template = """--- +webserver: + listen_address: "127.0.0.1:%d" + acl: + - 127.0.0.0/8 + +console: + listen_address: "127.0.0.1:%d" + key: "%s" + acl: + - 127.0.0.0/8 + +edns_client_subnet: + override_existing: true + source_prefix_v4: 32 + source_prefix_v6: 48 + +acl: + - 127.0.0.1/32 + - ::1/128 + +ring_buffers: + size: 2000 + shards: 2 + +binds: + - listen_address: "127.0.0.1:%d" + reuseport: true + protocol: Do53 + threads: 2 + +backends: + - address: "127.0.0.1:%d" + protocol: Do53 + pools: + - "tcp-pool" + - "inline" + +pools: + - name: "tcp-pool" + policy: "leastoutstanding" + +selectors: + - type: "TCP" + name: "is-tcp" + tcp: true + +query_rules: + - name: "route inline-yaml to inline pool" + selector: + type: "QNameSet" + qnames: + - "inline-lua.yaml.test.powerdns.com." + action: + type: "Pool" + pool_name: "inline" + stop_processing: true + - name: "my-rule" + selector: + type: "And" + selectors: + - type: "ByName" + selector_name: "is-tcp" + - type: "Not" + selector: + type: "RD" + action: + type: "Pool" + pool_name: "tcp-pool" + +response_rules: + - name: "inline RD=0 TCP gets cleared" + selector: + type: "And" + selectors: + - type: "ByName" + selector_name: "is-tcp" + - type: "QNameSet" + qnames: + - "inline-lua.yaml.test.powerdns.com." + - type: "Lua" + name: "Match responses on RD=0 (inline)" + function_code: | + return function(dr) + local rd = dr.dh:getRD() + if not rd then + return true + end + return false + end + action: + type: "ClearRecordTypes" + types: + - 1 + - name: "inline RD=0 UDP gets truncated" + selector: + type: "And" + selectors: + - type: "QNameSet" + qnames: + - "inline-lua.yaml.test.powerdns.com." + - type: "Lua" + name: "Match responses on RD=0 (file)" + function_file: "yaml-config-files/yaml-inline-lua-file.yml" + action: + type: "TC" +""" + _webServerPort = pickAvailablePort() + _dnsDistPort = pickAvailablePort() + _consoleKey = DNSDistTest.generateConsoleKey() + _consoleKeyB64 = base64.b64encode(_consoleKey).decode('ascii') + _consolePort = pickAvailablePort() + _testServerPort = pickAvailablePort() + _yaml_config_params = ['_webServerPort', '_consolePort', '_consoleKeyB64', '_dnsDistPort', '_testServerPort'] + _config_params = [] + + def testForwarded(self): + """ + Yaml: Forwarded query + """ + name = 'forwarded.yaml.test.powerdns.com.' + query = dns.message.make_query(name, 'A', 'IN') + query.flags &= ~dns.flags.RD + # UDP query should be dropped + (_, receivedResponse) = self.sendUDPQuery(query, response=None, useQueue=False) + self.assertEqual(receivedResponse, None) + # TCP query should be forwarded + response = dns.message.make_response(query) + rrset = dns.rrset.from_text(name, + 60, + dns.rdataclass.IN, + dns.rdatatype.A, + '127.0.0.1') + + response.answer.append(rrset) + (receivedQuery, receivedResponse) = self.sendTCPQuery(query, response=response) + receivedQuery.id = query.id + self.assertEqual(receivedQuery, query) + self.assertEqual(receivedResponse, response) + + def testInlineLua(self): + """ + Yaml: Inline Lua + """ + name = 'inline-lua.yaml.test.powerdns.com.' + + query = dns.message.make_query(name, 'A', 'IN') + query.flags &= ~dns.flags.RD + response = dns.message.make_response(query) + rrset = dns.rrset.from_text(name, + 60, + dns.rdataclass.IN, + dns.rdatatype.A, + '127.0.0.1') + + response.answer.append(rrset) + truncatedResponse = dns.message.make_response(query) + truncatedResponse.flags |= dns.flags.TC + clearedResponse = dns.message.make_response(query) + + # UDP response without RD should be truncated + (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response=response) + receivedQuery.id = query.id + self.assertEqual(receivedQuery, query) + self.assertEqual(receivedResponse, truncatedResponse) + + # TCP response should have its A records cleared + (receivedQuery, receivedResponse) = self.sendTCPQuery(query, response=response) + receivedQuery.id = query.id + self.assertEqual(receivedQuery, query) + self.assertEqual(receivedResponse, clearedResponse) + + # response with RD should be forwarded + query = dns.message.make_query(name, 'A', 'IN') + query.flags |= dns.flags.RD + response = dns.message.make_response(query) + rrset = dns.rrset.from_text(name, + 60, + dns.rdataclass.IN, + dns.rdatatype.A, + '127.0.0.1') + + response.answer.append(rrset) + for method in ["sendUDPQuery", "sendTCPQuery"]: + sender = getattr(self, method) + (receivedQuery, receivedResponse) = sender(query, response=response) + receivedQuery.id = query.id + self.assertEqual(receivedQuery, query) + self.assertEqual(receivedResponse, response) diff --git a/regression-tests.dnsdist/yaml-config-files/yaml-inline-lua-file.yml b/regression-tests.dnsdist/yaml-config-files/yaml-inline-lua-file.yml new file mode 100644 index 000000000000..4d6b877ceea4 --- /dev/null +++ b/regression-tests.dnsdist/yaml-config-files/yaml-inline-lua-file.yml @@ -0,0 +1,7 @@ +return function(dr) + local rd = dr.dh:getRD() + if not rd then + return true + end + return false +end diff --git a/tasks.py b/tasks.py index b8e8f9535c36..f40080069c4c 100644 --- a/tasks.py +++ b/tasks.py @@ -171,6 +171,11 @@ def apt_fresh(c): c.sudo('apt-get update') c.sudo('apt-get -y --allow-downgrades dist-upgrade') +@task +def install_lld_linker_if_needed(c): + if is_compiler_clang(): + c.sudo(f'apt-get -y --no-install-recommends install lld-{clang_version}') + @task def install_clang(c): """ @@ -453,12 +458,14 @@ def get_cxxflags(): ]) -def get_base_configure_cmd(additional_c_flags='', additional_cxx_flags='', enable_systemd=True, enable_sodium=True): +def get_base_configure_cmd(additional_c_flags='', additional_cxx_flags='', additional_ld_flags='', enable_systemd=True, enable_sodium=True): cflags = " ".join([get_cflags(), additional_c_flags]) cxxflags = " ".join([get_cxxflags(), additional_cxx_flags]) + ldflags = additional_ld_flags return " ".join([ f'CFLAGS="{cflags}"', f'CXXFLAGS="{cxxflags}"', + f'LDFLAGS="{ldflags}"', './configure', f"CC='{get_c_compiler()}'", f"CXX='{get_cxx_compiler()}'", @@ -657,6 +664,9 @@ def ci_rec_configure(c, features, build_dir=None, meson=False): @task def ci_dnsdist_configure(c, features): additional_flags = '' + additional_ld_flags = '' + if is_compiler_clang(): + additional_ld_flags += '-fuse-ld=lld ' if features == 'full': features_set = '--enable-dnstap \ --enable-dnscrypt \ @@ -665,6 +675,7 @@ def ci_dnsdist_configure(c, features): --enable-dns-over-quic \ --enable-dns-over-http3 \ --enable-systemd \ + --enable-yaml \ --prefix=/opt/dnsdist \ --with-gnutls \ --with-h2o \ @@ -725,7 +736,7 @@ def ci_dnsdist_configure(c, features): tools = f'''AR=llvm-ar-{clang_version} RANLIB=llvm-ranlib-{clang_version}''' if is_compiler_clang() else '' configure_cmd = " ".join([ tools, - get_base_configure_cmd(additional_c_flags='', additional_cxx_flags=additional_flags, enable_systemd=False, enable_sodium=False), + get_base_configure_cmd(additional_c_flags='', additional_cxx_flags=additional_flags, additional_ld_flags=additional_ld_flags, enable_systemd=False, enable_sodium=False), features_set, unittests, fuzztargets,