]> git.ipfire.org Git - thirdparty/pdns.git/commitdiff
Merge pull request #12819 from rgacogne/ddist-reuseaddr-udp
authorRemi Gacogne <remi.gacogne@powerdns.com>
Thu, 18 May 2023 11:29:47 +0000 (13:29 +0200)
committerGitHub <noreply@github.com>
Thu, 18 May 2023 11:29:47 +0000 (13:29 +0200)
dnsdist: Stop setting SO_REUSEADDR on outgoing UDP client sockets

21 files changed:
.clang-tidy.full
.github/workflows/build-and-test-all.yml
.github/workflows/fuzz.yml
.github/workflows/misc-dailies.yml
.github/workflows/secpoll.yml
Dockerfile-cifuzz [new file with mode: 0644]
build-scripts/gh-actions-setup-inv
docs/settings.rst
pdns/recursordist/pdns_recursor.cc
pdns/recursordist/rec-main.cc
pdns/recursordist/syncres.cc
pdns/recursordist/syncres.hh
pdns/recursordist/test-syncres_cc1.cc
regression-tests.auth-py/kerberos-client/krb5.conf
regression-tests.dnsdist/dnsdisttests.py
regression-tests.dnsdist/test_Advanced.py
regression-tests.nobackend/counters/command
regression-tests.nobackend/counters/expected_result.noipv6 [new file with mode: 0644]
regression-tests.nobackend/distributor/command
regression-tests.recursor-dnssec/test_SimpleDoT.py
tasks.py

index 3d678e379e2513d8eec0b1be46f52239428f6438..bf8c8c8eca186ed96db3919a69448f9e8feeb150 100644 (file)
@@ -1,5 +1,5 @@
 ---
-Checks:          'clang-diagnostic-*,clang-analyzer-*,cppcoreguidelines-*,bugprone-*,concurrency-*,modernize-*,performance-*,portability-*,readability-*,-modernize-use-trailing-return-type,-readability-magic-numbers,-cppcoreguidelines-avoid-magic-numbers,-cppcoreguidelines-pro-type-union-access,-cppcoreguidelines-avoid-non-const-global-variables'
+Checks:          'clang-diagnostic-*,clang-analyzer-*,cppcoreguidelines-*,bugprone-*,concurrency-*,modernize-*,performance-*,portability-*,readability-*,-modernize-use-trailing-return-type,-readability-magic-numbers,-cppcoreguidelines-avoid-magic-numbers,-cppcoreguidelines-pro-type-union-access,-cppcoreguidelines-avoid-non-const-global-variables,-cppcoreguidelines-pro-type-vararg'
 WarningsAsErrors: ''
 HeaderFilterRegex: ''
 AnalyzeTemporaryDtors: false
@@ -405,7 +405,7 @@ CheckOptions:
   - key:             modernize-use-noexcept.UseNoexceptFalse
     value:           'true'
   - key:             readability-function-cognitive-complexity.Threshold
-    value:           '50'
+    value:           '75'
   - key:             cppcoreguidelines-non-private-member-variables-in-classes.IgnoreClassesWithAllMemberVariablesBeingPublic
     value:           'true'
   - key:             bugprone-argument-comment.IgnoreSingleArgument
index 94e8db2e0993214acf3acd53a117ef92e4b6ebf7..3e0c3fe1be4eb5db49bfc1ca1682975e5d55185f 100644 (file)
@@ -10,21 +10,28 @@ on:
 permissions: # least privileges, see https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#permissions
   contents: read
 
+env:
+  CLANG_VERSION: '13'
+  # github.workspace variable points to the Runner home folder. Container home folder defined below.
+  REPO_HOME: '/__w/pdns/pdns'
+
 jobs:
   build-auth:
     name: build auth
     if: ${{ !github.event.schedule || vars.SCHEDULED_JOBS_BUILD_AND_TEST_ALL }}
     runs-on: ubuntu-20.04
-    env:
-      ASAN_OPTIONS: detect_leaks=0
-      FUZZING_TARGETS: yes
-      SANITIZERS: asan+ubsan
-      UBSAN_OPTIONS: "print_stacktrace=1:halt_on_error=1:suppressions=${{ github.workspace }}/build-scripts/UBSan.supp"
-      UNIT_TESTS: yes
+    container:
+      image: ghcr.io/powerdns/base-pdns-ci-image/debian-11-pdns-base:master
+      env:
+        ASAN_OPTIONS: detect_leaks=0
+        FUZZING_TARGETS: yes
+        SANITIZERS: asan+ubsan
+        UBSAN_OPTIONS: "print_stacktrace=1:halt_on_error=1:suppressions=${{ env.REPO_HOME }}/build-scripts/UBSan.supp"
+        UNIT_TESTS: yes
+        PDNS_TEST_NO_IPV6: yes
     outputs:
       clang-tidy-failed: ${{ steps.clang-tidy-annotations.outputs.failed }}
     steps:
-      - uses: PowerDNS/pdns/set-ubuntu-mirror@meta
       - uses: actions/checkout@v3
         with:
           fetch-depth: 5
@@ -34,23 +41,20 @@ jobs:
         run: |
           echo "stamp=$(/bin/date +%s)" >> "$GITHUB_OUTPUT"
         shell: bash
+      - run: mkdir -p ~/.ccache
       - name: let GitHub cache our ccache data
         uses: actions/cache@v3
         with:
           path: ~/.ccache
           key: auth-ccache-${{ steps.get-stamp.outputs.stamp }}
           restore-keys: auth-ccache-
-      - run: build-scripts/gh-actions-setup-inv  # this runs apt update+upgrade
-      - run: inv install-clang
-      - run: inv install-clang-tidy-tools
-      - run: inv install-auth-build-deps
       - run: inv ci-autoconf
       - run: inv ci-auth-configure
       - run: inv ci-auth-make-bear  # This runs under pdns/
       - run: ln -s .clang-tidy.full .clang-tidy
       - name: Run clang-tidy
         working-directory: pdns
-        run: git diff -U0 HEAD^..HEAD | python3 ../.github/scripts/git-filter.py | python3 /usr/bin/clang-tidy-diff-12.py -clang-tidy-binary /usr/bin/clang-tidy-12 -extra-arg=-ferror-limit=0 -p2 -export-fixes clang-tidy-auth.yml
+        run: git diff -U0 HEAD^..HEAD | python3 ../.github/scripts/git-filter.py | python3 /usr/bin/clang-tidy-diff-${CLANG_VERSION}.py -clang-tidy-binary /usr/bin/clang-tidy-${CLANG_VERSION} -extra-arg=-ferror-limit=0 -p2 -export-fixes clang-tidy-auth.yml
       - name: Print clang-tidy fixes YAML
         working-directory: pdns
         shell: bash
@@ -86,18 +90,19 @@ jobs:
     strategy:
       matrix:
         sanitizers: [ubsan+asan, tsan]
-    env:
-      ASAN_OPTIONS: detect_leaks=0
-      SANITIZERS: ${{ matrix.sanitizers }}
-      UBSAN_OPTIONS: "print_stacktrace=1:halt_on_error=1:suppressions=${{ github.workspace }}/build-scripts/UBSan.supp"
-      UNIT_TESTS: yes
+    container:
+      image: ghcr.io/powerdns/base-pdns-ci-image/debian-11-pdns-base:master
+      env:
+        ASAN_OPTIONS: detect_leaks=0
+        SANITIZERS: ${{ matrix.sanitizers }}
+        UBSAN_OPTIONS: "print_stacktrace=1:halt_on_error=1:suppressions=${{ env.REPO_HOME }}/build-scripts/UBSan.supp"
+        UNIT_TESTS: yes
     defaults:
       run:
         working-directory: ./pdns/recursordist/
     outputs:
       clang-tidy-failed: ${{ steps.clang-tidy-annotations.outputs.failed }}
     steps:
-      - uses: PowerDNS/pdns/set-ubuntu-mirror@meta
       - uses: actions/checkout@v3
         with:
           fetch-depth: 5
@@ -107,23 +112,19 @@ jobs:
         run: |
           echo "stamp=$(/bin/date +%s)" >> "$GITHUB_OUTPUT"
         shell: bash
+      - run: mkdir -p ~/.ccache
       - name: let GitHub cache our ccache data
         uses: actions/cache@v3
         with:
           path: ~/.ccache
           key: recursor-${{ matrix.sanitizers }}-ccache-${{ steps.get-stamp.outputs.stamp }}
           restore-keys: recursor-${{ matrix.sanitizers }}-ccache-
-      - run: ../../build-scripts/gh-actions-setup-inv  # this runs apt update+upgrade
-      - run: inv apt-fresh
-      - run: inv install-clang
-      - run: inv install-clang-tidy-tools
-      - run: inv install-rec-build-deps
       - run: inv ci-autoconf
       - run: inv ci-rec-configure
       - run: inv ci-rec-make-bear
       - run: ln -s ../../.clang-tidy.full .clang-tidy
       - name: Run clang-tidy
-        run: git diff -U0 HEAD^..HEAD | python3 ../../.github/scripts/git-filter.py | python3 /usr/bin/clang-tidy-diff-12.py -clang-tidy-binary /usr/bin/clang-tidy-12 -extra-arg=-ferror-limit=0 -p3 -export-fixes clang-tidy-rec.yml
+        run: git diff -U0 HEAD^..HEAD | python3 ../../.github/scripts/git-filter.py | python3 /usr/bin/clang-tidy-diff-${CLANG_VERSION}.py -clang-tidy-binary /usr/bin/clang-tidy-${CLANG_VERSION} -extra-arg=-ferror-limit=0 -p3 -export-fixes clang-tidy-rec.yml
       - name: Print clang-tidy fixes YAML
         shell: bash
         run: |
@@ -160,18 +161,19 @@ jobs:
         exclude:
           - sanitizers: tsan
             features: least
-    env:
-      ASAN_OPTIONS: detect_leaks=0
-      SANITIZERS: ${{ matrix.sanitizers }}
-      UBSAN_OPTIONS: "print_stacktrace=1:halt_on_error=1:suppressions=${{ github.workspace }}/build-scripts/UBSan.supp"
-      UNIT_TESTS: yes
+    container:
+      image: ghcr.io/powerdns/base-pdns-ci-image/debian-11-pdns-base:master
+      env:
+        ASAN_OPTIONS: detect_leaks=0
+        SANITIZERS: ${{ matrix.sanitizers }}
+        UBSAN_OPTIONS: "print_stacktrace=1:halt_on_error=1:suppressions=${{ env.REPO_HOME }}/build-scripts/UBSan.supp"
+        UNIT_TESTS: yes
     defaults:
       run:
         working-directory: ./pdns/dnsdistdist/
     outputs:
       clang-tidy-failed: ${{ steps.clang-tidy-annotations.outputs.failed }}
     steps:
-      - uses: PowerDNS/pdns/set-ubuntu-mirror@meta
       - uses: actions/checkout@v3
         with:
           fetch-depth: 5
@@ -181,23 +183,19 @@ jobs:
         run: |
           echo "stamp=$(/bin/date +%s)" >> "$GITHUB_OUTPUT"
         shell: bash
+      - run: mkdir -p ~/.ccache
       - name: let GitHub cache our ccache data
         uses: actions/cache@v3
         with:
           path: ~/.ccache
           key: dnsdist-${{ matrix.features }}-${{ matrix.sanitizers }}-ccache-${{ steps.get-stamp.outputs.stamp }}
           restore-keys: dnsdist-${{ matrix.features }}-${{ matrix.sanitizers }}-ccache-
-      - run: ../../build-scripts/gh-actions-setup-inv  # this runs apt update+upgrade
-      - run: inv apt-fresh
-      - run: inv install-clang
-      - run: inv install-clang-tidy-tools
-      - run: inv install-dnsdist-build-deps
       - run: inv ci-autoconf
       - run: inv ci-dnsdist-configure ${{ matrix.features }}
       - run: inv ci-dnsdist-make-bear
       - run: ln -s ../../.clang-tidy.full .clang-tidy
       - name: Run clang-tidy
-        run: git diff -U0 HEAD^..HEAD | python3 ../../.github/scripts/git-filter.py | python3 /usr/bin/clang-tidy-diff-12.py -clang-tidy-binary /usr/bin/clang-tidy-12 -extra-arg=-ferror-limit=0 -p3 -export-fixes clang-tidy-dnsdist.yml
+        run: git diff -U0 HEAD^..HEAD | python3 ../../.github/scripts/git-filter.py | python3 /usr/bin/clang-tidy-diff-${CLANG_VERSION}.py -clang-tidy-binary /usr/bin/clang-tidy-${CLANG_VERSION} -extra-arg=-ferror-limit=0 -p3 -export-fixes clang-tidy-dnsdist.yml
       - name: Print clang-tidy fixes YAML
         shell: bash
         run: |
@@ -226,10 +224,13 @@ jobs:
   test-auth-api:
     needs: build-auth
     runs-on: ubuntu-20.04
-    env:
-      UBSAN_OPTIONS: "print_stacktrace=1:halt_on_error=1:suppressions=${{ github.workspace }}/build-scripts/UBSan.supp"
-      ASAN_OPTIONS: detect_leaks=0
-      TSAN_OPTIONS: "halt_on_error=1:suppressions=${{ github.workspace }}/pdns/dnsdistdist/dnsdist-tsan.supp"
+    container:
+      image: ghcr.io/powerdns/base-pdns-ci-image/debian-11-pdns-base:master
+      env:
+        UBSAN_OPTIONS: "print_stacktrace=1:halt_on_error=1:suppressions=${{ env.REPO_HOME }}/build-scripts/UBSan.supp"
+        ASAN_OPTIONS: detect_leaks=0
+        TSAN_OPTIONS: "halt_on_error=1:suppressions=${{ env.REPO_HOME }}/pdns/dnsdistdist/dnsdist-tsan.supp"
+        AUTH_BACKEND_IP_ADDR: "172.17.0.1"
     strategy:
       matrix:
         include:
@@ -256,7 +257,6 @@ jobs:
         options: >-
           --restart always
     steps:
-      - uses: PowerDNS/pdns/set-ubuntu-mirror@meta
       - uses: actions/checkout@v3
         with:
           fetch-depth: 5
@@ -266,9 +266,7 @@ jobs:
         with:
           name: pdns-auth
           path: /opt/pdns-auth
-      # - name: Setup upterm session
-      #   uses: lhotari/action-upterm@v1
-      - run: build-scripts/gh-actions-setup-inv  # this runs apt update+upgrade
+      - run: inv apt-fresh
       - run: inv install-clang-runtime
       - run: inv install-auth-test-deps -b ${{ matrix.backend }}
       - run: inv test-api auth -b ${{ matrix.backend }}
@@ -276,10 +274,15 @@ jobs:
   test-auth-backend:
     needs: build-auth
     runs-on: ubuntu-20.04
-    env:
-      UBSAN_OPTIONS: "print_stacktrace=1:halt_on_error=1:suppressions=${{ github.workspace }}/build-scripts/UBSan.supp"
-      ASAN_OPTIONS: detect_leaks=0
-      LDAPHOST: ldap://ldapserver/
+    container:
+      image: ghcr.io/powerdns/base-pdns-ci-image/debian-11-pdns-base:master
+      env:
+        UBSAN_OPTIONS: "print_stacktrace=1:halt_on_error=1:suppressions=${{ env.REPO_HOME }}/build-scripts/UBSan.supp"
+        ASAN_OPTIONS: detect_leaks=0
+        LDAPHOST: ldap://ldapserver/
+        ODBCINI: /github/home/.odbc.ini
+        AUTH_BACKEND_IP_ADDR: "172.17.0.1"
+        SKIP_IPV6_TESTS: yes
     strategy:
       matrix:
         include:
@@ -366,7 +369,6 @@ jobs:
         options: >-
           --restart always
     steps:
-      - uses: PowerDNS/pdns/set-ubuntu-mirror@meta
       - uses: actions/checkout@v3
         with:
           fetch-depth: 5
@@ -376,10 +378,7 @@ jobs:
         with:
           name: pdns-auth
           path: /opt/pdns-auth
-      # - name: Setup upterm session
-      #   uses: lhotari/action-upterm@v1
       # FIXME: install recursor for backends that have ALIAS
-      - run: build-scripts/gh-actions-setup-inv  # this runs apt update+upgrade
       - run: inv install-clang-runtime
       - run: inv install-auth-test-deps -b ${{ matrix.backend }}
       - run: inv test-auth-backend -b ${{ matrix.backend }}
@@ -387,11 +386,12 @@ jobs:
   test-ixfrdist:
     needs: build-auth
     runs-on: ubuntu-20.04
-    env:
-      UBSAN_OPTIONS: "print_stacktrace=1:halt_on_error=1:suppressions=${{ github.workspace }}/build-scripts/UBSan.supp"
-      ASAN_OPTIONS: detect_leaks=0
+    container:
+      image: ghcr.io/powerdns/base-pdns-ci-image/debian-11-pdns-base:master
+      env:
+        UBSAN_OPTIONS: "print_stacktrace=1:halt_on_error=1:suppressions=${{ env.REPO_HOME }}/build-scripts/UBSan.supp"
+        ASAN_OPTIONS: detect_leaks=0
     steps:
-      - uses: PowerDNS/pdns/set-ubuntu-mirror@meta
       - uses: actions/checkout@v3
         with:
           fetch-depth: 5
@@ -401,7 +401,6 @@ jobs:
         with:
           name: pdns-auth
           path: /opt/pdns-auth
-      - run: build-scripts/gh-actions-setup-inv  # this runs apt update+upgrade
       - run: inv install-clang-runtime
       - run: inv install-auth-test-deps
       - run: inv test-ixfrdist
@@ -412,12 +411,16 @@ jobs:
     strategy:
       matrix:
         sanitizers: [ubsan+asan, tsan]
-    env:
-      UBSAN_OPTIONS: "print_stacktrace=1:halt_on_error=1:suppressions=${{ github.workspace }}/build-scripts/UBSan.supp"
-      ASAN_OPTIONS: detect_leaks=0
-      TSAN_OPTIONS: "halt_on_error=1:suppressions=${{ github.workspace }}/pdns/recursordist/recursor-tsan.supp"
+        dist_name: [debian]
+        dist_release_name: [bullseye]
+        pdns_repo_version: ['45']
+    container:
+      image: ghcr.io/powerdns/base-pdns-ci-image/debian-11-pdns-base:master
+      env:
+        UBSAN_OPTIONS: "print_stacktrace=1:halt_on_error=1:suppressions=${{ env.REPO_HOME }}/build-scripts/UBSan.supp"
+        ASAN_OPTIONS: detect_leaks=0
+        TSAN_OPTIONS: "halt_on_error=1:suppressions=${{ env.REPO_HOME }}/pdns/recursordist/recursor-tsan.supp"
     steps:
-      - uses: PowerDNS/pdns/set-ubuntu-mirror@meta
       - uses: actions/checkout@v3
         with:
           fetch-depth: 5
@@ -427,8 +430,8 @@ jobs:
         with:
           name: pdns-recursor-${{ matrix.sanitizers }}
           path: /opt/pdns-recursor
-      - run: build-scripts/gh-actions-setup-inv  # this runs apt update+upgrade
-      - run: inv add-auth-repo  # FIXME: do we need this for rec API testing?
+      - run: inv apt-fresh
+      - run: inv add-auth-repo  ${{ matrix.dist_name }} ${{ matrix.dist_release_name }} ${{ matrix.pdns_repo_version }}
       - run: inv install-clang-runtime
       - run: inv install-rec-test-deps
       - run: inv test-api recursor
@@ -439,13 +442,19 @@ jobs:
     strategy:
       matrix:
         sanitizers: [ubsan+asan, tsan]
-    env:
-      UBSAN_OPTIONS: 'print_stacktrace=1:halt_on_error=1:suppressions=${{ github.workspace }}/build-scripts/UBSan.supp'
-      ASAN_OPTIONS: detect_leaks=0
-      TSAN_OPTIONS: "halt_on_error=1:suppressions=${{ github.workspace }}/pdns/recursordist/recursor-tsan.supp"
+        dist_name: [debian]
+        dist_release_name: [bullseye]
+        pdns_repo_version: ['45']
+    container:
+      image: ghcr.io/powerdns/base-pdns-ci-image/debian-11-pdns-base:master
+      env:
+        UBSAN_OPTIONS: 'print_stacktrace=1:halt_on_error=1:suppressions=${{ env.REPO_HOME }}/build-scripts/UBSan.supp'
+        ASAN_OPTIONS: detect_leaks=0
+        TSAN_OPTIONS: "halt_on_error=1:suppressions=${{ env.REPO_HOME }}/pdns/recursordist/recursor-tsan.supp"
+        SKIP_IPV6_TESTS: yes
     steps:
-      - uses: PowerDNS/pdns/set-ubuntu-mirror@meta
-      - uses: actions/checkout@v3
+      - uses: PowerDNS/pdns/set-ubuntu-mirror@meta
+      - uses: actions/checkout@v3.1.0
         with:
           fetch-depth: 5
           submodules: recursive
@@ -454,8 +463,8 @@ jobs:
         with:
           name: pdns-recursor-${{ matrix.sanitizers }}
           path: /opt/pdns-recursor
-      - run: build-scripts/gh-actions-setup-inv  # this runs apt update+upgrade
-      - run: inv add-auth-repo
+      - run: inv apt-fresh
+      - run: inv add-auth-repo ${{ matrix.dist_name }} ${{ matrix.dist_release_name }} ${{ matrix.pdns_repo_version }}
       - run: inv install-clang-runtime
       - run: inv install-rec-test-deps
       - run: inv test-regression-recursor
@@ -470,12 +479,13 @@ jobs:
         threads: [1, 2, 3, 4, 8]
         mthreads: [2048]
         shards: [1, 2, 1024]
-    env:
-      UBSAN_OPTIONS: 'print_stacktrace=1:halt_on_error=1:suppressions=${{ github.workspace }}/build-scripts/UBSan.supp'
-      ASAN_OPTIONS: detect_leaks=0
-      TSAN_OPTIONS: "halt_on_error=1:suppressions=${{ github.workspace }}/pdns/recursordist/recursor-tsan.supp"
+    container:
+      image: ghcr.io/powerdns/base-pdns-ci-image/debian-11-pdns-base:master
+      env:
+        UBSAN_OPTIONS: 'print_stacktrace=1:halt_on_error=1:suppressions=${{ env.REPO_HOME }}/build-scripts/UBSan.supp'
+        ASAN_OPTIONS: detect_leaks=0
+        TSAN_OPTIONS: "halt_on_error=1:suppressions=${{ env.REPO_HOME }}/pdns/recursordist/recursor-tsan.supp"
     steps:
-      - uses: PowerDNS/pdns/set-ubuntu-mirror@meta
       - uses: actions/checkout@v3
         with:
           fetch-depth: 5
@@ -485,7 +495,6 @@ jobs:
         with:
           name: pdns-recursor-${{ matrix.sanitizers }}
           path: /opt/pdns-recursor
-      - run: build-scripts/gh-actions-setup-inv  # this runs apt update+upgrade
       - run: inv install-clang-runtime
       - run: inv install-rec-bulk-deps
       - run: inv test-bulk-recursor ${{ matrix.threads }} ${{ matrix.mthreads }} ${{ matrix.shards }}
@@ -496,15 +505,17 @@ jobs:
     strategy:
       matrix:
         sanitizers: [ubsan+asan, tsan]
-    env:
-      UBSAN_OPTIONS: "print_stacktrace=1:halt_on_error=1:suppressions=${{ github.workspace }}/build-scripts/UBSan.supp"
-      # Disabling (intercept_send=0) the custom send wrappers for ASAN and TSAN because they cause the tools to report a race that doesn't exist on actual implementations of send(), see https://github.com/google/sanitizers/issues/1498
-      ASAN_OPTIONS: detect_leaks=0:intercept_send=0
-      TSAN_OPTIONS: "halt_on_error=1:intercept_send=0:suppressions=${{ github.workspace }}/pdns/dnsdistdist/dnsdist-tsan.supp"
-      # IncludeDir tests are disabled because of a weird interaction between TSAN and these tests which ever only happens on GH actions
-      SKIP_INCLUDEDIR_TESTS: yes
+    container:
+      image: ghcr.io/powerdns/base-pdns-ci-image/debian-11-pdns-base:master
+      env:
+        UBSAN_OPTIONS: "print_stacktrace=1:halt_on_error=1:suppressions=${{ env.REPO_HOME }}/build-scripts/UBSan.supp"
+        # Disabling (intercept_send=0) the custom send wrappers for ASAN and TSAN because they cause the tools to report a race that doesn't exist on actual implementations of send(), see https://github.com/google/sanitizers/issues/1498
+        ASAN_OPTIONS: detect_leaks=0:intercept_send=0
+        TSAN_OPTIONS: "halt_on_error=1:intercept_send=0:suppressions=${{ env.REPO_HOME }}/pdns/dnsdistdist/dnsdist-tsan.supp"
+        # IncludeDir tests are disabled because of a weird interaction between TSAN and these tests which ever only happens on GH actions
+        SKIP_INCLUDEDIR_TESTS: yes
+        SKIP_IPV6_TESTS: yes
     steps:
-      - uses: PowerDNS/pdns/set-ubuntu-mirror@meta
       - uses: actions/checkout@v3
         with:
           fetch-depth: 5
@@ -514,7 +525,6 @@ jobs:
         with:
           name: dnsdist-full-${{ matrix.sanitizers }}
           path: /opt/dnsdist
-      - run: build-scripts/gh-actions-setup-inv  # this runs apt update+upgrade
       - run: inv install-clang-runtime
       - run: inv install-dnsdist-test-deps
       - run: inv test-dnsdist
@@ -522,13 +532,13 @@ jobs:
   swagger-syntax-check:
     if: ${{ !github.event.schedule || vars.SCHEDULED_JOBS_BUILD_AND_TEST_ALL }}
     runs-on: ubuntu-20.04
+    container:
+      image: ghcr.io/powerdns/base-pdns-ci-image/debian-11-pdns-base:master
     steps:
-      - uses: PowerDNS/pdns/set-ubuntu-mirror@meta
       - uses: actions/checkout@v3
         with:
           fetch-depth: 5
           submodules: recursive
-      - run: build-scripts/gh-actions-setup-inv  # this runs apt update+upgrade
       - run: inv install-swagger-tools
       - run: inv swagger-syntax-check
 
index c93ed0ef8769374afd8658ede15b5e582a0bbd92..2dbd3a34223f03de295cdac1d4b0f2cf352250c5 100644 (file)
@@ -8,6 +8,11 @@ jobs:
   Fuzzing:
     runs-on: ubuntu-20.04
     steps:
+    - uses: actions/checkout@v3
+      with:
+        fetch-depth: 5
+        submodules: recursive
+    - run: docker build -t gcr.io/oss-fuzz-base/base-builder:latest -f Dockerfile-cifuzz .
     - name: Build Fuzzers
       uses: google/oss-fuzz/infra/cifuzz/actions/build_fuzzers@master
       with:
index a7d0db59b655ca41cfc3dba29329702dac26ea07..1b12ce6cf03fbcea5d9f37c6f514a77a2b437c84 100644 (file)
@@ -7,6 +7,9 @@ on:
 permissions: # least privileges, see https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#permissions
   contents: read
 
+env:
+  CLANG_VERSION: '12'
+
 jobs:
   el7-devtoolset:
     if: ${{ vars.SCHEDULED_MISC_DAILIES }}
index b7b52ea7a1253c0573db029d72bc91b6b26f042f..e1e4463f9eaaddb96efbf63f3c741db70c34ac2f 100644 (file)
@@ -14,6 +14,7 @@ jobs:
     # on a ubuntu-20.04 VM
     runs-on: ubuntu-20.04
     steps:
+      - uses: PowerDNS/pdns/set-ubuntu-mirror@meta
       - uses: actions/checkout@v3
         with:
           fetch-depth: 5
diff --git a/Dockerfile-cifuzz b/Dockerfile-cifuzz
new file mode 100644 (file)
index 0000000..61036bb
--- /dev/null
@@ -0,0 +1,5 @@
+FROM gcr.io/oss-fuzz-base/base-builder:latest
+
+RUN sed -i 's/[a-z]*\.ubuntu\.com/mirror\.leaseweb\.net/' /etc/apt/sources.list
+RUN apt-get update
+
index 0cd64feb5b7f4b1557f9795c3f682c981151e5ec..50d11f607c77a4d46baf6033ebc56c26e707379d 100755 (executable)
@@ -8,6 +8,7 @@ sudo chmod 755 /usr/sbin/policy-rc.d
 sudo apt-get update
 # FIXME: Avoid GRUB related errors due to runner image configuration by removing it.
 sudo dpkg --purge --force-all grub-efi-amd64-signed && sudo dpkg --purge --force-all shim-signed
+sudo dpkg --purge --force-all firefox
 sudo apt-get autoremove
 sudo apt-get -qq -y --allow-downgrades dist-upgrade
 sudo apt-get -qq -y --no-install-recommends install python3-pip
index 782435ef5f9fda5ff3352cb10b7cacd3f6467d4b..32c8dde935e847b9b16c719753ed586912bbbe59 100644 (file)
@@ -300,7 +300,7 @@ It is strongly recommended to keep this setting enabled (`yes`).
 
 -  Path
 
-Location of configuration directory (``pdns.conf``). Usually
+Location of configuration directory (the directory containing ``pdns.conf``). Usually
 ``/etc/powerdns``, but this depends on ``SYSCONFDIR`` during
 compile-time.
 
index 587821f992c2e0bb68a4f76f8c8e2d73300beefb..e1dbcf7bd414542cf5b835e8d0341b9a427c62fb 100644 (file)
@@ -894,7 +894,7 @@ static uint32_t capPacketCacheTTL(const struct dnsheader& hdr, uint32_t ttl, boo
   return ttl;
 }
 
-void startDoResolve(void* p)
+void startDoResolve(void* p) // NOLINT(readability-function-cognitive-complexity): https://github.com/PowerDNS/pdns/issues/12791
 {
   auto dc = std::unique_ptr<DNSComboWriter>(reinterpret_cast<DNSComboWriter*>(p));
   SyncRes sr(dc->d_now);
@@ -1674,7 +1674,8 @@ void startDoResolve(void* p)
 #endif
     }
 
-    if (g_packetCache && !variableAnswer && !sr.wasVariable()) {
+    const bool intoPC = g_packetCache && !variableAnswer && !sr.wasVariable();
+    if (intoPC) {
       minTTL = capPacketCacheTTL(*pw.getHeader(), minTTL, seenAuthSOA);
       g_packetCache->insertResponsePacket(dc->d_tag, dc->d_qhash, std::move(dc->d_query), dc->d_mdp.d_qname,
                                           dc->d_mdp.d_qtype, dc->d_mdp.d_qclass,
@@ -1789,6 +1790,7 @@ void startDoResolve(void* p)
         if (!shouldNotValidate && sr.isDNSSECValidationRequested()) {
           g_log << ", dnssec=" << sr.getValidationState();
         }
+        g_log << " answer-is-variable=" << sr.wasVariable() << ", into-packetcache=" << intoPC;
         g_log << endl;
       }
       else {
@@ -1803,7 +1805,9 @@ void startDoResolve(void* p)
                         "tcpout", Logging::Loggable(sr.d_tcpoutqueries),
                         "dotout", Logging::Loggable(sr.d_dotoutqueries),
                         "rcode", Logging::Loggable(res),
-                        "validationState", Logging::Loggable(sr.getValidationState()));
+                        "validationState", Logging::Loggable(sr.getValidationState()),
+                        "answer-is-variable", Logging::Loggable(sr.wasVariable()),
+                        "into-packetcache", Logging::Loggable(intoPC));
       }
     }
 
index 36401172383a33da3466d858b75e8f32b3e6919d..0e7e3fceb3821d506f88bcc4f238137aba2585c6 100644 (file)
@@ -45,6 +45,8 @@
 
 #ifdef HAVE_LIBSODIUM
 #include <sodium.h>
+
+#include <cstddef>
 #endif
 
 #ifdef HAVE_SYSTEMD
@@ -1245,28 +1247,29 @@ void broadcastFunction(const pipefunc_t& func)
     func();
   }
 
-  unsigned int n = 0;
+  unsigned int thread = 0;
   for (const auto& threadInfo : RecThreadInfo::infos()) {
-    if (n++ == RecThreadInfo::id()) {
+    if (thread++ == RecThreadInfo::id()) {
       func(); // don't write to ourselves!
       continue;
     }
 
-    ThreadMSG* tmsg = new ThreadMSG();
+    ThreadMSG* tmsg = new ThreadMSG(); // NOLINT: manual ownership handling
     tmsg->func = func;
     tmsg->wantAnswer = true;
-    if (write(threadInfo.pipes.writeToThread, &tmsg, sizeof(tmsg)) != sizeof(tmsg)) {
-      delete tmsg;
+    if (write(threadInfo.pipes.writeToThread, &tmsg, sizeof(tmsg)) != sizeof(tmsg)) { // NOLINT: sizeof correct
+      delete tmsg; // NOLINT: manual ownership handling
 
       unixDie("write to thread pipe returned wrong size or error");
     }
 
     string* resp = nullptr;
-    if (read(threadInfo.pipes.readFromThread, &resp, sizeof(resp)) != sizeof(resp))
+    if (read(threadInfo.pipes.readFromThread, &resp, sizeof(resp)) != sizeof(resp)) { // NOLINT: sizeof correct
       unixDie("read from thread pipe returned wrong size or error");
+    }
 
-    if (resp) {
-      delete resp;
+    if (resp != nullptr) {
+      delete resp; // NOLINT: manual ownership handling
       resp = nullptr;
     }
     // coverity[leaked_storage]
@@ -1322,30 +1325,30 @@ T broadcastAccFunction(const std::function<T*()>& func)
     _exit(1);
   }
 
-  unsigned int n = 0;
+  unsigned int thread = 0;
   T ret = T();
   for (const auto& threadInfo : RecThreadInfo::infos()) {
-    if (n++ == RecThreadInfo::id()) {
+    if (thread++ == RecThreadInfo::id()) {
       continue;
     }
 
     const auto& tps = threadInfo.pipes;
-    ThreadMSG* tmsg = new ThreadMSG();
+    ThreadMSG* tmsg = new ThreadMSG(); // NOLINT: manual ownership handling
     tmsg->func = [func] { return voider<T>(func); };
     tmsg->wantAnswer = true;
 
-    if (write(tps.writeToThread, &tmsg, sizeof(tmsg)) != sizeof(tmsg)) {
-      delete tmsg;
+    if (write(tps.writeToThread, &tmsg, sizeof(tmsg)) != sizeof(tmsg)) { // NOLINT:: sizeof correct
+      delete tmsg; // NOLINT: manual ownership handling
       unixDie("write to thread pipe returned wrong size or error");
     }
 
     T* resp = nullptr;
-    if (read(tps.readFromThread, &resp, sizeof(resp)) != sizeof(resp))
+    if (read(tps.readFromThread, &resp, sizeof(resp)) != sizeof(resp)) // NOLINT: sizeof correct
       unixDie("read from thread pipe returned wrong size or error");
 
     if (resp) {
       ret += *resp;
-      delete resp;
+      delete resp; // NOLINT: manual ownership handling
       resp = nullptr;
     }
     // coverity[leaked_storage]
@@ -1362,27 +1365,8 @@ template ThreadTimes broadcastAccFunction(const std::function<ThreadTimes*()>& f
 template ProxyMappingStats_t broadcastAccFunction(const std::function<ProxyMappingStats_t*()>& fun);
 template RemoteLoggerStats_t broadcastAccFunction(const std::function<RemoteLoggerStats_t*()>& fun);
 
-static int serviceMain(int /* argc */, char* /* argv */[], Logr::log_t log)
+static int initNet(Logr::log_t log)
 {
-  g_log.setName(g_programname);
-  g_log.disableSyslog(::arg().mustDo("disable-syslog"));
-  g_log.setTimestamps(::arg().mustDo("log-timestamp"));
-  g_regressionTestMode = ::arg().mustDo("devonly-regression-test-mode");
-
-  if (!::arg()["logging-facility"].empty()) {
-    int val = logFacilityToLOG(::arg().asNum("logging-facility"));
-    if (val >= 0)
-      g_log.setFacility(val);
-    else {
-      SLOG(g_log << Logger::Error << "Unknown logging facility " << ::arg().asNum("logging-facility") << endl,
-           log->info(Logr::Error, "Unknown logging facility", "facility", Logging::Loggable(::arg().asNum("logging-facility"))));
-    }
-  }
-
-  showProductVersion();
-
-  g_disthashseed = dns_random(0xffffffff);
-
   checkLinuxIPv6Limits(log);
   try {
     pdns::parseQueryLocalAddress(::arg()["query-local-address"]);
@@ -1390,7 +1374,7 @@ static int serviceMain(int /* argc */, char* /* argv */[], Logr::log_t log)
   catch (std::exception& e) {
     SLOG(g_log << Logger::Error << "Assigning local query addresses: " << e.what(),
          log->error(Logr::Error, e.what(), "Unable to assign local query address"));
-    exit(99);
+    return 99;
   }
 
   if (pdns::isQueryLocalAddressFamilyEnabled(AF_INET)) {
@@ -1416,68 +1400,63 @@ static int serviceMain(int /* argc */, char* /* argv */[], Logr::log_t log)
   if (!SyncRes::s_doIPv6 && !SyncRes::s_doIPv4) {
     SLOG(g_log << Logger::Error << "No outgoing addresses configured! Can not continue" << endl,
          log->info(Logr::Error, "No outgoing addresses configured! Can not continue"));
-    exit(99);
+    return 99;
   }
+  return 0;
+}
 
-  // keep this ABOVE loadRecursorLuaConfig!
-  if (::arg()["dnssec"] == "off")
+static int initDNSSEC(Logr::log_t log)
+{
+  if (::arg()["dnssec"] == "off") {
     g_dnssecmode = DNSSECMode::Off;
-  else if (::arg()["dnssec"] == "process-no-validate")
+  }
+  else if (::arg()["dnssec"] == "process-no-validate") {
     g_dnssecmode = DNSSECMode::ProcessNoValidate;
-  else if (::arg()["dnssec"] == "process")
+  }
+  else if (::arg()["dnssec"] == "process") {
     g_dnssecmode = DNSSECMode::Process;
-  else if (::arg()["dnssec"] == "validate")
+  }
+  else if (::arg()["dnssec"] == "validate") {
     g_dnssecmode = DNSSECMode::ValidateAll;
-  else if (::arg()["dnssec"] == "log-fail")
+  }
+  else if (::arg()["dnssec"] == "log-fail") {
     g_dnssecmode = DNSSECMode::ValidateForLog;
+  }
   else {
     SLOG(g_log << Logger::Error << "Unknown DNSSEC mode " << ::arg()["dnssec"] << endl,
          log->info(Logr::Error, "Unknown DNSSEC mode", "dnssec", Logging::Loggable(::arg()["dnssec"])));
-    exit(1);
+    return 1;
   }
 
   g_signatureInceptionSkew = ::arg().asNum("signature-inception-skew");
   if (g_signatureInceptionSkew < 0) {
     SLOG(g_log << Logger::Error << "A negative value for 'signature-inception-skew' is not allowed" << endl,
          log->info(Logr::Error, "A negative value for 'signature-inception-skew' is not allowed"));
-    exit(1);
+    return 1;
   }
 
   g_dnssecLogBogus = ::arg().mustDo("dnssec-log-bogus");
   g_maxNSEC3Iterations = ::arg().asNum("nsec3-max-iterations");
+  return 0;
+}
 
-  g_maxCacheEntries = ::arg().asNum("max-cache-entries");
-
-  luaConfigDelayedThreads delayedLuaThreads;
-  try {
-    ProxyMapping proxyMapping;
-    loadRecursorLuaConfig(::arg()["lua-config-file"], delayedLuaThreads, proxyMapping);
-    // Initial proxy mapping
-    g_proxyMapping = proxyMapping.empty() ? nullptr : std::make_unique<ProxyMapping>(proxyMapping);
-  }
-  catch (PDNSException& e) {
-    SLOG(g_log << Logger::Error << "Cannot load Lua configuration: " << e.reason << endl,
-         log->error(Logr::Error, e.reason, "Cannot load Lua configuration"));
-    exit(1);
-  }
-
-  parseACLs();
-  initPublicSuffixList(::arg()["public-suffix-list-file"]);
-
+static void initDontQuery(Logr::log_t log)
+{
   if (!::arg()["dont-query"].empty()) {
     vector<string> ips;
     stringtok(ips, ::arg()["dont-query"], ", ");
-    ips.push_back("0.0.0.0");
-    ips.push_back("::");
+    ips.emplace_back("0.0.0.0");
+    ips.emplace_back("::");
 
-    for (const auto& ip : ips) {
-      SyncRes::addDontQuery(ip);
+    for (const auto& anIP : ips) {
+      SyncRes::addDontQuery(anIP);
     }
     if (!g_slogStructured) {
       g_log << Logger::Warning << "Will not send queries to: ";
-      for (vector<string>::const_iterator i = ips.begin(); i != ips.end(); ++i) {
-        if (i != ips.begin())
+      for (auto i = ips.begin(); i != ips.end(); ++i) {
+        if (i != ips.begin()) {
           g_log << Logger::Warning << ", ";
+        }
         g_log << Logger::Warning << *i;
       }
       g_log << Logger::Warning << endl;
@@ -1486,30 +1465,10 @@ static int serviceMain(int /* argc */, char* /* argv */[], Logr::log_t log)
       log->info(Logr::Notice, "Will not send queries to", "addresses", Logging::IterLoggable(ips.begin(), ips.end()));
     }
   }
+}
 
-  /* this needs to be done before parseACLs(), which call broadcastFunction() */
-  RecThreadInfo::setWeDistributeQueries(::arg().mustDo("pdns-distributes-queries"));
-  if (RecThreadInfo::weDistributeQueries()) {
-    SLOG(g_log << Logger::Warning << "PowerDNS Recursor itself will distribute queries over threads" << endl,
-         log->info(Logr::Notice, "PowerDNS Recursor itself will distribute queries over threads"));
-  }
-
-  g_outgoingEDNSBufsize = ::arg().asNum("edns-outgoing-bufsize");
-
-  if (::arg()["trace"] == "fail") {
-    SyncRes::setDefaultLogMode(SyncRes::Store);
-  }
-  else if (::arg().mustDo("trace")) {
-    SyncRes::setDefaultLogMode(SyncRes::Log);
-    ::arg().set("quiet") = "no";
-    g_quiet = false;
-  }
-  auto myHostname = getHostname();
-  if (!myHostname.has_value()) {
-    SLOG(g_log << Logger::Warning << "Unable to get the hostname, NSID and id.server values will be empty" << endl,
-         log->info(Logr::Warning, "Unable to get the hostname, NSID and id.server values will be empty"));
-  }
-
+static int initSyncRes(Logr::log_t log, const std::optional<std::string>& myHostname)
+{
   SyncRes::s_minimumTTL = ::arg().asNum("minimum-ttl-override");
   SyncRes::s_minimumECSTTL = ::arg().asNum("ecs-minimum-ttl-override");
   SyncRes::s_maxnegttl = ::arg().asNum("max-negative-ttl");
@@ -1547,7 +1506,7 @@ static int serviceMain(int /* argc */, char* /* argv */[], Logr::log_t log)
     if (sse > std::numeric_limits<uint16_t>::max()) {
       SLOG(g_log << Logger::Error << "Illegal serve-stale-extensions value: " << sse << "; range = 0..65536" << endl,
            log->info(Logr::Error, "Illegal serve-stale-extensions value; range = 0..65536", "value", Logging::Loggable(sse)));
-      exit(1);
+      return 1;
     }
     MemRecursorCache::s_maxServedStaleExtensions = sse;
     NegCache::s_maxServedStaleExtensions = sse;
@@ -1590,7 +1549,7 @@ static int serviceMain(int /* argc */, char* /* argv */[], Logr::log_t log)
   else if (value != "dnssec") {
     SLOG(g_log << Logger::Error << "Unknown nothing-below-nxdomain mode: " << value << endl,
          log->info(Logr::Error, "Unknown nothing-below-nxdomain mode", "mode", Logging::Loggable(value)));
-    exit(1);
+    return 1;
   }
 
   if (!::arg().isEmpty("ecs-scope-zero-address")) {
@@ -1598,185 +1557,36 @@ static int serviceMain(int /* argc */, char* /* argv */[], Logr::log_t log)
     SyncRes::setECSScopeZeroAddress(Netmask(scopeZero, scopeZero.isIPv4() ? 32 : 128));
   }
   else {
-    Netmask nm;
+    Netmask netmask;
     bool done = false;
 
     auto addr = pdns::getNonAnyQueryLocalAddress(AF_INET);
-    if (addr.sin4.sin_family != 0) {
-      nm = Netmask(addr, 32);
+    if (addr.sin4.sin_family != 0) { // NOLINT: union access
+      netmask = Netmask(addr, 32);
       done = true;
     }
     if (!done) {
       addr = pdns::getNonAnyQueryLocalAddress(AF_INET6);
-      if (addr.sin4.sin_family != 0) {
-        nm = Netmask(addr, 128);
+      if (addr.sin4.sin_family != 0) { // NOLINT: union access
+        netmask = Netmask(addr, 128);
         done = true;
       }
     }
     if (!done) {
-      nm = Netmask(ComboAddress("127.0.0.1"), 32);
+      netmask = Netmask(ComboAddress("127.0.0.1"), 32);
     }
-    SyncRes::setECSScopeZeroAddress(nm);
+    SyncRes::setECSScopeZeroAddress(netmask);
   }
 
   SyncRes::parseEDNSSubnetAllowlist(::arg()["edns-subnet-whitelist"]);
   SyncRes::parseEDNSSubnetAllowlist(::arg()["edns-subnet-allow-list"]);
   SyncRes::parseEDNSSubnetAddFor(::arg()["ecs-add-for"]);
   g_useIncomingECS = ::arg().mustDo("use-incoming-edns-subnet");
+  return 0;
+}
 
-  g_proxyProtocolACL.toMasks(::arg()["proxy-protocol-from"]);
-  g_proxyProtocolMaximumSize = ::arg().asNum("proxy-protocol-maximum-size");
-
-  if (!::arg()["dns64-prefix"].empty()) {
-    try {
-      auto dns64Prefix = Netmask(::arg()["dns64-prefix"]);
-      if (dns64Prefix.getBits() != 96) {
-        SLOG(g_log << Logger::Error << "Invalid prefix for 'dns64-prefix', the current implementation only supports /96 prefixes: " << ::arg()["dns64-prefix"] << endl,
-             log->info(Logr::Error, "Invalid prefix for 'dns64-prefix', the current implementation only supports /96 prefixes", "prefix", Logging::Loggable(::arg()["dns64-prefix"])));
-        exit(1);
-      }
-      g_dns64Prefix = dns64Prefix.getNetwork();
-      g_dns64PrefixReverse = reverseNameFromIP(*g_dns64Prefix);
-      /* /96 is 24 nibbles + 2 for "ip6.arpa." */
-      while (g_dns64PrefixReverse.countLabels() > 26) {
-        g_dns64PrefixReverse.chopOff();
-      }
-    }
-    catch (const NetmaskException& ne) {
-      SLOG(g_log << Logger::Error << "Invalid prefix '" << ::arg()["dns64-prefix"] << "' for 'dns64-prefix': " << ne.reason << endl,
-           log->info(Logr::Error, "Invalid prefix", "dns64-prefix", Logging::Loggable(::arg()["dns64-prefix"])));
-      exit(1);
-    }
-  }
-
-  g_networkTimeoutMsec = ::arg().asNum("network-timeout");
-
-  std::tie(g_initialDomainMap, g_initialAllowNotifyFor) = parseZoneConfiguration();
-
-  g_latencyStatSize = ::arg().asNum("latency-statistic-size");
-
-  g_logCommonErrors = ::arg().mustDo("log-common-errors");
-  g_logRPZChanges = ::arg().mustDo("log-rpz-changes");
-
-  g_anyToTcp = ::arg().mustDo("any-to-tcp");
-  g_udpTruncationThreshold = ::arg().asNum("udp-truncation-threshold");
-
-  g_lowercaseOutgoing = ::arg().mustDo("lowercase-outgoing");
-
-  g_paddingFrom.toMasks(::arg()["edns-padding-from"]);
-  if (::arg()["edns-padding-mode"] == "always") {
-    g_paddingMode = PaddingMode::Always;
-  }
-  else if (::arg()["edns-padding-mode"] == "padded-queries-only") {
-    g_paddingMode = PaddingMode::PaddedQueries;
-  }
-  else {
-    SLOG(g_log << Logger::Error << "Unknown edns-padding-mode: " << ::arg()["edns-padding-mode"] << endl,
-         log->info(Logr::Error, "Unknown edns-padding-mode", "edns-padding-mode", Logging::Loggable(::arg()["edns-padding-mode"])));
-    exit(1);
-  }
-  g_paddingTag = ::arg().asNum("edns-padding-tag");
-  g_paddingOutgoing = ::arg().mustDo("edns-padding-out");
-
-  RecThreadInfo::setNumDistributorThreads(::arg().asNum("distributor-threads"));
-  RecThreadInfo::setNumWorkerThreads(::arg().asNum("threads"));
-  if (RecThreadInfo::numWorkers() < 1) {
-    SLOG(g_log << Logger::Warning << "Asked to run with 0 threads, raising to 1 instead" << endl,
-         log->info(Logr::Warning, "Asked to run with 0 threads, raising to 1 instead"));
-    RecThreadInfo::setNumWorkerThreads(1);
-  }
-
-  g_maxMThreads = ::arg().asNum("max-mthreads");
-
-  int64_t maxInFlight = ::arg().asNum("max-concurrent-requests-per-tcp-connection");
-  if (maxInFlight < 1 || maxInFlight > USHRT_MAX || maxInFlight >= g_maxMThreads) {
-    SLOG(g_log << Logger::Warning << "Asked to run with illegal max-concurrent-requests-per-tcp-connection, setting to default (10)" << endl,
-         log->info(Logr::Warning, "Asked to run with illegal max-concurrent-requests-per-tcp-connection, setting to default (10)"));
-    TCPConnection::s_maxInFlight = 10;
-  }
-  else {
-    TCPConnection::s_maxInFlight = maxInFlight;
-  }
-
-  int64_t millis = ::arg().asNum("tcp-out-max-idle-ms");
-  TCPOutConnectionManager::s_maxIdleTime = timeval{millis / 1000, (static_cast<suseconds_t>(millis) % 1000) * 1000};
-  TCPOutConnectionManager::s_maxIdlePerAuth = ::arg().asNum("tcp-out-max-idle-per-auth");
-  TCPOutConnectionManager::s_maxQueries = ::arg().asNum("tcp-out-max-queries");
-  TCPOutConnectionManager::s_maxIdlePerThread = ::arg().asNum("tcp-out-max-idle-per-thread");
-
-  g_gettagNeedsEDNSOptions = ::arg().mustDo("gettag-needs-edns-options");
-
-  s_statisticsInterval = ::arg().asNum("statistics-interval");
-
-  SyncRes::s_addExtendedResolutionDNSErrors = ::arg().mustDo("extended-resolution-errors");
-
-  if (::arg().asNum("aggressive-nsec-cache-size") > 0) {
-    if (g_dnssecmode == DNSSECMode::ValidateAll || g_dnssecmode == DNSSECMode::ValidateForLog || g_dnssecmode == DNSSECMode::Process) {
-      g_aggressiveNSECCache = make_unique<AggressiveNSECCache>(::arg().asNum("aggressive-nsec-cache-size"));
-    }
-    else {
-      SLOG(g_log << Logger::Warning << "Aggressive NSEC/NSEC3 caching is enabled but DNSSEC validation is not set to 'validate', 'log-fail' or 'process', ignoring" << endl,
-           log->info(Logr::Warning, "Aggressive NSEC/NSEC3 caching is enabled but DNSSEC validation is not set to 'validate', 'log-fail' or 'process', ignoring"));
-    }
-  }
-
-  AggressiveNSECCache::s_maxNSEC3CommonPrefix = static_cast<uint8_t>(std::round(std::log2(::arg().asNum("aggressive-cache-min-nsec3-hit-ratio"))));
-  SLOG(g_log << Logger::Debug << "NSEC3 aggressive cache tuning: aggressive-cache-min-nsec3-hit-ratio: " << ::arg().asNum("aggressive-cache-min-nsec3-hit-ratio") << " max common prefix bits: " << std::to_string(AggressiveNSECCache::s_maxNSEC3CommonPrefix) << endl,
-       log->info(Logr::Debug, "NSEC3 aggressive cache tuning", "aggressive-cache-min-nsec3-hit-ratio", Logging::Loggable(::arg().asNum("aggressive-cache-min-nsec3-hit-ratio")), "maxCommonPrefixBits", Logging::Loggable(AggressiveNSECCache::s_maxNSEC3CommonPrefix)));
-
-  {
-    SuffixMatchNode dontThrottleNames;
-    vector<string> parts;
-    stringtok(parts, ::arg()["dont-throttle-names"], " ,");
-    for (const auto& p : parts) {
-      dontThrottleNames.add(DNSName(p));
-    }
-    g_dontThrottleNames.setState(std::move(dontThrottleNames));
-
-    parts.clear();
-    NetmaskGroup dontThrottleNetmasks;
-    stringtok(parts, ::arg()["dont-throttle-netmasks"], " ,");
-    for (const auto& p : parts) {
-      dontThrottleNetmasks.addMask(Netmask(p));
-    }
-    g_dontThrottleNetmasks.setState(std::move(dontThrottleNetmasks));
-  }
-
-  {
-    SuffixMatchNode xdnssecNames;
-    vector<string> parts;
-    stringtok(parts, ::arg()["x-dnssec-names"], " ,");
-    for (const auto& p : parts) {
-      xdnssecNames.add(DNSName(p));
-    }
-    g_xdnssec.setState(std::move(xdnssecNames));
-  }
-
-  {
-    SuffixMatchNode dotauthNames;
-    vector<string> parts;
-    stringtok(parts, ::arg()["dot-to-auth-names"], " ,");
-#ifndef HAVE_DNS_OVER_TLS
-    if (parts.size()) {
-      SLOG(g_log << Logger::Error << "dot-to-auth-names setting contains names, but Recursor was built without DNS over TLS support. Setting will be ignored." << endl,
-           log->info(Logr::Error, "dot-to-auth-names setting contains names, but Recursor was built without DNS over TLS support. Setting will be ignored"));
-    }
-#endif
-    for (const auto& p : parts) {
-      dotauthNames.add(DNSName(p));
-    }
-    g_DoTToAuthNames.setState(std::move(dotauthNames));
-  }
-
-  {
-    CarbonConfig config;
-    stringtok(config.servers, arg()["carbon-server"], ", ");
-    config.hostname = arg()["carbon-ourname"];
-    config.instance_name = arg()["carbon-instance"];
-    config.namespace_name = arg()["carbon-namespace"];
-    g_carbonConfig.setState(std::move(config));
-  }
-
+static void initDistribution(Logr::log_t log)
+{
   g_balancingFactor = ::arg().asDouble("distribution-load-factor");
   if (g_balancingFactor != 0.0 && g_balancingFactor < 1.0) {
     g_balancingFactor = 0.0;
@@ -1834,16 +1644,15 @@ static int serviceMain(int /* argc */, char* /* argv */[], Logr::log_t log)
       }
     }
   }
+}
 
-#ifdef NOD_ENABLED
-  // Setup newly observed domain globals
-  setupNODGlobal();
-#endif /* NOD_ENABLED */
-
-  int forks;
-  for (forks = 0; forks < ::arg().asNum("processes") - 1; ++forks) {
-    if (!fork()) // we are child
+static int initForks(Logr::log_t log)
+{
+  int forks = 0;
+  for (; forks < ::arg().asNum("processes") - 1; ++forks) {
+    if (fork() == 0) { // we are child
       break;
+    }
   }
 
   if (::arg().mustDo("daemon")) {
@@ -1852,6 +1661,7 @@ static int serviceMain(int /* argc */, char* /* argv */[], Logr::log_t log)
     g_log.toConsole(Logger::Critical);
     daemonize(log);
   }
+
   if (Utility::getpid() == 1) {
     /* We are running as pid 1, register sigterm and sigint handler
 
@@ -1872,63 +1682,86 @@ static int serviceMain(int /* argc */, char* /* argv */[], Logr::log_t log)
 
   signal(SIGUSR1, usr1Handler);
   signal(SIGUSR2, usr2Handler);
-  signal(SIGPIPE, SIG_IGN);
-
-  checkOrFixFDS(log);
+  signal(SIGPIPE, SIG_IGN); // NOLINT: Posix API
+  return forks;
+}
 
-#ifdef HAVE_LIBSODIUM
-  if (sodium_init() == -1) {
-    SLOG(g_log << Logger::Error << "Unable to initialize sodium crypto library" << endl,
-         log->info(Logr::Error, "Unable to initialize sodium crypto library"));
-    exit(99);
+static int initPorts(Logr::log_t log)
+{
+  int port = ::arg().asNum("udp-source-port-min");
+  if (port < 1024 || port > 65535) {
+    SLOG(g_log << Logger::Error << "Unable to launch, udp-source-port-min is not a valid port number" << endl,
+         log->info(Logr::Error, "Unable to launch, udp-source-port-min is not a valid port number"));
+    return 99; // this isn't going to fix itself either
   }
-#endif
-
-  openssl_thread_setup();
-  openssl_seed();
-  /* setup rng before chroot */
-  dns_random_init();
-
-  if (::arg()["server-id"].empty()) {
-    ::arg().set("server-id") = myHostname.has_value() ? *myHostname : "";
+  g_minUdpSourcePort = port;
+  port = ::arg().asNum("udp-source-port-max");
+  if (port < 1024 || port > 65535 || port < g_minUdpSourcePort) {
+    SLOG(g_log << Logger::Error << "Unable to launch, udp-source-port-max is not a valid port number or is smaller than udp-source-port-min" << endl,
+         log->info(Logr::Error, "Unable to launch, udp-source-port-max is not a valid port number or is smaller than udp-source-port-min"));
+    return 99; // this isn't going to fix itself either
   }
-
-  int newgid = 0;
-  if (!::arg()["setgid"].empty())
-    newgid = strToGID(::arg()["setgid"]);
-  int newuid = 0;
-  if (!::arg()["setuid"].empty())
-    newuid = strToUID(::arg()["setuid"]);
-
-  Utility::dropGroupPrivs(newuid, newgid);
-
-  if (!::arg()["chroot"].empty()) {
-#ifdef HAVE_SYSTEMD
+  g_maxUdpSourcePort = port;
+  std::vector<string> parts{};
+  stringtok(parts, ::arg()["udp-source-port-avoid"], ", ");
+  for (const auto& part : parts) {
+    port = std::stoi(part);
+    if (port < 1024 || port > 65535) {
+      SLOG(g_log << Logger::Error << "Unable to launch, udp-source-port-avoid contains an invalid port number: " << part << endl,
+           log->info(Logr::Error, "Unable to launch, udp-source-port-avoid contains an invalid port number", "port", Logging::Loggable(part)));
+      return 99; // this isn't going to fix itself either
+    }
+    g_avoidUdpSourcePorts.insert(port);
+  }
+  return 0;
+}
+
+static void initSNMP([[maybe_unused]] Logr::log_t log)
+{
+  if (::arg().mustDo("snmp-agent")) {
+#ifdef HAVE_NET_SNMP
+    string setting = ::arg()["snmp-daemon-socket"];
+    if (setting.empty()) {
+      setting = ::arg()["snmp-master-socket"];
+    }
+    g_snmpAgent = std::make_shared<RecursorSNMPAgent>("recursor", setting);
+    g_snmpAgent->run();
+#else
+    const std::string msg = "snmp-agent set but SNMP support not compiled in";
+    SLOG(g_log << Logger::Error << msg << endl,
+         log->info(Logr::Error, msg));
+#endif // HAVE_NET_SNMP
+  }
+}
+
+static int initControl(Logr::log_t log, uid_t newuid, int forks) // NOLINT(bugprone-easily-swappable-parameter*) #12791 Remove NOLINT(readability-function-cognitive-complexity) omoerbeek
+{
+  if (!::arg()["chroot"].empty()) {
+#ifdef HAVE_SYSTEMD
     char* ns;
     ns = getenv("NOTIFY_SOCKET");
     if (ns != nullptr) {
       SLOG(g_log << Logger::Error << "Unable to chroot when running from systemd. Please disable chroot= or set the 'Type' for this service to 'simple'" << endl,
            log->info(Logr::Error, "Unable to chroot when running from systemd. Please disable chroot= or set the 'Type' for this service to 'simple'"));
-      exit(1);
+      return 1;
     }
 #endif
     if (chroot(::arg()["chroot"].c_str()) < 0 || chdir("/") < 0) {
       int err = errno;
-      SLOG(g_log << Logger::Error << "Unable to chroot to '" + ::arg()["chroot"] + "': " << strerror(err) << ", exiting" << endl,
+      SLOG(g_log << Logger::Error << "Unable to chroot to '" + ::arg()["chroot"] + "': " << stringerror(err) << ", exiting" << endl,
            log->error(Logr::Error, err, "Unable to chroot", "chroot", Logging::Loggable(::arg()["chroot"])));
-      exit(1);
-    }
-    else {
-      SLOG(g_log << Logger::Info << "Chrooted to '" << ::arg()["chroot"] << "'" << endl,
-           log->info(Logr::Info, "Chrooted", "chroot", Logging::Loggable(::arg()["chroot"])));
+      return 1;
     }
+    SLOG(g_log << Logger::Info << "Chrooted to '" << ::arg()["chroot"] << "'" << endl,
+         log->info(Logr::Info, "Chrooted", "chroot", Logging::Loggable(::arg()["chroot"])));
   }
 
   checkSocketDir(log);
 
   g_pidfname = ::arg()["socket-dir"] + "/" + g_programname + ".pid";
-  if (!g_pidfname.empty())
+  if (!g_pidfname.empty()) {
     unlink(g_pidfname.c_str()); // remove possible old pid file
+  }
   writePid(log);
 
   makeControlChannelSocket(::arg().asNum("processes") > 1 ? forks : -1);
@@ -1945,6 +1778,297 @@ static int serviceMain(int /* argc */, char* /* argv */[], Logr::log_t log)
     SLOG(g_log << Logger::Warning << e.what() << endl,
          log->error(Logr::Warning, e.what(), "Could not drop capabilities"));
   }
+  return 0;
+}
+
+static void initSuffixMatchNodes([[maybe_unused]] Logr::log_t log)
+{
+  {
+    SuffixMatchNode dontThrottleNames;
+    vector<string> parts;
+    stringtok(parts, ::arg()["dont-throttle-names"], " ,");
+    for (const auto& part : parts) {
+      dontThrottleNames.add(DNSName(part));
+    }
+    g_dontThrottleNames.setState(std::move(dontThrottleNames));
+
+    parts.clear();
+    NetmaskGroup dontThrottleNetmasks;
+    stringtok(parts, ::arg()["dont-throttle-netmasks"], " ,");
+    for (const auto& part : parts) {
+      dontThrottleNetmasks.addMask(Netmask(part));
+    }
+    g_dontThrottleNetmasks.setState(std::move(dontThrottleNetmasks));
+  }
+
+  {
+    SuffixMatchNode xdnssecNames;
+    vector<string> parts;
+    stringtok(parts, ::arg()["x-dnssec-names"], " ,");
+    for (const auto& part : parts) {
+      xdnssecNames.add(DNSName(part));
+    }
+    g_xdnssec.setState(std::move(xdnssecNames));
+  }
+
+  {
+    SuffixMatchNode dotauthNames;
+    vector<string> parts;
+    stringtok(parts, ::arg()["dot-to-auth-names"], " ,");
+#ifndef HAVE_DNS_OVER_TLS
+    if (parts.size()) {
+      SLOG(g_log << Logger::Error << "dot-to-auth-names setting contains names, but Recursor was built without DNS over TLS support. Setting will be ignored." << endl,
+           log->info(Logr::Error, "dot-to-auth-names setting contains names, but Recursor was built without DNS over TLS support. Setting will be ignored"));
+    }
+#endif
+    for (const auto& part : parts) {
+      dotauthNames.add(DNSName(part));
+    }
+    g_DoTToAuthNames.setState(std::move(dotauthNames));
+  }
+}
+
+static void initCarbon()
+{
+  CarbonConfig config;
+  stringtok(config.servers, arg()["carbon-server"], ", ");
+  config.hostname = arg()["carbon-ourname"];
+  config.instance_name = arg()["carbon-instance"];
+  config.namespace_name = arg()["carbon-namespace"];
+  g_carbonConfig.setState(std::move(config));
+}
+
+static int initDNS64(Logr::log_t log)
+{
+  if (!::arg()["dns64-prefix"].empty()) {
+    try {
+      auto dns64Prefix = Netmask(::arg()["dns64-prefix"]);
+      if (dns64Prefix.getBits() != 96) {
+        SLOG(g_log << Logger::Error << "Invalid prefix for 'dns64-prefix', the current implementation only supports /96 prefixes: " << ::arg()["dns64-prefix"] << endl,
+             log->info(Logr::Error, "Invalid prefix for 'dns64-prefix', the current implementation only supports /96 prefixes", "prefix", Logging::Loggable(::arg()["dns64-prefix"])));
+        return 1;
+      }
+      g_dns64Prefix = dns64Prefix.getNetwork();
+      g_dns64PrefixReverse = reverseNameFromIP(*g_dns64Prefix);
+      /* /96 is 24 nibbles + 2 for "ip6.arpa." */
+      while (g_dns64PrefixReverse.countLabels() > 26) {
+        g_dns64PrefixReverse.chopOff();
+      }
+    }
+    catch (const NetmaskException& ne) {
+      SLOG(g_log << Logger::Error << "Invalid prefix '" << ::arg()["dns64-prefix"] << "' for 'dns64-prefix': " << ne.reason << endl,
+           log->info(Logr::Error, "Invalid prefix", "dns64-prefix", Logging::Loggable(::arg()["dns64-prefix"])));
+      return 1;
+    }
+  }
+  return 0;
+}
+
+static int serviceMain(Logr::log_t log) // NOLINT(readability-function-cognitive-complexity) #12791 Remove NOLINT(readability-function-cognitive-complexity) omoerbeek
+{
+  g_log.setName(g_programname);
+  g_log.disableSyslog(::arg().mustDo("disable-syslog"));
+  g_log.setTimestamps(::arg().mustDo("log-timestamp"));
+  g_regressionTestMode = ::arg().mustDo("devonly-regression-test-mode");
+
+  if (!::arg()["logging-facility"].empty()) {
+    int val = logFacilityToLOG(::arg().asNum("logging-facility"));
+    if (val >= 0) {
+      g_log.setFacility(val);
+    }
+    else {
+      SLOG(g_log << Logger::Error << "Unknown logging facility " << ::arg().asNum("logging-facility") << endl,
+           log->info(Logr::Error, "Unknown logging facility", "facility", Logging::Loggable(::arg().asNum("logging-facility"))));
+    }
+  }
+
+  showProductVersion();
+
+  g_disthashseed = dns_random(0xffffffff);
+
+  int ret = initNet(log);
+  if (ret != 0) {
+    return ret;
+  }
+  // keep this ABOVE loadRecursorLuaConfig!
+  ret = initDNSSEC(log);
+  if (ret != 0) {
+    return ret;
+  }
+  g_maxCacheEntries = ::arg().asNum("max-cache-entries");
+
+  luaConfigDelayedThreads delayedLuaThreads;
+  try {
+    ProxyMapping proxyMapping;
+    loadRecursorLuaConfig(::arg()["lua-config-file"], delayedLuaThreads, proxyMapping);
+    // Initial proxy mapping
+    g_proxyMapping = proxyMapping.empty() ? nullptr : std::make_unique<ProxyMapping>(proxyMapping);
+  }
+  catch (PDNSException& e) {
+    SLOG(g_log << Logger::Error << "Cannot load Lua configuration: " << e.reason << endl,
+         log->error(Logr::Error, e.reason, "Cannot load Lua configuration"));
+    return 1;
+  }
+
+  parseACLs();
+  initPublicSuffixList(::arg()["public-suffix-list-file"]);
+
+  initDontQuery(log);
+
+  RecThreadInfo::setWeDistributeQueries(::arg().mustDo("pdns-distributes-queries"));
+  if (RecThreadInfo::weDistributeQueries()) {
+    SLOG(g_log << Logger::Warning << "PowerDNS Recursor itself will distribute queries over threads" << endl,
+         log->info(Logr::Notice, "PowerDNS Recursor itself will distribute queries over threads"));
+  }
+
+  g_outgoingEDNSBufsize = ::arg().asNum("edns-outgoing-bufsize");
+
+  if (::arg()["trace"] == "fail") {
+    SyncRes::setDefaultLogMode(SyncRes::Store);
+  }
+  else if (::arg().mustDo("trace")) {
+    SyncRes::setDefaultLogMode(SyncRes::Log);
+    ::arg().set("quiet") = "no";
+    g_quiet = false;
+  }
+  auto myHostname = getHostname();
+  if (!myHostname.has_value()) {
+    SLOG(g_log << Logger::Warning << "Unable to get the hostname, NSID and id.server values will be empty" << endl,
+         log->info(Logr::Warning, "Unable to get the hostname, NSID and id.server values will be empty"));
+  }
+
+  ret = initSyncRes(log, myHostname);
+  if (ret != 0) {
+    return ret;
+  }
+
+  g_proxyProtocolACL.toMasks(::arg()["proxy-protocol-from"]);
+  g_proxyProtocolMaximumSize = ::arg().asNum("proxy-protocol-maximum-size");
+
+  ret = initDNS64(log);
+  if (ret != 0) {
+    return ret;
+  }
+  g_networkTimeoutMsec = ::arg().asNum("network-timeout");
+
+  std::tie(g_initialDomainMap, g_initialAllowNotifyFor) = parseZoneConfiguration();
+
+  g_latencyStatSize = ::arg().asNum("latency-statistic-size");
+
+  g_logCommonErrors = ::arg().mustDo("log-common-errors");
+  g_logRPZChanges = ::arg().mustDo("log-rpz-changes");
+
+  g_anyToTcp = ::arg().mustDo("any-to-tcp");
+  g_udpTruncationThreshold = ::arg().asNum("udp-truncation-threshold");
+
+  g_lowercaseOutgoing = ::arg().mustDo("lowercase-outgoing");
+
+  g_paddingFrom.toMasks(::arg()["edns-padding-from"]);
+  if (::arg()["edns-padding-mode"] == "always") {
+    g_paddingMode = PaddingMode::Always;
+  }
+  else if (::arg()["edns-padding-mode"] == "padded-queries-only") {
+    g_paddingMode = PaddingMode::PaddedQueries;
+  }
+  else {
+    SLOG(g_log << Logger::Error << "Unknown edns-padding-mode: " << ::arg()["edns-padding-mode"] << endl,
+         log->info(Logr::Error, "Unknown edns-padding-mode", "edns-padding-mode", Logging::Loggable(::arg()["edns-padding-mode"])));
+    return 1;
+  }
+  g_paddingTag = ::arg().asNum("edns-padding-tag");
+  g_paddingOutgoing = ::arg().mustDo("edns-padding-out");
+
+  RecThreadInfo::setNumDistributorThreads(::arg().asNum("distributor-threads"));
+  RecThreadInfo::setNumWorkerThreads(::arg().asNum("threads"));
+  if (RecThreadInfo::numWorkers() < 1) {
+    SLOG(g_log << Logger::Warning << "Asked to run with 0 threads, raising to 1 instead" << endl,
+         log->info(Logr::Warning, "Asked to run with 0 threads, raising to 1 instead"));
+    RecThreadInfo::setNumWorkerThreads(1);
+  }
+
+  g_maxMThreads = ::arg().asNum("max-mthreads");
+
+  int64_t maxInFlight = ::arg().asNum("max-concurrent-requests-per-tcp-connection");
+  if (maxInFlight < 1 || maxInFlight > USHRT_MAX || maxInFlight >= g_maxMThreads) {
+    SLOG(g_log << Logger::Warning << "Asked to run with illegal max-concurrent-requests-per-tcp-connection, setting to default (10)" << endl,
+         log->info(Logr::Warning, "Asked to run with illegal max-concurrent-requests-per-tcp-connection, setting to default (10)"));
+    TCPConnection::s_maxInFlight = 10;
+  }
+  else {
+    TCPConnection::s_maxInFlight = maxInFlight;
+  }
+
+  int64_t millis = ::arg().asNum("tcp-out-max-idle-ms");
+  TCPOutConnectionManager::s_maxIdleTime = timeval{millis / 1000, (static_cast<suseconds_t>(millis) % 1000) * 1000};
+  TCPOutConnectionManager::s_maxIdlePerAuth = ::arg().asNum("tcp-out-max-idle-per-auth");
+  TCPOutConnectionManager::s_maxQueries = ::arg().asNum("tcp-out-max-queries");
+  TCPOutConnectionManager::s_maxIdlePerThread = ::arg().asNum("tcp-out-max-idle-per-thread");
+
+  g_gettagNeedsEDNSOptions = ::arg().mustDo("gettag-needs-edns-options");
+
+  s_statisticsInterval = ::arg().asNum("statistics-interval");
+
+  SyncRes::s_addExtendedResolutionDNSErrors = ::arg().mustDo("extended-resolution-errors");
+
+  if (::arg().asNum("aggressive-nsec-cache-size") > 0) {
+    if (g_dnssecmode == DNSSECMode::ValidateAll || g_dnssecmode == DNSSECMode::ValidateForLog || g_dnssecmode == DNSSECMode::Process) {
+      g_aggressiveNSECCache = make_unique<AggressiveNSECCache>(::arg().asNum("aggressive-nsec-cache-size"));
+    }
+    else {
+      SLOG(g_log << Logger::Warning << "Aggressive NSEC/NSEC3 caching is enabled but DNSSEC validation is not set to 'validate', 'log-fail' or 'process', ignoring" << endl,
+           log->info(Logr::Warning, "Aggressive NSEC/NSEC3 caching is enabled but DNSSEC validation is not set to 'validate', 'log-fail' or 'process', ignoring"));
+    }
+  }
+
+  AggressiveNSECCache::s_maxNSEC3CommonPrefix = static_cast<uint8_t>(std::round(std::log2(::arg().asNum("aggressive-cache-min-nsec3-hit-ratio"))));
+  SLOG(g_log << Logger::Debug << "NSEC3 aggressive cache tuning: aggressive-cache-min-nsec3-hit-ratio: " << ::arg().asNum("aggressive-cache-min-nsec3-hit-ratio") << " max common prefix bits: " << std::to_string(AggressiveNSECCache::s_maxNSEC3CommonPrefix) << endl,
+       log->info(Logr::Debug, "NSEC3 aggressive cache tuning", "aggressive-cache-min-nsec3-hit-ratio", Logging::Loggable(::arg().asNum("aggressive-cache-min-nsec3-hit-ratio")), "maxCommonPrefixBits", Logging::Loggable(AggressiveNSECCache::s_maxNSEC3CommonPrefix)));
+
+  initSuffixMatchNodes(log);
+  initCarbon();
+  initDistribution(log);
+
+#ifdef NOD_ENABLED
+  // Setup newly observed domain globals
+  setupNODGlobal();
+#endif /* NOD_ENABLED */
+
+  auto forks = initForks(log);
+
+  checkOrFixFDS(log);
+
+#ifdef HAVE_LIBSODIUM
+  if (sodium_init() == -1) {
+    SLOG(g_log << Logger::Error << "Unable to initialize sodium crypto library" << endl,
+         log->info(Logr::Error, "Unable to initialize sodium crypto library"));
+    return 99;
+  }
+#endif
+
+  openssl_thread_setup();
+  openssl_seed();
+  /* setup rng before chroot */
+  dns_random_init();
+
+  if (::arg()["server-id"].empty()) {
+    ::arg().set("server-id") = myHostname.has_value() ? *myHostname : "";
+  }
+
+  gid_t newgid = 0;
+  if (!::arg()["setgid"].empty()) {
+    newgid = strToGID(::arg()["setgid"]);
+  }
+  uid_t newuid = 0;
+  if (!::arg()["setuid"].empty()) {
+    newuid = strToUID(::arg()["setuid"]);
+  }
+
+  Utility::dropGroupPrivs(newuid, newgid);
+
+  ret = initControl(log, newuid, forks);
+  if (ret != 0) {
+    return ret;
+  }
 
   startLuaConfigDelayedThreads(delayedLuaThreads, g_luaconfs.getCopy().generation);
   delayedLuaThreads.rpzPrimaryThreads.clear(); // no longer needed
@@ -1966,64 +2090,30 @@ static int serviceMain(int /* argc */, char* /* argv */[], Logr::log_t log)
   disableStats(StatComponent::API, ::arg()["stats-api-disabled-list"]);
   disableStats(StatComponent::Carbon, ::arg()["stats-carbon-disabled-list"]);
   disableStats(StatComponent::RecControl, ::arg()["stats-rec-control-disabled-list"]);
-  disableStats(StatComponent::SNMP, ::arg()["stats-snmp-disabled-list"]);
-
-  // Run before any thread doing stats related things
-  registerAllStats();
-
-  if (::arg().mustDo("snmp-agent")) {
-#ifdef HAVE_NET_SNMP
-    string setting = ::arg()["snmp-daemon-socket"];
-    if (setting.empty()) {
-      setting = ::arg()["snmp-master-socket"];
-    }
-    g_snmpAgent = std::make_shared<RecursorSNMPAgent>("recursor", setting);
-    g_snmpAgent->run();
-#else
-    const std::string msg = "snmp-agent set but SNMP support not compiled in";
-    SLOG(g_log << Logger::Error << msg << endl,
-         log->info(Logr::Error, msg));
-#endif // HAVE_NET_SNMP
-  }
+  disableStats(StatComponent::SNMP, ::arg()["stats-snmp-disabled-list"]);
 
-  int port = ::arg().asNum("udp-source-port-min");
-  if (port < 1024 || port > 65535) {
-    SLOG(g_log << Logger::Error << "Unable to launch, udp-source-port-min is not a valid port number" << endl,
-         log->info(Logr::Error, "Unable to launch, udp-source-port-min is not a valid port number"));
-    exit(99); // this isn't going to fix itself either
-  }
-  g_minUdpSourcePort = port;
-  port = ::arg().asNum("udp-source-port-max");
-  if (port < 1024 || port > 65535 || port < g_minUdpSourcePort) {
-    SLOG(g_log << Logger::Error << "Unable to launch, udp-source-port-max is not a valid port number or is smaller than udp-source-port-min" << endl,
-         log->info(Logr::Error, "Unable to launch, udp-source-port-max is not a valid port number or is smaller than udp-source-port-min"));
-    exit(99); // this isn't going to fix itself either
-  }
-  g_maxUdpSourcePort = port;
-  std::vector<string> parts{};
-  stringtok(parts, ::arg()["udp-source-port-avoid"], ", ");
-  for (const auto& part : parts) {
-    port = std::stoi(part);
-    if (port < 1024 || port > 65535) {
-      SLOG(g_log << Logger::Error << "Unable to launch, udp-source-port-avoid contains an invalid port number: " << part << endl,
-           log->info(Logr::Error, "Unable to launch, udp-source-port-avoid contains an invalid port number", "port", Logging::Loggable(part)));
-      exit(99); // this isn't going to fix itself either
-    }
-    g_avoidUdpSourcePorts.insert(port);
+  // Run before any thread doing stats related things
+  registerAllStats();
+
+  initSNMP(log);
+
+  ret = initPorts(log);
+  if (ret != 0) {
+    return ret;
   }
 
   return RecThreadInfo::runThreads(log);
 }
 
-static void handlePipeRequest(int fd, FDMultiplexer::funcparam_t& /* var */)
+static void handlePipeRequest(int fileDesc, FDMultiplexer::funcparam_t& /* var */)
 {
   ThreadMSG* tmsg = nullptr;
 
-  if (read(fd, &tmsg, sizeof(tmsg)) != sizeof(tmsg)) { // fd == readToThread || fd == readQueriesToThread
+  if (read(fileDesc, &tmsg, sizeof(tmsg)) != sizeof(tmsg)) { // fd == readToThread || fd == readQueriesToThread NOLINT: sizeof correct
     unixDie("read from thread pipe returned wrong size or error");
   }
 
-  void* resp = 0;
+  void* resp = nullptr;
   try {
     resp = tmsg->func();
   }
@@ -2041,12 +2131,12 @@ static void handlePipeRequest(int fd, FDMultiplexer::funcparam_t& /* var */)
   }
   if (tmsg->wantAnswer) {
     if (write(RecThreadInfo::self().pipes.writeFromThread, &resp, sizeof(resp)) != sizeof(resp)) {
-      delete tmsg;
+      delete tmsg; // NOLINT: manual ownership handling
       unixDie("write to thread pipe returned wrong size or error");
     }
   }
 
-  delete tmsg;
+  delete tmsg; // NOLINT: manual ownership handling
 }
 
 static void handleRCC(int fd, FDMultiplexer::funcparam_t& /* var */)
@@ -2128,194 +2218,201 @@ private:
   const string name;
 };
 
-static void houseKeeping(void*)
+static void houseKeepingWork(Logr::log_t log) // NOLINT(readability-function-cognitive-complexity) #12791 Remove NOLINT(readability-function-cognitive-complexity) omoerbeek
 {
-  auto log = g_slog->withName("housekeeping");
-  static thread_local bool t_running; // houseKeeping can get suspended in secpoll, and be restarted, which makes us do duplicate work
-
-  try {
-    if (t_running) {
-      return;
-    }
-    t_running = true;
-
-    struct timeval now;
-    Utility::gettimeofday(&now);
-    t_Counters.updateSnap(now, g_regressionTestMode);
+  struct timeval now
+  {
+  };
+  Utility::gettimeofday(&now);
+  t_Counters.updateSnap(now, g_regressionTestMode);
 
-    // Below are the tasks that run for every recursorThread, including handler and taskThread
+  // Below are the tasks that run for every recursorThread, including handler and taskThread
 
-    static thread_local PeriodicTask pruneTCPTask{"pruneTCPTask", 5};
-    pruneTCPTask.runIfDue(now, [now]() {
-      t_tcp_manager.cleanup(now);
-    });
+  static thread_local PeriodicTask pruneTCPTask{"pruneTCPTask", 5};
+  pruneTCPTask.runIfDue(now, [now]() {
+    t_tcp_manager.cleanup(now);
+  });
 
-    const auto& info = RecThreadInfo::self();
+  const auto& info = RecThreadInfo::self();
 
-    // Threads handling packets process config changes in the input path, but not all threads process input packets
-    // distr threads only process TCP, so that may not happenn very often. So do all periodically.
-    static thread_local PeriodicTask exportConfigTask{"exportConfigTask", 30};
-    auto luaconfsLocal = g_luaconfs.getLocal();
-    exportConfigTask.runIfDue(now, [&luaconfsLocal]() {
-      checkProtobufExport(luaconfsLocal);
-      checkOutgoingProtobufExport(luaconfsLocal);
+  // Threads handling packets process config changes in the input path, but not all threads process input packets
+  // distr threads only process TCP, so that may not happenn very often. So do all periodically.
+  static thread_local PeriodicTask exportConfigTask{"exportConfigTask", 30};
+  auto luaconfsLocal = g_luaconfs.getLocal();
+  exportConfigTask.runIfDue(now, [&luaconfsLocal]() {
+    checkProtobufExport(luaconfsLocal);
+    checkOutgoingProtobufExport(luaconfsLocal);
 #ifdef HAVE_FSTRM
-      checkFrameStreamExport(luaconfsLocal, luaconfsLocal->frameStreamExportConfig, t_frameStreamServersInfo);
-      checkFrameStreamExport(luaconfsLocal, luaconfsLocal->nodFrameStreamExportConfig, t_nodFrameStreamServersInfo);
+    checkFrameStreamExport(luaconfsLocal, luaconfsLocal->frameStreamExportConfig, t_frameStreamServersInfo);
+    checkFrameStreamExport(luaconfsLocal, luaconfsLocal->nodFrameStreamExportConfig, t_nodFrameStreamServersInfo);
 #endif
-    });
+  });
 
-    // Below are the thread specific tasks for the handler and the taskThread
-    // Likley a few handler tasks could be moved to the taskThread
-    if (info.isTaskThread()) {
-      // TaskQueue is run always
-      runTasks(10, g_logCommonErrors);
-
-      static PeriodicTask ztcTask{"ZTC", 60};
-      static map<DNSName, RecZoneToCache::State> ztcStates;
-      ztcTask.runIfDue(now, [&luaconfsLocal]() {
-        RecZoneToCache::maintainStates(luaconfsLocal->ztcConfigs, ztcStates, luaconfsLocal->generation);
-        for (auto& ztc : luaconfsLocal->ztcConfigs) {
-          RecZoneToCache::ZoneToCache(ztc.second, ztcStates.at(ztc.first));
-        }
-      });
-    }
-    else if (info.isHandler()) {
-      if (g_packetCache) {
-        static PeriodicTask packetCacheTask{"packetCacheTask", 5};
-        packetCacheTask.runIfDue(now, []() {
-          g_packetCache->doPruneTo(g_maxPacketCacheEntries);
-        });
+  // Below are the thread specific tasks for the handler and the taskThread
+  // Likley a few handler tasks could be moved to the taskThread
+  if (info.isTaskThread()) {
+    // TaskQueue is run always
+    runTasks(10, g_logCommonErrors);
+
+    static PeriodicTask ztcTask{"ZTC", 60};
+    static map<DNSName, RecZoneToCache::State> ztcStates;
+    ztcTask.runIfDue(now, [&luaconfsLocal]() {
+      RecZoneToCache::maintainStates(luaconfsLocal->ztcConfigs, ztcStates, luaconfsLocal->generation);
+      for (const auto& ztc : luaconfsLocal->ztcConfigs) {
+        RecZoneToCache::ZoneToCache(ztc.second, ztcStates.at(ztc.first));
       }
-      static PeriodicTask recordCachePruneTask{"RecordCachePruneTask", 5};
-      recordCachePruneTask.runIfDue(now, []() {
-        g_recCache->doPrune(g_maxCacheEntries);
+    });
+  }
+  else if (info.isHandler()) {
+    if (g_packetCache) {
+      static PeriodicTask packetCacheTask{"packetCacheTask", 5};
+      packetCacheTask.runIfDue(now, []() {
+        g_packetCache->doPruneTo(g_maxPacketCacheEntries);
       });
+    }
+    static PeriodicTask recordCachePruneTask{"RecordCachePruneTask", 5};
+    recordCachePruneTask.runIfDue(now, []() {
+      g_recCache->doPrune(g_maxCacheEntries);
+    });
 
-      static PeriodicTask negCachePruneTask{"NegCachePrunteTask", 5};
-      negCachePruneTask.runIfDue(now, []() {
-        g_negCache->prune(g_maxCacheEntries / 8);
-      });
+    static PeriodicTask negCachePruneTask{"NegCachePrunteTask", 5};
+    negCachePruneTask.runIfDue(now, []() {
+      g_negCache->prune(g_maxCacheEntries / 8);
+    });
 
-      static PeriodicTask aggrNSECPruneTask{"AggrNSECPruneTask", 5};
-      aggrNSECPruneTask.runIfDue(now, [now]() {
-        if (g_aggressiveNSECCache) {
-          g_aggressiveNSECCache->prune(now.tv_sec);
-        }
-      });
+    static PeriodicTask aggrNSECPruneTask{"AggrNSECPruneTask", 5};
+    aggrNSECPruneTask.runIfDue(now, [now]() {
+      if (g_aggressiveNSECCache) {
+        g_aggressiveNSECCache->prune(now.tv_sec);
+      }
+    });
 
-      static PeriodicTask pruneNSpeedTask{"pruneNSSpeedTask", 30};
-      pruneNSpeedTask.runIfDue(now, [now]() {
-        SyncRes::pruneNSSpeeds(now.tv_sec - 300);
-      });
+    static PeriodicTask pruneNSpeedTask{"pruneNSSpeedTask", 30};
+    pruneNSpeedTask.runIfDue(now, [now]() {
+      SyncRes::pruneNSSpeeds(now.tv_sec - 300);
+    });
+
+    static PeriodicTask pruneEDNSTask{"pruneEDNSTask", 60};
+    pruneEDNSTask.runIfDue(now, [now]() {
+      SyncRes::pruneEDNSStatuses(now.tv_sec);
+    });
 
-      static PeriodicTask pruneEDNSTask{"pruneEDNSTask", 60};
-      pruneEDNSTask.runIfDue(now, [now]() {
-        SyncRes::pruneEDNSStatuses(now.tv_sec);
+    if (SyncRes::s_max_busy_dot_probes > 0) {
+      static PeriodicTask pruneDoTProbeMap{"pruneDoTProbeMapTask", 60};
+      pruneDoTProbeMap.runIfDue(now, [now]() {
+        SyncRes::pruneDoTProbeMap(now.tv_sec);
       });
+    }
 
-      if (SyncRes::s_max_busy_dot_probes > 0) {
-        static PeriodicTask pruneDoTProbeMap{"pruneDoTProbeMapTask", 60};
-        pruneDoTProbeMap.runIfDue(now, [now]() {
-          SyncRes::pruneDoTProbeMap(now.tv_sec);
-        });
-      }
+    static PeriodicTask pruneThrottledTask{"pruneThrottledTask", 5};
+    pruneThrottledTask.runIfDue(now, [now]() {
+      SyncRes::pruneThrottledServers(now.tv_sec);
+    });
 
-      static PeriodicTask pruneThrottledTask{"pruneThrottledTask", 5};
-      pruneThrottledTask.runIfDue(now, [now]() {
-        SyncRes::pruneThrottledServers(now.tv_sec);
-      });
+    static PeriodicTask pruneFailedServersTask{"pruneFailedServerTask", 5};
+    pruneFailedServersTask.runIfDue(now, [now]() {
+      SyncRes::pruneFailedServers(now.tv_sec - static_cast<time_t>(SyncRes::s_serverdownthrottletime * 10));
+    });
 
-      static PeriodicTask pruneFailedServersTask{"pruneFailedServerTask", 5};
-      pruneFailedServersTask.runIfDue(now, [now]() {
-        SyncRes::pruneFailedServers(now.tv_sec - SyncRes::s_serverdownthrottletime * 10);
-      });
+    static PeriodicTask pruneNonResolvingTask{"pruneNonResolvingTask", 5};
+    pruneNonResolvingTask.runIfDue(now, [now]() {
+      SyncRes::pruneNonResolving(now.tv_sec - SyncRes::s_nonresolvingnsthrottletime);
+    });
 
-      static PeriodicTask pruneNonResolvingTask{"pruneNonResolvingTask", 5};
-      pruneNonResolvingTask.runIfDue(now, [now]() {
-        SyncRes::pruneNonResolving(now.tv_sec - SyncRes::s_nonresolvingnsthrottletime);
-      });
+    static PeriodicTask pruneSaveParentSetTask{"pruneSaveParentSetTask", 60};
+    pruneSaveParentSetTask.runIfDue(now, [now]() {
+      SyncRes::pruneSaveParentsNSSets(now.tv_sec);
+    });
 
-      static PeriodicTask pruneSaveParentSetTask{"pruneSaveParentSetTask", 60};
-      pruneSaveParentSetTask.runIfDue(now, [now]() {
-        SyncRes::pruneSaveParentsNSSets(now.tv_sec);
-      });
+    // By default, refresh at 80% of max-cache-ttl with a minimum period of 10s
+    const unsigned int minRootRefreshInterval = 10;
+    static PeriodicTask rootUpdateTask{"rootUpdateTask", std::max(SyncRes::s_maxcachettl * 8 / 10, minRootRefreshInterval)};
+    rootUpdateTask.runIfDue(now, [now, &log, minRootRefreshInterval]() {
+      int res = 0;
+      if (!g_regressionTestMode) {
+        res = SyncRes::getRootNS(now, nullptr, 0, log);
+      }
+      if (res == 0) {
+        // Success, go back to the defaut period
+        rootUpdateTask.setPeriod(std::max(SyncRes::s_maxcachettl * 8 / 10, minRootRefreshInterval));
+      }
+      else {
+        // On failure, go to the middle of the remaining period (initially 80% / 8 = 10%) and shorten the interval on each
+        // failure by dividing the existing interval by 8, keeping the minimum interval at 10s.
+        // So with a 1 day period and failures we'll see a refresh attempt at 69120, 69120+11520, 69120+11520+1440, ...
+        rootUpdateTask.setPeriod(std::max<time_t>(rootUpdateTask.getPeriod() / 8, minRootRefreshInterval));
+      }
+    });
 
-      // By default, refresh at 80% of max-cache-ttl with a minimum period of 10s
-      const unsigned int minRootRefreshInterval = 10;
-      static PeriodicTask rootUpdateTask{"rootUpdateTask", std::max(SyncRes::s_maxcachettl * 8 / 10, minRootRefreshInterval)};
-      rootUpdateTask.runIfDue(now, [now, &log, minRootRefreshInterval]() {
-        int res = 0;
-        if (!g_regressionTestMode) {
-          res = SyncRes::getRootNS(now, nullptr, 0, log);
-        }
-        if (res == 0) {
-          // Success, go back to the defaut period
-          rootUpdateTask.setPeriod(std::max(SyncRes::s_maxcachettl * 8 / 10, minRootRefreshInterval));
-        }
-        else {
-          // On failure, go to the middle of the remaining period (initially 80% / 8 = 10%) and shorten the interval on each
-          // failure by dividing the existing interval by 8, keeping the minimum interval at 10s.
-          // So with a 1 day period and failures we'll see a refresh attempt at 69120, 69120+11520, 69120+11520+1440, ...
-          rootUpdateTask.setPeriod(std::max<time_t>(rootUpdateTask.getPeriod() / 8, minRootRefreshInterval));
-        }
-      });
+    static PeriodicTask secpollTask{"secpollTask", 3600};
+    static time_t t_last_secpoll;
+    secpollTask.runIfDue(now, [&log]() {
+      try {
+        doSecPoll(&t_last_secpoll, log);
+      }
+      catch (const std::exception& e) {
+        SLOG(g_log << Logger::Error << "Exception while performing security poll: " << e.what() << endl,
+             log->error(Logr::Error, e.what(), "Exception while performing security poll"));
+      }
+      catch (const PDNSException& e) {
+        SLOG(g_log << Logger::Error << "Exception while performing security poll: " << e.reason << endl,
+             log->error(Logr::Error, e.reason, "Exception while performing security poll"));
+      }
+      catch (const ImmediateServFailException& e) {
+        SLOG(g_log << Logger::Error << "Exception while performing security poll: " << e.reason << endl,
+             log->error(Logr::Error, e.reason, "Exception while performing security poll"));
+      }
+      catch (const PolicyHitException& e) {
+        SLOG(g_log << Logger::Error << "Policy hit while performing security poll" << endl,
+             log->info(Logr::Error, "Policy hit while performing security poll"));
+      }
+      catch (...) {
+        SLOG(g_log << Logger::Error << "Exception while performing security poll" << endl,
+             log->info(Logr::Error, "Exception while performing security poll"));
+      }
+    });
 
-      static PeriodicTask secpollTask{"secpollTask", 3600};
-      static time_t t_last_secpoll;
-      secpollTask.runIfDue(now, [&log]() {
+    const time_t taInterval = std::max(1, static_cast<int>(luaconfsLocal->trustAnchorFileInfo.interval) * 3600);
+    static PeriodicTask trustAnchorTask{"trustAnchorTask", taInterval};
+    if (!trustAnchorTask.hasRun()) {
+      // Loading the Lua config file already "refreshed" the TAs
+      trustAnchorTask.updateLastRun();
+    }
+    // interval might have ben updated
+    trustAnchorTask.setPeriod(taInterval);
+    trustAnchorTask.runIfDue(now, [&luaconfsLocal, &log]() {
+      if (!luaconfsLocal->trustAnchorFileInfo.fname.empty() && luaconfsLocal->trustAnchorFileInfo.interval != 0) {
+        SLOG(g_log << Logger::Debug << "Refreshing Trust Anchors from file" << endl,
+             log->info(Logr::Debug, "Refreshing Trust Anchors from file"));
         try {
-          doSecPoll(&t_last_secpoll, log);
-        }
-        catch (const std::exception& e) {
-          SLOG(g_log << Logger::Error << "Exception while performing security poll: " << e.what() << endl,
-               log->error(Logr::Error, e.what(), "Exception while performing security poll"));
-        }
-        catch (const PDNSException& e) {
-          SLOG(g_log << Logger::Error << "Exception while performing security poll: " << e.reason << endl,
-               log->error(Logr::Error, e.reason, "Exception while performing security poll"));
-        }
-        catch (const ImmediateServFailException& e) {
-          SLOG(g_log << Logger::Error << "Exception while performing security poll: " << e.reason << endl,
-               log->error(Logr::Error, e.reason, "Exception while performing security poll"));
-        }
-        catch (const PolicyHitException& e) {
-          SLOG(g_log << Logger::Error << "Policy hit while performing security poll" << endl,
-               log->info(Logr::Error, "Policy hit while performing security poll"));
+          map<DNSName, dsmap_t> dsAnchors;
+          if (updateTrustAnchorsFromFile(luaconfsLocal->trustAnchorFileInfo.fname, dsAnchors, log)) {
+            g_luaconfs.modify([&dsAnchors](LuaConfigItems& lci) {
+              lci.dsAnchors = dsAnchors;
+            });
+          }
         }
-        catch (...) {
-          SLOG(g_log << Logger::Error << "Exception while performing security poll" << endl,
-               log->info(Logr::Error, "Exception while performing security poll"));
+        catch (const PDNSException& pe) {
+          SLOG(g_log << Logger::Error << "Unable to update Trust Anchors: " << pe.reason << endl,
+               log->error(Logr::Error, pe.reason, "Unable to update Trust Anchors"));
         }
-      });
-
-      static PeriodicTask trustAnchorTask{"trustAnchorTask", std::max(1U, luaconfsLocal->trustAnchorFileInfo.interval) * 3600};
-      if (!trustAnchorTask.hasRun()) {
-        // Loading the Lua config file already "refreshed" the TAs
-        trustAnchorTask.updateLastRun();
       }
-      // interval might have ben updated
-      trustAnchorTask.setPeriod(std::max(1U, luaconfsLocal->trustAnchorFileInfo.interval) * 3600);
-      trustAnchorTask.runIfDue(now, [&luaconfsLocal, &log]() {
-        if (!luaconfsLocal->trustAnchorFileInfo.fname.empty() && luaconfsLocal->trustAnchorFileInfo.interval != 0) {
-          SLOG(g_log << Logger::Debug << "Refreshing Trust Anchors from file" << endl,
-               log->info(Logr::Debug, "Refreshing Trust Anchors from file"));
-          try {
-            map<DNSName, dsmap_t> dsAnchors;
-            if (updateTrustAnchorsFromFile(luaconfsLocal->trustAnchorFileInfo.fname, dsAnchors, log)) {
-              g_luaconfs.modify([&dsAnchors](LuaConfigItems& lci) {
-                lci.dsAnchors = dsAnchors;
-              });
-            }
-          }
-          catch (const PDNSException& pe) {
-            SLOG(g_log << Logger::Error << "Unable to update Trust Anchors: " << pe.reason << endl,
-                 log->error(Logr::Error, pe.reason, "Unable to update Trust Anchors"));
-          }
-        }
-      });
+    });
+  }
+  t_Counters.updateSnap(g_regressionTestMode);
+}
+
+static void houseKeeping(void* /* ignored */)
+{
+  auto log = g_slog->withName("housekeeping");
+  static thread_local bool t_running; // houseKeeping can get suspended in secpoll, and be restarted, which makes us do duplicate work
+
+  try {
+    if (t_running) {
+      return;
     }
-    t_Counters.updateSnap(g_regressionTestMode);
+    t_running = true;
+    houseKeepingWork(log);
     t_running = false;
   }
   catch (const PDNSException& ae) {
@@ -2332,7 +2429,126 @@ static void houseKeeping(void*)
   }
 }
 
-static void recursorThread()
+static void runLuaMaintenance(RecThreadInfo& threadInfo, time_t& last_lua_maintenance, time_t luaMaintenanceInterval)
+{
+  if (t_pdl != nullptr) {
+    // lua-dns-script directive is present, call the maintenance callback if needed
+    /* remember that the listener threads handle TCP queries */
+    if (threadInfo.isWorker() || threadInfo.isListener()) {
+      // Only on threads processing queries
+      if (g_now.tv_sec - last_lua_maintenance >= luaMaintenanceInterval) {
+        struct timeval start
+        {
+        };
+        Utility::gettimeofday(&start);
+        t_pdl->maintenance();
+        last_lua_maintenance = g_now.tv_sec;
+        struct timeval stop
+        {
+        };
+        Utility::gettimeofday(&stop);
+        t_Counters.at(rec::Counter::maintenanceUsec) += uSec(stop - start);
+        ++t_Counters.at(rec::Counter::maintenanceCalls);
+      }
+    }
+  }
+}
+
+static void runTCPMaintenance(RecThreadInfo& threadInfo, bool& listenOnTCP, unsigned int maxTcpClients)
+{
+  if (threadInfo.isListener()) {
+    if (listenOnTCP) {
+      if (TCPConnection::getCurrentConnections() > maxTcpClients) { // shutdown, too many connections
+        for (const auto fileDesc : threadInfo.tcpSockets) {
+          t_fdm->removeReadFD(fileDesc);
+        }
+        listenOnTCP = false;
+      }
+    }
+    else {
+      if (TCPConnection::getCurrentConnections() <= maxTcpClients) { // reenable
+        for (const auto fileDesc : threadInfo.tcpSockets) {
+          t_fdm->addReadFD(fileDesc, handleNewTCPQuestion);
+        }
+        listenOnTCP = true;
+      }
+    }
+  }
+}
+
+static void recLoop()
+{
+  unsigned int maxTcpClients = ::arg().asNum("max-tcp-clients");
+  bool listenOnTCP{true};
+  time_t last_stat = 0;
+  time_t last_carbon = 0;
+  time_t last_lua_maintenance = 0;
+  time_t carbonInterval = ::arg().asNum("carbon-interval");
+  time_t luaMaintenanceInterval = ::arg().asNum("lua-maintenance-interval");
+
+  auto& threadInfo = RecThreadInfo::self();
+
+  while (!RecursorControlChannel::stop) {
+    while (MT->schedule(&g_now)) {
+      ; // MTasker letting the mthreads do their thing
+    }
+
+    // Use primes, it avoid not being scheduled in cases where the counter has a regular pattern.
+    // We want to call handler thread often, it gets scheduled about 2 times per second
+    if (((threadInfo.isHandler() || threadInfo.isTaskThread()) && s_counter % 11 == 0) || s_counter % 499 == 0) {
+      struct timeval start
+      {
+      };
+      Utility::gettimeofday(&start);
+      MT->makeThread(houseKeeping, nullptr);
+      if (!threadInfo.isTaskThread()) {
+        struct timeval stop
+        {
+        };
+        Utility::gettimeofday(&stop);
+        t_Counters.at(rec::Counter::maintenanceUsec) += uSec(stop - start);
+        ++t_Counters.at(rec::Counter::maintenanceCalls);
+      }
+    }
+
+    if (s_counter % 55 == 0) {
+      auto expired = t_fdm->getTimeouts(g_now);
+
+      for (const auto& exp : expired) {
+        auto conn = boost::any_cast<shared_ptr<TCPConnection>>(exp.second);
+        if (g_logCommonErrors) {
+          SLOG(g_log << Logger::Warning << "Timeout from remote TCP client " << conn->d_remote.toStringWithPort() << endl, // NOLINT: union access
+               g_slogtcpin->info(Logr::Warning, "Timeout from remote TCP client", "remote", Logging::Loggable(conn->d_remote)));
+        }
+        t_fdm->removeReadFD(exp.first);
+      }
+    }
+
+    s_counter++;
+
+    if (threadInfo.isHandler()) {
+      if (statsWanted || (s_statisticsInterval > 0 && (g_now.tv_sec - last_stat) >= s_statisticsInterval)) {
+        doStats();
+        last_stat = g_now.tv_sec;
+      }
+
+      Utility::gettimeofday(&g_now, nullptr);
+
+      if ((g_now.tv_sec - last_carbon) >= carbonInterval) {
+        MT->makeThread(doCarbonDump, nullptr);
+        last_carbon = g_now.tv_sec;
+      }
+    }
+    runLuaMaintenance(threadInfo, last_lua_maintenance, luaMaintenanceInterval);
+
+    t_fdm->run(&g_now);
+    // 'run' updates g_now for us
+
+    runTCPMaintenance(threadInfo, listenOnTCP, maxTcpClients);
+  }
+}
+
+static void recursorThread() // NOLINT(readability-function-cognitive-complexity) #12791 Remove NOLINT(readability-function-cognitive-complexity) omoerbeek
 {
   auto log = g_slog->withName("runtime");
   t_Counters.updateSnap(true);
@@ -2356,7 +2572,7 @@ static void recursorThread()
       if (threadInfo.isHandler()) {
         if (!primeHints()) {
           threadInfo.setExitCode(EXIT_FAILURE);
-          RecursorControlChannel::stop = 1;
+          RecursorControlChannel::stop = true;
           SLOG(g_log << Logger::Critical << "Priming cache failed, stopping" << endl,
                log->info(Logr::Critical, "Priming cache failed, stopping"));
         }
@@ -2366,8 +2582,9 @@ static void recursorThread()
     }
 
 #ifdef NOD_ENABLED
-    if (threadInfo.isWorker())
+    if (threadInfo.isWorker()) {
       setupNODThread(log);
+    }
 #endif /* NOD_ENABLED */
 
     /* the listener threads handle TCP queries */
@@ -2388,12 +2605,14 @@ static void recursorThread()
     }
 
     unsigned int ringsize = ::arg().asNum("stats-ringbuffer-entries") / RecThreadInfo::numWorkers();
-    if (ringsize) {
+    if (ringsize != 0) {
       t_remotes = std::make_unique<addrringbuf_t>();
-      if (RecThreadInfo::weDistributeQueries())
+      if (RecThreadInfo::weDistributeQueries()) {
         t_remotes->set_capacity(::arg().asNum("stats-ringbuffer-entries") / RecThreadInfo::numDistributors());
-      else
+      }
+      else {
         t_remotes->set_capacity(ringsize);
+      }
       t_servfailremotes = std::make_unique<addrringbuf_t>();
       t_servfailremotes->set_capacity(ringsize);
       t_bogusremotes = std::make_unique<addrringbuf_t>();
@@ -2467,15 +2686,6 @@ static void recursorThread()
       t_fdm->addReadFD(g_rcc.d_fd, handleRCC); // control channel
     }
 
-    unsigned int maxTcpClients = ::arg().asNum("max-tcp-clients");
-
-    bool listenOnTCP{true};
-
-    time_t last_stat = 0;
-    time_t last_carbon = 0, last_lua_maintenance = 0;
-    time_t carbonInterval = ::arg().asNum("carbon-interval");
-    time_t luaMaintenanceInterval = ::arg().asNum("lua-maintenance-interval");
-
 #ifdef HAVE_SYSTEMD
     if (threadInfo.isHandler()) {
       // There is a race, as some threads might not be ready yet to do work.
@@ -2484,92 +2694,8 @@ static void recursorThread()
       sd_notify(0, "READY=1");
     }
 #endif
-    while (!RecursorControlChannel::stop) {
-      while (MT->schedule(&g_now))
-        ; // MTasker letting the mthreads do their thing
-
-      // Use primes, it avoid not being scheduled in cases where the counter has a regular pattern.
-      // We want to call handler thread often, it gets scheduled about 2 times per second
-      if (((threadInfo.isHandler() || threadInfo.isTaskThread()) && s_counter % 11 == 0) || s_counter % 499 == 0) {
-        struct timeval start;
-        Utility::gettimeofday(&start);
-        MT->makeThread(houseKeeping, nullptr);
-        if (!threadInfo.isTaskThread()) {
-          struct timeval stop;
-          Utility::gettimeofday(&stop);
-          t_Counters.at(rec::Counter::maintenanceUsec) += uSec(stop - start);
-          ++t_Counters.at(rec::Counter::maintenanceCalls);
-        }
-      }
-
-      if (!(s_counter % 55)) {
-        typedef vector<pair<int, FDMultiplexer::funcparam_t>> expired_t;
-        expired_t expired = t_fdm->getTimeouts(g_now);
-
-        for (expired_t::iterator i = expired.begin(); i != expired.end(); ++i) {
-          shared_ptr<TCPConnection> conn = boost::any_cast<shared_ptr<TCPConnection>>(i->second);
-          if (g_logCommonErrors)
-            SLOG(g_log << Logger::Warning << "Timeout from remote TCP client " << conn->d_remote.toStringWithPort() << endl,
-                 g_slogtcpin->info(Logr::Warning, "Timeout from remote TCP client", "remote", Logging::Loggable(conn->d_remote)));
-          t_fdm->removeReadFD(i->first);
-        }
-      }
-
-      s_counter++;
-
-      if (threadInfo.isHandler()) {
-        if (statsWanted || (s_statisticsInterval > 0 && (g_now.tv_sec - last_stat) >= s_statisticsInterval)) {
-          doStats();
-          last_stat = g_now.tv_sec;
-        }
-
-        Utility::gettimeofday(&g_now, nullptr);
 
-        if ((g_now.tv_sec - last_carbon) >= carbonInterval) {
-          MT->makeThread(doCarbonDump, 0);
-          last_carbon = g_now.tv_sec;
-        }
-      }
-      if (t_pdl != nullptr) {
-        // lua-dns-script directive is present, call the maintenance callback if needed
-        /* remember that the listener threads handle TCP queries */
-        if (threadInfo.isWorker() || threadInfo.isListener()) {
-          // Only on threads processing queries
-          if (g_now.tv_sec - last_lua_maintenance >= luaMaintenanceInterval) {
-            struct timeval start;
-            Utility::gettimeofday(&start);
-            t_pdl->maintenance();
-            last_lua_maintenance = g_now.tv_sec;
-            struct timeval stop;
-            Utility::gettimeofday(&stop);
-            t_Counters.at(rec::Counter::maintenanceUsec) += uSec(stop - start);
-            ++t_Counters.at(rec::Counter::maintenanceCalls);
-          }
-        }
-      }
-
-      t_fdm->run(&g_now);
-      // 'run' updates g_now for us
-
-      if (threadInfo.isListener()) {
-        if (listenOnTCP) {
-          if (TCPConnection::getCurrentConnections() > maxTcpClients) { // shutdown, too many connections
-            for (const auto fd : threadInfo.tcpSockets) {
-              t_fdm->removeReadFD(fd);
-            }
-            listenOnTCP = false;
-          }
-        }
-        else {
-          if (TCPConnection::getCurrentConnections() <= maxTcpClients) { // reenable
-            for (const auto fd : threadInfo.tcpSockets) {
-              t_fdm->addReadFD(fd, handleNewTCPQuestion);
-            }
-            listenOnTCP = true;
-          }
-        }
-      }
-    }
+    recLoop();
   }
   catch (PDNSException& ae) {
     SLOG(g_log << Logger::Error << "Exception: " << ae.reason << endl,
@@ -2585,298 +2711,349 @@ static void recursorThread()
   }
 }
 
-int main(int argc, char** argv)
+static void initArgs()
 {
-  g_argc = argc;
-  g_argv = argv;
-  Utility::srandom();
-  versionSetProduct(ProductRecursor);
-  reportBasicTypes();
-  reportOtherTypes();
-
-  int ret = EXIT_SUCCESS;
-
-  try {
 #if HAVE_FIBER_SANITIZER
-    // Asan needs more stack
-    ::arg().set("stack-size", "stack size per mthread") = "600000";
+  // Asan needs more stack
+  ::arg().set("stack-size", "stack size per mthread") = "600000";
 #else
-    ::arg().set("stack-size", "stack size per mthread") = "200000";
+  ::arg().set("stack-size", "stack size per mthread") = "200000";
 #endif
-    ::arg().set("stack-cache-size", "Size of the stack cache, per mthread") = "100";
-    // This mode forces metrics snap updates and disable root-refresh, to get consistent counters
-    ::arg().setSwitch("devonly-regression-test-mode", "internal use only") = "no";
-    ::arg().set("soa-minimum-ttl", "Don't change") = "0";
-    ::arg().set("no-shuffle", "Don't change") = "off";
-    ::arg().set("local-port", "port to listen on") = "53";
-    ::arg().set("local-address", "IP addresses to listen on, separated by spaces or commas. Also accepts ports.") = "127.0.0.1";
-    ::arg().setSwitch("non-local-bind", "Enable binding to non-local addresses by using FREEBIND / BINDANY socket options") = "no";
-    ::arg().set("trace", "if we should output heaps of logging. set to 'fail' to only log failing domains") = "off";
-    ::arg().set("dnssec", "DNSSEC mode: off/process-no-validate/process (default)/log-fail/validate") = "process";
-    ::arg().set("dnssec-log-bogus", "Log DNSSEC bogus validations") = "no";
-    ::arg().set("signature-inception-skew", "Allow the signature inception to be off by this number of seconds") = "60";
-    ::arg().set("daemon", "Operate as a daemon") = "no";
-    ::arg().setSwitch("write-pid", "Write a PID file") = "yes";
-    ::arg().set("loglevel", "Amount of logging. Higher is more. Do not set below 3") = "6";
-    ::arg().set("disable-syslog", "Disable logging to syslog, useful when running inside a supervisor that logs stdout") = "no";
-    ::arg().set("log-timestamp", "Print timestamps in log lines, useful to disable when running with a tool that timestamps stdout already") = "yes";
-    ::arg().set("log-common-errors", "If we should log rather common errors") = "no";
-    ::arg().set("chroot", "switch to chroot jail") = "";
-    ::arg().set("setgid", "If set, change group id to this gid for more security"
+  ::arg().set("stack-cache-size", "Size of the stack cache, per mthread") = "100";
+  // This mode forces metrics snap updates and disable root-refresh, to get consistent counters
+  ::arg().setSwitch("devonly-regression-test-mode", "internal use only") = "no";
+  ::arg().set("soa-minimum-ttl", "Don't change") = "0";
+  ::arg().set("no-shuffle", "Don't change") = "off";
+  ::arg().set("local-port", "port to listen on") = "53";
+  ::arg().set("local-address", "IP addresses to listen on, separated by spaces or commas. Also accepts ports.") = "127.0.0.1";
+  ::arg().setSwitch("non-local-bind", "Enable binding to non-local addresses by using FREEBIND / BINDANY socket options") = "no";
+  ::arg().set("trace", "if we should output heaps of logging. set to 'fail' to only log failing domains") = "off";
+  ::arg().set("dnssec", "DNSSEC mode: off/process-no-validate/process (default)/log-fail/validate") = "process";
+  ::arg().set("dnssec-log-bogus", "Log DNSSEC bogus validations") = "no";
+  ::arg().set("signature-inception-skew", "Allow the signature inception to be off by this number of seconds") = "60";
+  ::arg().set("daemon", "Operate as a daemon") = "no";
+  ::arg().setSwitch("write-pid", "Write a PID file") = "yes";
+  ::arg().set("loglevel", "Amount of logging. Higher is more. Do not set below 3") = "6";
+  ::arg().set("disable-syslog", "Disable logging to syslog, useful when running inside a supervisor that logs stdout") = "no";
+  ::arg().set("log-timestamp", "Print timestamps in log lines, useful to disable when running with a tool that timestamps stdout already") = "yes";
+  ::arg().set("log-common-errors", "If we should log rather common errors") = "no";
+  ::arg().set("chroot", "switch to chroot jail") = "";
+  ::arg().set("setgid", "If set, change group id to this gid for more security"
 #ifdef HAVE_SYSTEMD
 #define SYSTEMD_SETID_MSG ". When running inside systemd, use the User and Group settings in the unit-file!"
-                SYSTEMD_SETID_MSG
+              SYSTEMD_SETID_MSG
 #endif
-                )
-      = "";
-    ::arg().set("setuid", "If set, change user id to this uid for more security"
+              )
+    = "";
+  ::arg().set("setuid", "If set, change user id to this uid for more security"
 #ifdef HAVE_SYSTEMD
-                SYSTEMD_SETID_MSG
+              SYSTEMD_SETID_MSG
 #endif
-                )
-      = "";
-    ::arg().set("network-timeout", "Wait this number of milliseconds for network i/o") = "1500";
-    ::arg().set("threads", "Launch this number of threads") = "2";
-    ::arg().set("distributor-threads", "Launch this number of distributor threads, distributing queries to other threads") = "0";
-    ::arg().set("processes", "Launch this number of processes (EXPERIMENTAL, DO NOT CHANGE)") = "1"; // if we un-experimental this, need to fix openssl rand seeding for multiple PIDs!
-    ::arg().set("config-name", "Name of this virtual configuration - will rename the binary image") = "";
-    ::arg().set("api-config-dir", "Directory where REST API stores config and zones") = "";
-    ::arg().set("api-key", "Static pre-shared authentication key for access to the REST API") = "";
-    ::arg().setSwitch("webserver", "Start a webserver (for REST API)") = "no";
-    ::arg().set("webserver-address", "IP Address of webserver to listen on") = "127.0.0.1";
-    ::arg().set("webserver-port", "Port of webserver to listen on") = "8082";
-    ::arg().set("webserver-password", "Password required for accessing the webserver") = "";
-    ::arg().set("webserver-allow-from", "Webserver access is only allowed from these subnets") = "127.0.0.1,::1";
-    ::arg().set("webserver-loglevel", "Amount of logging in the webserver (none, normal, detailed)") = "normal";
-    ::arg().setSwitch("webserver-hash-plaintext-credentials", "Whether to hash passwords and api keys supplied in plaintext, to prevent keeping the plaintext version in memory at runtime") = "no";
-    ::arg().set("carbon-ourname", "If set, overrides our reported hostname for carbon stats") = "";
-    ::arg().set("carbon-server", "If set, send metrics in carbon (graphite) format to this server IP address") = "";
-    ::arg().set("carbon-interval", "Number of seconds between carbon (graphite) updates") = "30";
-    ::arg().set("carbon-namespace", "If set overwrites the first part of the carbon string") = "pdns";
-    ::arg().set("carbon-instance", "If set overwrites the instance name default") = "recursor";
-
-    ::arg().set("statistics-interval", "Number of seconds between printing of recursor statistics, 0 to disable") = "1800";
-    ::arg().set("quiet", "Suppress logging of questions and answers") = "";
-    ::arg().set("logging-facility", "Facility to log messages as. 0 corresponds to local0") = "";
-    ::arg().set("config-dir", "Location of configuration directory (recursor.conf)") = SYSCONFDIR;
-    ::arg().set("socket-owner", "Owner of socket") = "";
-    ::arg().set("socket-group", "Group of socket") = "";
-    ::arg().set("socket-mode", "Permissions for socket") = "";
-
-    ::arg().set("socket-dir", string("Where the controlsocket will live, ") + LOCALSTATEDIR + "/pdns-recursor when unset and not chrooted"
+              )
+    = "";
+  ::arg().set("network-timeout", "Wait this number of milliseconds for network i/o") = "1500";
+  ::arg().set("threads", "Launch this number of threads") = "2";
+  ::arg().set("distributor-threads", "Launch this number of distributor threads, distributing queries to other threads") = "0";
+  ::arg().set("processes", "Launch this number of processes (EXPERIMENTAL, DO NOT CHANGE)") = "1"; // if we un-experimental this, need to fix openssl rand seeding for multiple PIDs!
+  ::arg().set("config-name", "Name of this virtual configuration - will rename the binary image") = "";
+  ::arg().set("api-config-dir", "Directory where REST API stores config and zones") = "";
+  ::arg().set("api-key", "Static pre-shared authentication key for access to the REST API") = "";
+  ::arg().setSwitch("webserver", "Start a webserver (for REST API)") = "no";
+  ::arg().set("webserver-address", "IP Address of webserver to listen on") = "127.0.0.1";
+  ::arg().set("webserver-port", "Port of webserver to listen on") = "8082";
+  ::arg().set("webserver-password", "Password required for accessing the webserver") = "";
+  ::arg().set("webserver-allow-from", "Webserver access is only allowed from these subnets") = "127.0.0.1,::1";
+  ::arg().set("webserver-loglevel", "Amount of logging in the webserver (none, normal, detailed)") = "normal";
+  ::arg().setSwitch("webserver-hash-plaintext-credentials", "Whether to hash passwords and api keys supplied in plaintext, to prevent keeping the plaintext version in memory at runtime") = "no";
+  ::arg().set("carbon-ourname", "If set, overrides our reported hostname for carbon stats") = "";
+  ::arg().set("carbon-server", "If set, send metrics in carbon (graphite) format to this server IP address") = "";
+  ::arg().set("carbon-interval", "Number of seconds between carbon (graphite) updates") = "30";
+  ::arg().set("carbon-namespace", "If set overwrites the first part of the carbon string") = "pdns";
+  ::arg().set("carbon-instance", "If set overwrites the instance name default") = "recursor";
+
+  ::arg().set("statistics-interval", "Number of seconds between printing of recursor statistics, 0 to disable") = "1800";
+  ::arg().set("quiet", "Suppress logging of questions and answers") = "";
+  ::arg().set("logging-facility", "Facility to log messages as. 0 corresponds to local0") = "";
+  ::arg().set("config-dir", "Location of configuration directory (recursor.conf)") = SYSCONFDIR;
+  ::arg().set("socket-owner", "Owner of socket") = "";
+  ::arg().set("socket-group", "Group of socket") = "";
+  ::arg().set("socket-mode", "Permissions for socket") = "";
+
+  ::arg().set("socket-dir", string("Where the controlsocket will live, ") + LOCALSTATEDIR + "/pdns-recursor when unset and not chrooted"
 #ifdef HAVE_SYSTEMD
-                  + ". Set to the RUNTIME_DIRECTORY environment variable when that variable has a value (e.g. under systemd).")
-      = "";
-    auto runtimeDir = getenv("RUNTIME_DIRECTORY");
-    if (runtimeDir != nullptr) {
-      ::arg().set("socket-dir") = runtimeDir;
-    }
+                + ". Set to the RUNTIME_DIRECTORY environment variable when that variable has a value (e.g. under systemd).")
+    = "";
+  auto* runtimeDir = getenv("RUNTIME_DIRECTORY"); // NOLINT(concurrency-mt-unsafe,cppcoreguidelines-pro-type-vararg)
+  if (runtimeDir != nullptr) {
+    ::arg().set("socket-dir") = runtimeDir;
+  }
 #else
-                )
-      = "";
+              )
+    = "";
 #endif
-    ::arg().set("query-local-address", "Source IP address for sending queries") = "0.0.0.0";
-    ::arg().set("client-tcp-timeout", "Timeout in seconds when talking to TCP clients") = "2";
-    ::arg().set("max-mthreads", "Maximum number of simultaneous Mtasker threads") = "2048";
-    ::arg().set("max-tcp-clients", "Maximum number of simultaneous TCP clients") = "128";
-    ::arg().set("max-concurrent-requests-per-tcp-connection", "Maximum number of requests handled concurrently per TCP connection") = "10";
-    ::arg().set("server-down-max-fails", "Maximum number of consecutive timeouts (and unreachables) to mark a server as down ( 0 => disabled )") = "64";
-    ::arg().set("server-down-throttle-time", "Number of seconds to throttle all queries to a server after being marked as down") = "60";
-    ::arg().set("dont-throttle-names", "Do not throttle nameservers with this name or suffix") = "";
-    ::arg().set("dont-throttle-netmasks", "Do not throttle nameservers with this IP netmask") = "";
-    ::arg().set("non-resolving-ns-max-fails", "Number of failed address resolves of a nameserver to start throttling it, 0 is disabled") = "5";
-    ::arg().set("non-resolving-ns-throttle-time", "Number of seconds to throttle a nameserver with a name failing to resolve") = "60";
-
-    ::arg().set("hint-file", "If set, load root hints from this file") = "";
-    ::arg().set("max-cache-entries", "If set, maximum number of entries in the main cache") = "1000000";
-    ::arg().set("max-negative-ttl", "maximum number of seconds to keep a negative cached entry in memory") = "3600";
-    ::arg().set("max-cache-bogus-ttl", "maximum number of seconds to keep a Bogus (positive or negative) cached entry in memory") = "3600";
-    ::arg().set("max-cache-ttl", "maximum number of seconds to keep a cached entry in memory") = "86400";
-    ::arg().set("packetcache-ttl", "maximum number of seconds to keep a cached entry in packetcache") = "86400";
-    ::arg().set("max-packetcache-entries", "maximum number of entries to keep in the packetcache") = "500000";
-    ::arg().set("packetcache-servfail-ttl", "maximum number of seconds to keep a cached servfail entry in packetcache") = "60";
-    ::arg().set("packetcache-negative-ttl", "maximum number of seconds to keep a cached NxDomain or NoData entry in packetcache") = "60";
-    ::arg().set("server-id", "Returned when queried for 'id.server' TXT or NSID, defaults to hostname, set custom or 'disabled'") = "";
-    ::arg().set("stats-ringbuffer-entries", "maximum number of packets to store statistics for") = "10000";
-    ::arg().set("version-string", "string reported on version.pdns or version.bind") = fullVersionString();
-    ::arg().set("allow-from", "If set, only allow these comma separated netmasks to recurse") = LOCAL_NETS;
-    ::arg().set("allow-from-file", "If set, load allowed netmasks from this file") = "";
-    ::arg().set("allow-notify-for", "If set, NOTIFY requests for these zones will be allowed") = "";
-    ::arg().set("allow-notify-for-file", "If set, load NOTIFY-allowed zones from this file") = "";
-    ::arg().set("allow-notify-from", "If set, NOTIFY requests from these comma separated netmasks will be allowed") = "";
-    ::arg().set("allow-notify-from-file", "If set, load NOTIFY-allowed netmasks from this file") = "";
-    ::arg().set("entropy-source", "If set, read entropy from this file") = "/dev/urandom";
-    ::arg().set("dont-query", "If set, do not query these netmasks for DNS data") = DONT_QUERY;
-    ::arg().set("max-tcp-per-client", "If set, maximum number of TCP sessions per client (IP address)") = "0";
-    ::arg().set("max-tcp-queries-per-connection", "If set, maximum number of TCP queries in a TCP connection") = "0";
-    ::arg().set("spoof-nearmiss-max", "If non-zero, assume spoofing after this many near misses") = "1";
-    ::arg().set("single-socket", "If set, only use a single socket for outgoing queries") = "off";
-    ::arg().set("auth-zones", "Zones for which we have authoritative data, comma separated domain=file pairs ") = "";
-    ::arg().set("lua-config-file", "More powerful configuration options") = "";
-    ::arg().setSwitch("allow-trust-anchor-query", "Allow queries for trustanchor.server CH TXT and negativetrustanchor.server CH TXT") = "no";
-
-    ::arg().set("forward-zones", "Zones for which we forward queries, comma separated domain=ip pairs") = "";
-    ::arg().set("forward-zones-recurse", "Zones for which we forward queries with recursion bit, comma separated domain=ip pairs") = "";
-    ::arg().set("forward-zones-file", "File with (+)domain=ip pairs for forwarding") = "";
-    ::arg().set("export-etc-hosts", "If we should serve up contents from /etc/hosts") = "off";
-    ::arg().set("export-etc-hosts-search-suffix", "Also serve up the contents of /etc/hosts with this suffix") = "";
-    ::arg().set("etc-hosts-file", "Path to 'hosts' file") = "/etc/hosts";
-    ::arg().set("serve-rfc1918", "If we should be authoritative for RFC 1918 private IP space") = "yes";
-    ::arg().set("lua-dns-script", "Filename containing an optional 'lua' script that will be used to modify dns answers") = "";
-    ::arg().set("lua-maintenance-interval", "Number of seconds between calls to the lua user defined maintenance() function") = "1";
-    ::arg().set("latency-statistic-size", "Number of latency values to calculate the qa-latency average") = "10000";
-    ::arg().setSwitch("disable-packetcache", "Disable packetcache") = "no";
-    ::arg().set("ecs-ipv4-bits", "Number of bits of IPv4 address to pass for EDNS Client Subnet") = "24";
-    ::arg().set("ecs-ipv4-cache-bits", "Maximum number of bits of IPv4 mask to cache ECS response") = "24";
-    ::arg().set("ecs-ipv6-bits", "Number of bits of IPv6 address to pass for EDNS Client Subnet") = "56";
-    ::arg().set("ecs-ipv6-cache-bits", "Maximum number of bits of IPv6 mask to cache ECS response") = "56";
-    ::arg().setSwitch("ecs-ipv4-never-cache", "If we should never cache IPv4 ECS responses") = "no";
-    ::arg().setSwitch("ecs-ipv6-never-cache", "If we should never cache IPv6 ECS responses") = "no";
-    ::arg().set("ecs-minimum-ttl-override", "The minimum TTL for records in ECS-specific answers") = "1";
-    ::arg().set("ecs-cache-limit-ttl", "Minimum TTL to cache ECS response") = "0";
-    ::arg().set("edns-subnet-whitelist", "List of netmasks and domains that we should enable EDNS subnet for (deprecated)") = "";
-    ::arg().set("edns-subnet-allow-list", "List of netmasks and domains that we should enable EDNS subnet for") = "";
-    ::arg().set("ecs-add-for", "List of client netmasks for which EDNS Client Subnet will be added") = "0.0.0.0/0, ::/0, " LOCAL_NETS_INVERSE;
-    ::arg().set("ecs-scope-zero-address", "Address to send to allow-listed authoritative servers for incoming queries with ECS prefix-length source of 0") = "";
-    ::arg().setSwitch("use-incoming-edns-subnet", "Pass along received EDNS Client Subnet information") = "no";
-    ::arg().setSwitch("pdns-distributes-queries", "If PowerDNS itself should distribute queries over threads") = "no";
-    ::arg().setSwitch("root-nx-trust", "If set, believe that an NXDOMAIN from the root means the TLD does not exist") = "yes";
-    ::arg().setSwitch("any-to-tcp", "Answer ANY queries with tc=1, shunting to TCP") = "no";
-    ::arg().setSwitch("lowercase-outgoing", "Force outgoing questions to lowercase") = "no";
-    ::arg().setSwitch("gettag-needs-edns-options", "If EDNS Options should be extracted before calling the gettag() hook") = "no";
-    ::arg().set("udp-truncation-threshold", "Maximum UDP response size before we truncate") = "1232";
-    ::arg().set("edns-outgoing-bufsize", "Outgoing EDNS buffer size") = "1232";
-    ::arg().set("minimum-ttl-override", "The minimum TTL") = "1";
-    ::arg().set("max-qperq", "Maximum outgoing queries per query") = "60";
-    ::arg().set("max-ns-per-resolve", "Maximum number of NS records to consider to resolve a name, 0 is no limit") = "13";
-    ::arg().set("max-ns-address-qperq", "Maximum outgoing NS address queries per query") = "10";
-    ::arg().set("max-total-msec", "Maximum total wall-clock time per query in milliseconds, 0 for unlimited") = "7000";
-    ::arg().set("max-recursion-depth", "Maximum number of internal recursion calls per query, 0 for unlimited") = "40";
-    ::arg().set("max-udp-queries-per-round", "Maximum number of UDP queries processed per recvmsg() round, before returning back to normal processing") = "10000";
-    ::arg().set("protobuf-use-kernel-timestamp", "Compute the latency of queries in protobuf messages by using the timestamp set by the kernel when the query was received (when available)") = "";
-    ::arg().set("distribution-pipe-buffer-size", "Size in bytes of the internal buffer of the pipe used by the distributor to pass incoming queries to a worker thread") = "0";
-
-    ::arg().set("include-dir", "Include *.conf files from this directory") = "";
-    ::arg().set("security-poll-suffix", "Domain name from which to query security update notifications") = "secpoll.powerdns.com.";
+  ::arg().set("query-local-address", "Source IP address for sending queries") = "0.0.0.0";
+  ::arg().set("client-tcp-timeout", "Timeout in seconds when talking to TCP clients") = "2";
+  ::arg().set("max-mthreads", "Maximum number of simultaneous Mtasker threads") = "2048";
+  ::arg().set("max-tcp-clients", "Maximum number of simultaneous TCP clients") = "128";
+  ::arg().set("max-concurrent-requests-per-tcp-connection", "Maximum number of requests handled concurrently per TCP connection") = "10";
+  ::arg().set("server-down-max-fails", "Maximum number of consecutive timeouts (and unreachables) to mark a server as down ( 0 => disabled )") = "64";
+  ::arg().set("server-down-throttle-time", "Number of seconds to throttle all queries to a server after being marked as down") = "60";
+  ::arg().set("dont-throttle-names", "Do not throttle nameservers with this name or suffix") = "";
+  ::arg().set("dont-throttle-netmasks", "Do not throttle nameservers with this IP netmask") = "";
+  ::arg().set("non-resolving-ns-max-fails", "Number of failed address resolves of a nameserver to start throttling it, 0 is disabled") = "5";
+  ::arg().set("non-resolving-ns-throttle-time", "Number of seconds to throttle a nameserver with a name failing to resolve") = "60";
+
+  ::arg().set("hint-file", "If set, load root hints from this file") = "";
+  ::arg().set("max-cache-entries", "If set, maximum number of entries in the main cache") = "1000000";
+  ::arg().set("max-negative-ttl", "maximum number of seconds to keep a negative cached entry in memory") = "3600";
+  ::arg().set("max-cache-bogus-ttl", "maximum number of seconds to keep a Bogus (positive or negative) cached entry in memory") = "3600";
+  ::arg().set("max-cache-ttl", "maximum number of seconds to keep a cached entry in memory") = "86400";
+  ::arg().set("packetcache-ttl", "maximum number of seconds to keep a cached entry in packetcache") = "86400";
+  ::arg().set("max-packetcache-entries", "maximum number of entries to keep in the packetcache") = "500000";
+  ::arg().set("packetcache-servfail-ttl", "maximum number of seconds to keep a cached servfail entry in packetcache") = "60";
+  ::arg().set("packetcache-negative-ttl", "maximum number of seconds to keep a cached NxDomain or NoData entry in packetcache") = "60";
+  ::arg().set("server-id", "Returned when queried for 'id.server' TXT or NSID, defaults to hostname, set custom or 'disabled'") = "";
+  ::arg().set("stats-ringbuffer-entries", "maximum number of packets to store statistics for") = "10000";
+  ::arg().set("version-string", "string reported on version.pdns or version.bind") = fullVersionString();
+  ::arg().set("allow-from", "If set, only allow these comma separated netmasks to recurse") = LOCAL_NETS;
+  ::arg().set("allow-from-file", "If set, load allowed netmasks from this file") = "";
+  ::arg().set("allow-notify-for", "If set, NOTIFY requests for these zones will be allowed") = "";
+  ::arg().set("allow-notify-for-file", "If set, load NOTIFY-allowed zones from this file") = "";
+  ::arg().set("allow-notify-from", "If set, NOTIFY requests from these comma separated netmasks will be allowed") = "";
+  ::arg().set("allow-notify-from-file", "If set, load NOTIFY-allowed netmasks from this file") = "";
+  ::arg().set("entropy-source", "If set, read entropy from this file") = "/dev/urandom";
+  ::arg().set("dont-query", "If set, do not query these netmasks for DNS data") = DONT_QUERY;
+  ::arg().set("max-tcp-per-client", "If set, maximum number of TCP sessions per client (IP address)") = "0";
+  ::arg().set("max-tcp-queries-per-connection", "If set, maximum number of TCP queries in a TCP connection") = "0";
+  ::arg().set("spoof-nearmiss-max", "If non-zero, assume spoofing after this many near misses") = "1";
+  ::arg().set("single-socket", "If set, only use a single socket for outgoing queries") = "off";
+  ::arg().set("auth-zones", "Zones for which we have authoritative data, comma separated domain=file pairs ") = "";
+  ::arg().set("lua-config-file", "More powerful configuration options") = "";
+  ::arg().setSwitch("allow-trust-anchor-query", "Allow queries for trustanchor.server CH TXT and negativetrustanchor.server CH TXT") = "no";
+
+  ::arg().set("forward-zones", "Zones for which we forward queries, comma separated domain=ip pairs") = "";
+  ::arg().set("forward-zones-recurse", "Zones for which we forward queries with recursion bit, comma separated domain=ip pairs") = "";
+  ::arg().set("forward-zones-file", "File with (+)domain=ip pairs for forwarding") = "";
+  ::arg().set("export-etc-hosts", "If we should serve up contents from /etc/hosts") = "off";
+  ::arg().set("export-etc-hosts-search-suffix", "Also serve up the contents of /etc/hosts with this suffix") = "";
+  ::arg().set("etc-hosts-file", "Path to 'hosts' file") = "/etc/hosts";
+  ::arg().set("serve-rfc1918", "If we should be authoritative for RFC 1918 private IP space") = "yes";
+  ::arg().set("lua-dns-script", "Filename containing an optional 'lua' script that will be used to modify dns answers") = "";
+  ::arg().set("lua-maintenance-interval", "Number of seconds between calls to the lua user defined maintenance() function") = "1";
+  ::arg().set("latency-statistic-size", "Number of latency values to calculate the qa-latency average") = "10000";
+  ::arg().setSwitch("disable-packetcache", "Disable packetcache") = "no";
+  ::arg().set("ecs-ipv4-bits", "Number of bits of IPv4 address to pass for EDNS Client Subnet") = "24";
+  ::arg().set("ecs-ipv4-cache-bits", "Maximum number of bits of IPv4 mask to cache ECS response") = "24";
+  ::arg().set("ecs-ipv6-bits", "Number of bits of IPv6 address to pass for EDNS Client Subnet") = "56";
+  ::arg().set("ecs-ipv6-cache-bits", "Maximum number of bits of IPv6 mask to cache ECS response") = "56";
+  ::arg().setSwitch("ecs-ipv4-never-cache", "If we should never cache IPv4 ECS responses") = "no";
+  ::arg().setSwitch("ecs-ipv6-never-cache", "If we should never cache IPv6 ECS responses") = "no";
+  ::arg().set("ecs-minimum-ttl-override", "The minimum TTL for records in ECS-specific answers") = "1";
+  ::arg().set("ecs-cache-limit-ttl", "Minimum TTL to cache ECS response") = "0";
+  ::arg().set("edns-subnet-whitelist", "List of netmasks and domains that we should enable EDNS subnet for (deprecated)") = "";
+  ::arg().set("edns-subnet-allow-list", "List of netmasks and domains that we should enable EDNS subnet for") = "";
+  ::arg().set("ecs-add-for", "List of client netmasks for which EDNS Client Subnet will be added") = "0.0.0.0/0, ::/0, " LOCAL_NETS_INVERSE;
+  ::arg().set("ecs-scope-zero-address", "Address to send to allow-listed authoritative servers for incoming queries with ECS prefix-length source of 0") = "";
+  ::arg().setSwitch("use-incoming-edns-subnet", "Pass along received EDNS Client Subnet information") = "no";
+  ::arg().setSwitch("pdns-distributes-queries", "If PowerDNS itself should distribute queries over threads") = "no";
+  ::arg().setSwitch("root-nx-trust", "If set, believe that an NXDOMAIN from the root means the TLD does not exist") = "yes";
+  ::arg().setSwitch("any-to-tcp", "Answer ANY queries with tc=1, shunting to TCP") = "no";
+  ::arg().setSwitch("lowercase-outgoing", "Force outgoing questions to lowercase") = "no";
+  ::arg().setSwitch("gettag-needs-edns-options", "If EDNS Options should be extracted before calling the gettag() hook") = "no";
+  ::arg().set("udp-truncation-threshold", "Maximum UDP response size before we truncate") = "1232";
+  ::arg().set("edns-outgoing-bufsize", "Outgoing EDNS buffer size") = "1232";
+  ::arg().set("minimum-ttl-override", "The minimum TTL") = "1";
+  ::arg().set("max-qperq", "Maximum outgoing queries per query") = "60";
+  ::arg().set("max-ns-per-resolve", "Maximum number of NS records to consider to resolve a name, 0 is no limit") = "13";
+  ::arg().set("max-ns-address-qperq", "Maximum outgoing NS address queries per query") = "10";
+  ::arg().set("max-total-msec", "Maximum total wall-clock time per query in milliseconds, 0 for unlimited") = "7000";
+  ::arg().set("max-recursion-depth", "Maximum number of internal recursion calls per query, 0 for unlimited") = "40";
+  ::arg().set("max-udp-queries-per-round", "Maximum number of UDP queries processed per recvmsg() round, before returning back to normal processing") = "10000";
+  ::arg().set("protobuf-use-kernel-timestamp", "Compute the latency of queries in protobuf messages by using the timestamp set by the kernel when the query was received (when available)") = "";
+  ::arg().set("distribution-pipe-buffer-size", "Size in bytes of the internal buffer of the pipe used by the distributor to pass incoming queries to a worker thread") = "0";
+
+  ::arg().set("include-dir", "Include *.conf files from this directory") = "";
+  ::arg().set("security-poll-suffix", "Domain name from which to query security update notifications") = "secpoll.powerdns.com.";
 
 #ifdef SO_REUSEPORT
-    ::arg().setSwitch("reuseport", "Enable SO_REUSEPORT allowing multiple recursors processes to listen to 1 address") = "yes";
+  ::arg().setSwitch("reuseport", "Enable SO_REUSEPORT allowing multiple recursors processes to listen to 1 address") = "yes";
 #else
-    ::arg().setSwitch("reuseport", "Enable SO_REUSEPORT allowing multiple recursors processes to listen to 1 address") = "no";
+  ::arg().setSwitch("reuseport", "Enable SO_REUSEPORT allowing multiple recursors processes to listen to 1 address") = "no";
 #endif
-    ::arg().setSwitch("snmp-agent", "If set, register as an SNMP agent") = "no";
-    ::arg().set("snmp-master-socket", "If set and snmp-agent is set, the socket to use to register to the SNMP daemon (deprecated)") = "";
-    ::arg().set("snmp-daemon-socket", "If set and snmp-agent is set, the socket to use to register to the SNMP daemon") = "";
+  ::arg().setSwitch("snmp-agent", "If set, register as an SNMP agent") = "no";
+  ::arg().set("snmp-master-socket", "If set and snmp-agent is set, the socket to use to register to the SNMP daemon (deprecated)") = "";
+  ::arg().set("snmp-daemon-socket", "If set and snmp-agent is set, the socket to use to register to the SNMP daemon") = "";
 
-    std::string defaultAPIDisabledStats = "cache-bytes, packetcache-bytes, special-memory-usage";
-    for (size_t idx = 0; idx < 32; idx++) {
-      defaultAPIDisabledStats += ", ecs-v4-response-bits-" + std::to_string(idx + 1);
-    }
-    for (size_t idx = 0; idx < 128; idx++) {
-      defaultAPIDisabledStats += ", ecs-v6-response-bits-" + std::to_string(idx + 1);
-    }
-    std::string defaultDisabledStats = defaultAPIDisabledStats + ", cumul-clientanswers, cumul-authanswers, policy-hits, proxy-mapping-total, remote-logger-count";
+  std::string defaultAPIDisabledStats = "cache-bytes, packetcache-bytes, special-memory-usage";
+  for (size_t idx = 0; idx < 32; idx++) {
+    defaultAPIDisabledStats += ", ecs-v4-response-bits-" + std::to_string(idx + 1);
+  }
+  for (size_t idx = 0; idx < 128; idx++) {
+    defaultAPIDisabledStats += ", ecs-v6-response-bits-" + std::to_string(idx + 1);
+  }
+  std::string defaultDisabledStats = defaultAPIDisabledStats + ", cumul-clientanswers, cumul-authanswers, policy-hits, proxy-mapping-total, remote-logger-count";
 
-    ::arg().set("stats-api-blacklist", "List of statistics that are disabled when retrieving the complete list of statistics via the API (deprecated)") = defaultAPIDisabledStats;
-    ::arg().set("stats-carbon-blacklist", "List of statistics that are prevented from being exported via Carbon (deprecated)") = defaultDisabledStats;
-    ::arg().set("stats-rec-control-blacklist", "List of statistics that are prevented from being exported via rec_control get-all (deprecated)") = defaultDisabledStats;
-    ::arg().set("stats-snmp-blacklist", "List of statistics that are prevented from being exported via SNMP (deprecated)") = defaultDisabledStats;
+  ::arg().set("stats-api-blacklist", "List of statistics that are disabled when retrieving the complete list of statistics via the API (deprecated)") = defaultAPIDisabledStats;
+  ::arg().set("stats-carbon-blacklist", "List of statistics that are prevented from being exported via Carbon (deprecated)") = defaultDisabledStats;
+  ::arg().set("stats-rec-control-blacklist", "List of statistics that are prevented from being exported via rec_control get-all (deprecated)") = defaultDisabledStats;
+  ::arg().set("stats-snmp-blacklist", "List of statistics that are prevented from being exported via SNMP (deprecated)") = defaultDisabledStats;
 
-    ::arg().set("stats-api-disabled-list", "List of statistics that are disabled when retrieving the complete list of statistics via the API") = defaultAPIDisabledStats;
-    ::arg().set("stats-carbon-disabled-list", "List of statistics that are prevented from being exported via Carbon") = defaultDisabledStats;
-    ::arg().set("stats-rec-control-disabled-list", "List of statistics that are prevented from being exported via rec_control get-all") = defaultDisabledStats;
-    ::arg().set("stats-snmp-disabled-list", "List of statistics that are prevented from being exported via SNMP") = defaultDisabledStats;
+  ::arg().set("stats-api-disabled-list", "List of statistics that are disabled when retrieving the complete list of statistics via the API") = defaultAPIDisabledStats;
+  ::arg().set("stats-carbon-disabled-list", "List of statistics that are prevented from being exported via Carbon") = defaultDisabledStats;
+  ::arg().set("stats-rec-control-disabled-list", "List of statistics that are prevented from being exported via rec_control get-all") = defaultDisabledStats;
+  ::arg().set("stats-snmp-disabled-list", "List of statistics that are prevented from being exported via SNMP") = defaultDisabledStats;
 
-    ::arg().set("tcp-fast-open", "Enable TCP Fast Open support on the listening sockets, using the supplied numerical value as the queue size") = "0";
-    ::arg().set("tcp-fast-open-connect", "Enable TCP Fast Open support on outgoing sockets") = "no";
-    ::arg().set("nsec3-max-iterations", "Maximum number of iterations allowed for an NSEC3 record") = "150";
+  ::arg().set("tcp-fast-open", "Enable TCP Fast Open support on the listening sockets, using the supplied numerical value as the queue size") = "0";
+  ::arg().set("tcp-fast-open-connect", "Enable TCP Fast Open support on outgoing sockets") = "no";
+  ::arg().set("nsec3-max-iterations", "Maximum number of iterations allowed for an NSEC3 record") = "150";
 
-    ::arg().set("cpu-map", "Thread to CPU mapping, space separated thread-id=cpu1,cpu2..cpuN pairs") = "";
+  ::arg().set("cpu-map", "Thread to CPU mapping, space separated thread-id=cpu1,cpu2..cpuN pairs") = "";
 
-    ::arg().setSwitch("log-rpz-changes", "Log additions and removals to RPZ zones at Info level") = "no";
+  ::arg().setSwitch("log-rpz-changes", "Log additions and removals to RPZ zones at Info level") = "no";
 
-    ::arg().set("proxy-protocol-from", "A Proxy Protocol header is only allowed from these subnets") = "";
-    ::arg().set("proxy-protocol-maximum-size", "The maximum size of a proxy protocol payload, including the TLV values") = "512";
+  ::arg().set("proxy-protocol-from", "A Proxy Protocol header is only allowed from these subnets") = "";
+  ::arg().set("proxy-protocol-maximum-size", "The maximum size of a proxy protocol payload, including the TLV values") = "512";
 
-    ::arg().set("dns64-prefix", "DNS64 prefix") = "";
+  ::arg().set("dns64-prefix", "DNS64 prefix") = "";
 
-    ::arg().set("udp-source-port-min", "Minimum UDP port to bind on") = "1024";
-    ::arg().set("udp-source-port-max", "Maximum UDP port to bind on") = "65535";
-    ::arg().set("udp-source-port-avoid", "List of comma separated UDP port number to avoid") = "11211";
-    ::arg().set("rng", "Specify random number generator to use. Valid values are auto,sodium,openssl,getrandom,arc4random,urandom.") = "auto";
-    ::arg().set("public-suffix-list-file", "Path to the Public Suffix List file, if any") = "";
-    ::arg().set("distribution-load-factor", "The load factor used when PowerDNS is distributing queries to worker threads") = "0.0";
+  ::arg().set("udp-source-port-min", "Minimum UDP port to bind on") = "1024";
+  ::arg().set("udp-source-port-max", "Maximum UDP port to bind on") = "65535";
+  ::arg().set("udp-source-port-avoid", "List of comma separated UDP port number to avoid") = "11211";
+  ::arg().set("rng", "Specify random number generator to use. Valid values are auto,sodium,openssl,getrandom,arc4random,urandom.") = "auto";
+  ::arg().set("public-suffix-list-file", "Path to the Public Suffix List file, if any") = "";
+  ::arg().set("distribution-load-factor", "The load factor used when PowerDNS is distributing queries to worker threads") = "0.0";
 
-    ::arg().setSwitch("qname-minimization", "Use Query Name Minimization") = "yes";
-    ::arg().setSwitch("nothing-below-nxdomain", "When an NXDOMAIN exists in cache for a name with fewer labels than the qname, send NXDOMAIN without doing a lookup (see RFC 8020)") = "dnssec";
-    ::arg().set("max-generate-steps", "Maximum number of $GENERATE steps when loading a zone from a file") = "0";
-    ::arg().set("max-include-depth", "Maximum nested $INCLUDE depth when loading a zone from a file") = "20";
+  ::arg().setSwitch("qname-minimization", "Use Query Name Minimization") = "yes";
+  ::arg().setSwitch("nothing-below-nxdomain", "When an NXDOMAIN exists in cache for a name with fewer labels than the qname, send NXDOMAIN without doing a lookup (see RFC 8020)") = "dnssec";
+  ::arg().set("max-generate-steps", "Maximum number of $GENERATE steps when loading a zone from a file") = "0";
+  ::arg().set("max-include-depth", "Maximum nested $INCLUDE depth when loading a zone from a file") = "20";
 
-    ::arg().set("record-cache-shards", "Number of shards in the record cache") = "1024";
-    ::arg().set("packetcache-shards", "Number of shards in the packet cache") = "1024";
+  ::arg().set("record-cache-shards", "Number of shards in the record cache") = "1024";
+  ::arg().set("packetcache-shards", "Number of shards in the packet cache") = "1024";
 
-    ::arg().set("refresh-on-ttl-perc", "If a record is requested from the cache and only this % of original TTL remains, refetch") = "0";
-    ::arg().set("record-cache-locked-ttl-perc", "Replace records in record cache only after this % of original TTL has passed") = "0";
+  ::arg().set("refresh-on-ttl-perc", "If a record is requested from the cache and only this % of original TTL remains, refetch") = "0";
+  ::arg().set("record-cache-locked-ttl-perc", "Replace records in record cache only after this % of original TTL has passed") = "0";
 
-    ::arg().set("x-dnssec-names", "Collect DNSSEC statistics for names or suffixes in this list in separate x-dnssec counters") = "";
+  ::arg().set("x-dnssec-names", "Collect DNSSEC statistics for names or suffixes in this list in separate x-dnssec counters") = "";
 
 #ifdef NOD_ENABLED
-    ::arg().set("new-domain-tracking", "Track newly observed domains (i.e. never seen before).") = "no";
-    ::arg().set("new-domain-log", "Log newly observed domains.") = "yes";
-    ::arg().set("new-domain-lookup", "Perform a DNS lookup newly observed domains as a subdomain of the configured domain") = "";
-    ::arg().set("new-domain-history-dir", "Persist new domain tracking data here to persist between restarts") = string(NODCACHEDIR) + "/nod";
-    ::arg().set("new-domain-whitelist", "List of domains (and implicitly all subdomains) which will never be considered a new domain (deprecated)") = "";
-    ::arg().set("new-domain-ignore-list", "List of domains (and implicitly all subdomains) which will never be considered a new domain") = "";
-    ::arg().set("new-domain-db-size", "Size of the DB used to track new domains in terms of number of cells. Defaults to 67108864") = "67108864";
-    ::arg().set("new-domain-pb-tag", "If protobuf is configured, the tag to use for messages containing newly observed domains. Defaults to 'pdns-nod'") = "pdns-nod";
-    ::arg().set("unique-response-tracking", "Track unique responses (tuple of query name, type and RR).") = "no";
-    ::arg().set("unique-response-log", "Log unique responses") = "yes";
-    ::arg().set("unique-response-history-dir", "Persist unique response tracking data here to persist between restarts") = string(NODCACHEDIR) + "/udr";
-    ::arg().set("unique-response-db-size", "Size of the DB used to track unique responses in terms of number of cells. Defaults to 67108864") = "67108864";
-    ::arg().set("unique-response-pb-tag", "If protobuf is configured, the tag to use for messages containing unique DNS responses. Defaults to 'pdns-udr'") = "pdns-udr";
+  ::arg().set("new-domain-tracking", "Track newly observed domains (i.e. never seen before).") = "no";
+  ::arg().set("new-domain-log", "Log newly observed domains.") = "yes";
+  ::arg().set("new-domain-lookup", "Perform a DNS lookup newly observed domains as a subdomain of the configured domain") = "";
+  ::arg().set("new-domain-history-dir", "Persist new domain tracking data here to persist between restarts") = string(NODCACHEDIR) + "/nod";
+  ::arg().set("new-domain-whitelist", "List of domains (and implicitly all subdomains) which will never be considered a new domain (deprecated)") = "";
+  ::arg().set("new-domain-ignore-list", "List of domains (and implicitly all subdomains) which will never be considered a new domain") = "";
+  ::arg().set("new-domain-db-size", "Size of the DB used to track new domains in terms of number of cells. Defaults to 67108864") = "67108864";
+  ::arg().set("new-domain-pb-tag", "If protobuf is configured, the tag to use for messages containing newly observed domains. Defaults to 'pdns-nod'") = "pdns-nod";
+  ::arg().set("unique-response-tracking", "Track unique responses (tuple of query name, type and RR).") = "no";
+  ::arg().set("unique-response-log", "Log unique responses") = "yes";
+  ::arg().set("unique-response-history-dir", "Persist unique response tracking data here to persist between restarts") = string(NODCACHEDIR) + "/udr";
+  ::arg().set("unique-response-db-size", "Size of the DB used to track unique responses in terms of number of cells. Defaults to 67108864") = "67108864";
+  ::arg().set("unique-response-pb-tag", "If protobuf is configured, the tag to use for messages containing unique DNS responses. Defaults to 'pdns-udr'") = "pdns-udr";
 #endif /* NOD_ENABLED */
 
-    ::arg().setSwitch("extended-resolution-errors", "If set, send an EDNS Extended Error extension on resolution failures, like DNSSEC validation errors") = "no";
-
-    ::arg().set("aggressive-nsec-cache-size", "The number of records to cache in the aggressive cache. If set to a value greater than 0, and DNSSEC processing or validation is enabled, the recursor will cache NSEC and NSEC3 records to generate negative answers, as defined in rfc8198") = "100000";
-    ::arg().set("aggressive-cache-min-nsec3-hit-ratio", "The minimum expected hit ratio to store NSEC3 records into the aggressive cache") = "2000";
-
-    ::arg().set("edns-padding-from", "List of netmasks (proxy IP in case of proxy-protocol presence, client IP otherwise) for which EDNS padding will be enabled in responses, provided that 'edns-padding-mode' applies") = "";
-    ::arg().set("edns-padding-mode", "Whether to add EDNS padding to all responses ('always') or only to responses for queries containing the EDNS padding option ('padded-queries-only', the default). In both modes, padding will only be added to responses for queries coming from `edns-padding-from`_ sources") = "padded-queries-only";
-    ::arg().set("edns-padding-tag", "Packetcache tag associated to responses sent with EDNS padding, to prevent sending these to clients for which padding is not enabled.") = "7830";
-    ::arg().setSwitch("edns-padding-out", "Whether to add EDNS padding to outgoing DoT messages") = "yes";
-
-    ::arg().setSwitch("dot-to-port-853", "Force DoT connection to target port 853 if DoT compiled in") = "yes";
-    ::arg().set("dot-to-auth-names", "Use DoT to authoritative servers with these names or suffixes") = "";
-    ::arg().set("event-trace-enabled", "If set, event traces are collected and send out via protobuf logging (1), logfile (2) or both(3)") = "0";
-
-    ::arg().set("tcp-out-max-idle-ms", "Time TCP/DoT connections are left idle in milliseconds or 0 if no limit") = "10000";
-    ::arg().set("tcp-out-max-idle-per-auth", "Maximum number of idle TCP/DoT connections to a specific IP per thread, 0 means do not keep idle connections open") = "10";
-    ::arg().set("tcp-out-max-queries", "Maximum total number of queries per TCP/DoT connection, 0 means no limit") = "0";
-    ::arg().set("tcp-out-max-idle-per-thread", "Maximum number of idle TCP/DoT connections per thread") = "100";
-    ::arg().setSwitch("structured-logging", "Prefer structured logging") = "yes";
-    ::arg().set("structured-logging-backend", "Structured logging backend") = "default";
-    ::arg().setSwitch("save-parent-ns-set", "Save parent NS set to be used if child NS set fails") = "yes";
-    ::arg().set("max-busy-dot-probes", "Maximum number of concurrent DoT probes") = "0";
-    ::arg().set("serve-stale-extensions", "Number of times a record's ttl is extended by 30s to be served stale") = "0";
-
-    ::arg().setCmd("help", "Provide a helpful message");
-    ::arg().setCmd("version", "Print version string");
-    ::arg().setCmd("config", "Output blank configuration. You can use --config=check to test the config file and command line arguments.");
-    ::arg().setDefaults();
-    g_log.toConsole(Logger::Info);
+  ::arg().setSwitch("extended-resolution-errors", "If set, send an EDNS Extended Error extension on resolution failures, like DNSSEC validation errors") = "no";
+
+  ::arg().set("aggressive-nsec-cache-size", "The number of records to cache in the aggressive cache. If set to a value greater than 0, and DNSSEC processing or validation is enabled, the recursor will cache NSEC and NSEC3 records to generate negative answers, as defined in rfc8198") = "100000";
+  ::arg().set("aggressive-cache-min-nsec3-hit-ratio", "The minimum expected hit ratio to store NSEC3 records into the aggressive cache") = "2000";
+
+  ::arg().set("edns-padding-from", "List of netmasks (proxy IP in case of proxy-protocol presence, client IP otherwise) for which EDNS padding will be enabled in responses, provided that 'edns-padding-mode' applies") = "";
+  ::arg().set("edns-padding-mode", "Whether to add EDNS padding to all responses ('always') or only to responses for queries containing the EDNS padding option ('padded-queries-only', the default). In both modes, padding will only be added to responses for queries coming from `edns-padding-from`_ sources") = "padded-queries-only";
+  ::arg().set("edns-padding-tag", "Packetcache tag associated to responses sent with EDNS padding, to prevent sending these to clients for which padding is not enabled.") = "7830";
+  ::arg().setSwitch("edns-padding-out", "Whether to add EDNS padding to outgoing DoT messages") = "yes";
+
+  ::arg().setSwitch("dot-to-port-853", "Force DoT connection to target port 853 if DoT compiled in") = "yes";
+  ::arg().set("dot-to-auth-names", "Use DoT to authoritative servers with these names or suffixes") = "";
+  ::arg().set("event-trace-enabled", "If set, event traces are collected and send out via protobuf logging (1), logfile (2) or both(3)") = "0";
+
+  ::arg().set("tcp-out-max-idle-ms", "Time TCP/DoT connections are left idle in milliseconds or 0 if no limit") = "10000";
+  ::arg().set("tcp-out-max-idle-per-auth", "Maximum number of idle TCP/DoT connections to a specific IP per thread, 0 means do not keep idle connections open") = "10";
+  ::arg().set("tcp-out-max-queries", "Maximum total number of queries per TCP/DoT connection, 0 means no limit") = "0";
+  ::arg().set("tcp-out-max-idle-per-thread", "Maximum number of idle TCP/DoT connections per thread") = "100";
+  ::arg().setSwitch("structured-logging", "Prefer structured logging") = "yes";
+  ::arg().set("structured-logging-backend", "Structured logging backend") = "default";
+  ::arg().setSwitch("save-parent-ns-set", "Save parent NS set to be used if child NS set fails") = "yes";
+  ::arg().set("max-busy-dot-probes", "Maximum number of concurrent DoT probes") = "0";
+  ::arg().set("serve-stale-extensions", "Number of times a record's ttl is extended by 30s to be served stale") = "0";
+
+  ::arg().setCmd("help", "Provide a helpful message");
+  ::arg().setCmd("version", "Print version string");
+  ::arg().setCmd("config", "Output blank configuration. You can use --config=check to test the config file and command line arguments.");
+  ::arg().setDefaults();
+  g_log.toConsole(Logger::Info);
+}
+
+static pair<int, bool> doConfig(Logr::log_t startupLog, const string& configname, int argc, char* argv[]) // NOLINT: Posix API
+{
+  if (::arg().mustDo("config")) {
+    string config = ::arg()["config"];
+    if (config == "check") {
+      try {
+        if (!::arg().file(configname.c_str())) {
+          SLOG(g_log << Logger::Warning << "Unable to open configuration file '" << configname << "'" << endl,
+               startupLog->error("No such file", "Unable to open configuration file", "config_file", Logging::Loggable(configname)));
+          return {1, true};
+        }
+        ::arg().parse(argc, argv);
+        return {0, true};
+      }
+      catch (const ArgException& argException) {
+        SLOG(g_log << Logger::Warning << "Unable to parse configuration file '" << configname << "': " << argException.reason << endl,
+             startupLog->error("Cannot parse configuration", "Unable to parse configuration file", "config_file", Logging::Loggable(configname), "reason", Logging::Loggable(argException.reason)));
+        return {1, true};
+      }
+    }
+    else if (config == "default" || config.empty()) {
+      cout << ::arg().configstring(false, true);
+    }
+    else if (config == "diff") {
+      if (!::arg().laxFile(configname.c_str())) {
+        SLOG(g_log << Logger::Warning << "Unable to open configuration file '" << configname << "'" << endl,
+             startupLog->error("No such file", "Unable to open configuration file", "config_file", Logging::Loggable(configname)));
+        return {1, true};
+      }
+      ::arg().laxParse(argc, argv);
+      cout << ::arg().configstring(true, false);
+    }
+    else {
+      if (!::arg().laxFile(configname.c_str())) {
+        SLOG(g_log << Logger::Warning << "Unable to open configuration file '" << configname << "'" << endl,
+             startupLog->error("No such file", "Unable to open configuration file", "config_file", Logging::Loggable(configname)));
+        return {1, true};
+      }
+      ::arg().laxParse(argc, argv);
+      cout << ::arg().configstring(true, true);
+    }
+    return {0, true};
+  }
+  return {0, false};
+}
+
+int main(int argc, char** argv) // NOLINT(readability-function-cognitive-complexity) #12791 Remove NOLINT(readability-function-cognitive-complexity) omoerbeek
+{
+  g_argc = argc;
+  g_argv = argv;
+  Utility::srandom();
+  versionSetProduct(ProductRecursor);
+  reportBasicTypes();
+  reportOtherTypes();
+
+  int ret = EXIT_SUCCESS;
+
+  try {
+    initArgs();
     ::arg().laxParse(argc, argv); // do a lax parse
 
     if (::arg().mustDo("version")) {
       showProductVersion();
       showBuildConfiguration();
-      exit(0);
+      return 0;
     }
     if (::arg().mustDo("help")) {
       cout << "syntax:" << endl
            << endl;
       cout << ::arg().helpstring(::arg()["help"]) << endl;
-      exit(0);
+      return 0;
     }
 
     // Pick up options given on command line to setup logging asap.
@@ -2908,15 +3085,15 @@ int main(int argc, char** argv)
       }
       cerr << " (";
       bool first = true;
-      for (const auto& c : ::arg().getCommands()) {
+      for (const auto& command : ::arg().getCommands()) {
         if (!first) {
           cerr << ", ";
         }
         first = false;
-        cerr << c;
+        cerr << command;
       }
       cerr << ") on the command line, perhaps a '--setting=123' statement missed the '='?" << endl;
-      exit(99);
+      return 99;
     }
 
     if (s_structured_logger_backend == "systemd-journal") {
@@ -2943,46 +3120,10 @@ int main(int argc, char** argv)
 
     ::arg().setSLog(startupLog);
 
-    if (::arg().mustDo("config")) {
-      string config = ::arg()["config"];
-      if (config == "check") {
-        try {
-          if (!::arg().file(configname.c_str())) {
-            SLOG(g_log << Logger::Warning << "Unable to open configuration file '" << configname << "'" << endl,
-                 startupLog->error("No such file", "Unable to open configuration file", "config_file", Logging::Loggable(configname)));
-            exit(1);
-          }
-          ::arg().parse(argc, argv);
-          exit(0);
-        }
-        catch (const ArgException& argException) {
-          SLOG(g_log << Logger::Warning << "Unable to parse configuration file '" << configname << "': " << argException.reason << endl,
-               startupLog->error("Cannot parse configuration", "Unable to parse configuration file", "config_file", Logging::Loggable(configname), "reason", Logging::Loggable(argException.reason)));
-          exit(1);
-        }
-      }
-      else if (config == "default" || config.empty()) {
-        cout << ::arg().configstring(false, true);
-      }
-      else if (config == "diff") {
-        if (!::arg().laxFile(configname.c_str())) {
-          SLOG(g_log << Logger::Warning << "Unable to open configuration file '" << configname << "'" << endl,
-               startupLog->error("No such file", "Unable to open configuration file", "config_file", Logging::Loggable(configname)));
-          exit(1);
-        }
-        ::arg().laxParse(argc, argv);
-        cout << ::arg().configstring(true, false);
-      }
-      else {
-        if (!::arg().laxFile(configname.c_str())) {
-          SLOG(g_log << Logger::Warning << "Unable to open configuration file '" << configname << "'" << endl,
-               startupLog->error("No such file", "Unable to open configuration file", "config_file", Logging::Loggable(configname)));
-          exit(1);
-        }
-        ::arg().laxParse(argc, argv);
-        cout << ::arg().configstring(true, true);
-      }
-      exit(0);
+    bool mustExit = false;
+    std::tie(ret, mustExit) = doConfig(startupLog, configname, argc, argv);
+    if (ret != 0 || mustExit) {
+      return ret;
     }
 
     if (!::arg().file(configname.c_str())) {
@@ -3009,14 +3150,16 @@ int main(int argc, char** argv)
     if (!::arg()["chroot"].empty() && !::arg()["api-config-dir"].empty()) {
       SLOG(g_log << Logger::Error << "Using chroot and enabling the API is not possible" << endl,
            startupLog->info(Logr::Error, "Cannot use chroot and enable the API at the same time"));
-      exit(EXIT_FAILURE);
+      return EXIT_FAILURE;
     }
 
     if (::arg()["socket-dir"].empty()) {
-      if (::arg()["chroot"].empty())
+      if (::arg()["chroot"].empty()) {
         ::arg().set("socket-dir") = std::string(LOCALSTATEDIR) + "/pdns-recursor";
-      else
+      }
+      else {
         ::arg().set("socket-dir") = "/";
+      }
     }
 
     if (::arg().asNum("threads") == 1) {
@@ -3044,7 +3187,7 @@ int main(int argc, char** argv)
       g_packetCache = std::make_unique<RecursorPacketCache>(g_maxPacketCacheEntries, ::arg().asNum("packetcache-shards"));
     }
 
-    ret = serviceMain(argc, argv, startupLog);
+    ret = serviceMain(startupLog);
   }
   catch (const PDNSException& ae) {
     SLOG(g_log << Logger::Error << "Exception: " << ae.reason << endl,
index f7270474f1062bfc652320f4d706675904463d8d..6119a325e991c8f5c744b884127a8a491fcbbf71 100644 (file)
@@ -468,6 +468,7 @@ bool SyncRes::s_dot_to_port_853;
 int SyncRes::s_event_trace_enabled;
 bool SyncRes::s_save_parent_ns_set;
 unsigned int SyncRes::s_max_busy_dot_probes;
+unsigned int SyncRes::s_max_CNAMES_followed = 10;
 bool SyncRes::s_addExtendedResolutionDNSErrors;
 
 #define LOG(x)                       \
@@ -1816,7 +1817,7 @@ unsigned int SyncRes::getAdjustedRecursionBound() const
  * \param stopAtDelegation if non-nullptr and pointed-to value is Stop requests the callee to stop at a delegation, if so pointed-to value is set to Stopped
  * \return DNS RCODE or -1 (Error)
  */
-int SyncRes::doResolveNoQNameMinimization(const DNSName& qname, const QType qtype, vector<DNSRecord>& ret, unsigned int depth, set<GetBestNSAnswer>& beenthere, Context& context, bool* fromCache, StopAtDelegation* stopAtDelegation)
+int SyncRes::doResolveNoQNameMinimization(const DNSName& qname, const QType qtype, vector<DNSRecord>& ret, unsigned int depth, set<GetBestNSAnswer>& beenthere, Context& context, bool* fromCache, StopAtDelegation* stopAtDelegation) // NOLINT(readability-function-cognitive-complexity)
 {
   context.extendedError.reset();
   auto prefix = getPrefix(depth);
@@ -1825,7 +1826,8 @@ int SyncRes::doResolveNoQNameMinimization(const DNSName& qname, const QType qtyp
 
   if (s_maxdepth > 0) {
     auto bound = getAdjustedRecursionBound();
-    if (depth > bound) {
+    // Use a stricter bound if throttling
+    if (depth > bound || (d_outqueries > 10 && d_throttledqueries > 5 && depth > bound * 2 / 3)) {
       string msg = "More than " + std::to_string(bound) + " (adjusted max-recursion-depth) levels of recursion needed while resolving " + qname.toLogString();
       LOG(prefix << qname << ": " << msg << endl);
       throw ImmediateServFailException(msg);
@@ -2403,30 +2405,22 @@ void SyncRes::updateValidationStatusInCache(const DNSName& qname, const QType qt
   }
 }
 
-static bool scanForCNAMELoop(const DNSName& name, const vector<DNSRecord>& records)
+static pair<bool, unsigned int> scanForCNAMELoop(const DNSName& name, const vector<DNSRecord>& records)
 {
+  unsigned int numCNames = 0;
   for (const auto& record : records) {
     if (record.d_type == QType::CNAME && record.d_place == DNSResourceRecord::ANSWER) {
+      ++numCNames;
       if (name == record.d_name) {
-        return true;
+        return {true, numCNames};
       }
     }
   }
-  return false;
+  return {false, numCNames};
 }
 
-bool SyncRes::doCNAMECacheCheck(const DNSName& qname, const QType qtype, vector<DNSRecord>& ret, unsigned int depth, const string& prefix, int& res, Context& context, bool wasAuthZone, bool wasForwardRecurse)
+bool SyncRes::doCNAMECacheCheck(const DNSName& qname, const QType qtype, vector<DNSRecord>& ret, unsigned int depth, const string& prefix, int& res, Context& context, bool wasAuthZone, bool wasForwardRecurse) // NOLINT(readability-function-cognitive-complexity)
 {
-  // Even if s_maxdepth is zero, we want to have this check
-  auto bound = std::max(40U, getAdjustedRecursionBound());
-  // Bounds were > 9 and > 15 originally, now they are derived from s_maxdepth (default 40)
-  // Apply more strict bound if we see throttling
-  if ((depth >= bound / 4 && d_outqueries > 10 && d_throttledqueries > 5) || depth > bound * 3 / 8) {
-    LOG(prefix << qname << ": Recursing (CNAME or other indirection) too deep, depth=" << depth << endl);
-    res = RCode::ServFail;
-    return true;
-  }
-
   vector<DNSRecord> cset;
   vector<std::shared_ptr<const RRSIGRecordContent>> signatures;
   vector<std::shared_ptr<DNSRecord>> authorityRecs;
@@ -2599,12 +2593,18 @@ bool SyncRes::doCNAMECacheCheck(const DNSName& qname, const QType qtype, vector<
         return true;
       }
 
-      // Check to see if we already have seen the new target as a previous target
-      if (scanForCNAMELoop(newTarget, ret)) {
+      // Check to see if we already have seen the new target as a previous target or that we have a very long CNAME chain
+      const auto [CNAMELoop, numCNAMEs] = scanForCNAMELoop(newTarget, ret);
+      if (CNAMELoop) {
         string msg = "got a CNAME referral (from cache) that causes a loop";
         LOG(prefix << qname << ": Status=" << msg << endl);
         throw ImmediateServFailException(msg);
       }
+      if (numCNAMEs > s_max_CNAMES_followed) {
+        string msg = "max number of CNAMEs exceeded";
+        LOG(prefix << qname << ": Status=" << msg << endl);
+        throw ImmediateServFailException(msg);
+      }
 
       set<GetBestNSAnswer> beenthere;
       Context cnameContext;
@@ -5327,26 +5327,24 @@ void SyncRes::handleNewTarget(const std::string& prefix, const DNSName& qname, c
     setQNameMinimization(false);
   }
 
-  // Was 10 originally, default s_maxdepth is 40, but even if it is zero we want to apply a bound
-  auto bound = std::max(40U, getAdjustedRecursionBound()) / 4;
-  if (depth > bound) {
-    LOG(prefix << qname << ": Status=got a CNAME referral, but recursing too deep, returning SERVFAIL" << endl);
-    rcode = RCode::ServFail;
-    return;
-  }
-
   if (!d_followCNAME) {
     rcode = RCode::NoError;
     return;
   }
 
-  // Check to see if we already have seen the new target as a previous target
-  if (scanForCNAMELoop(newtarget, ret)) {
+  // Check to see if we already have seen the new target as a previous target or that the chain is too long
+  const auto [CNAMELoop, numCNAMEs] = scanForCNAMELoop(newtarget, ret);
+  if (CNAMELoop) {
     LOG(prefix << qname << ": Status=got a CNAME referral that causes a loop, returning SERVFAIL" << endl);
     ret.clear();
     rcode = RCode::ServFail;
     return;
   }
+  if (numCNAMEs > s_max_CNAMES_followed) {
+    LOG(prefix << qname << ": Status=got a CNAME referral, but chain too long, returning SERVFAIL" << endl);
+    rcode = RCode::ServFail;
+    return;
+  }
 
   if (qtype == QType::DS || qtype == QType::DNSKEY) {
     LOG(prefix << qname << ": Status=got a CNAME referral, but we are looking for a DS or DNSKEY" << endl);
index b271acfbfaf63a7cf8bcb02097ee6cf4c056d241..445482b95265e0a196860030b2462345a8b39d2d 100644 (file)
@@ -545,6 +545,7 @@ public:
   static bool s_tcp_fast_open_connect;
   static bool s_dot_to_port_853;
   static unsigned int s_max_busy_dot_probes;
+  static unsigned int s_max_CNAMES_followed;
 
   static const int event_trace_to_pb = 1;
   static const int event_trace_to_log = 2;
index 9d1c19d420c8416c4a02c363935b144a0df6ae9a..3d642a17240c7cabb203e63de7f8611962dd3b74 100644 (file)
@@ -1002,6 +1002,56 @@ BOOST_AUTO_TEST_CASE(test_glueless_referral)
   BOOST_CHECK_EQUAL(ret[0].d_name, target);
 }
 
+BOOST_AUTO_TEST_CASE(test_endless_glueless_referral)
+{
+  std::unique_ptr<SyncRes> sr;
+  initSR(sr);
+
+  primeHints();
+
+  const DNSName target("powerdns.com.");
+
+  size_t count = 0;
+  sr->setAsyncCallback([target, &count](const ComboAddress& ip, const DNSName& domain, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const boost::optional<const ResolveContext&>& /* context */, LWResult* res, bool* /* chained */) {
+    if (isRootServer(ip)) {
+      setLWResult(res, 0, false, false, true);
+
+      if (domain.isPartOf(DNSName("com."))) {
+        addRecordToLW(res, "com.", QType::NS, "a.gtld-servers.net.", DNSResourceRecord::AUTHORITY, 172800);
+      }
+      else if (domain.isPartOf(DNSName("org."))) {
+        addRecordToLW(res, "org.", QType::NS, "a.gtld-servers.net.", DNSResourceRecord::AUTHORITY, 172800);
+      }
+      else {
+        setLWResult(res, RCode::NXDomain, false, false, true);
+        return LWResult::Result::Success;
+      }
+
+      addRecordToLW(res, "a.gtld-servers.net.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600);
+      addRecordToLW(res, "a.gtld-servers.net.", QType::AAAA, "2001:DB8::1", DNSResourceRecord::ADDITIONAL, 3600);
+      return LWResult::Result::Success;
+    }
+    if (domain == target) {
+      setLWResult(res, 0, false, false, true);
+      addRecordToLW(res, "powerdns.com.", QType::NS, "ns1.powerdns.org.", DNSResourceRecord::AUTHORITY, 172800);
+      addRecordToLW(res, "powerdns.com.", QType::NS, "ns2.powerdns.org.", DNSResourceRecord::AUTHORITY, 172800);
+      return LWResult::Result::Success;
+    }
+    setLWResult(res, 0, false, false, true);
+    addRecordToLW(res, domain, QType::NS, std::to_string(count) + ".ns1.powerdns.org", DNSResourceRecord::AUTHORITY, 172800);
+    addRecordToLW(res, domain, QType::NS, std::to_string(count) + ".ns2.powerdns.org", DNSResourceRecord::AUTHORITY, 172800);
+    count++;
+    return LWResult::Result::Success;
+  });
+
+  vector<DNSRecord> ret;
+  BOOST_CHECK_EXCEPTION(sr->beginResolve(target, QType(QType::A), QClass::IN, ret),
+                        ImmediateServFailException,
+                        [](const ImmediateServFailException& isfe) {
+                          return isfe.reason.substr(0, 9) == "More than";
+                        });
+}
+
 BOOST_AUTO_TEST_CASE(test_glueless_referral_aaaa_task)
 {
   std::unique_ptr<SyncRes> sr;
@@ -1592,7 +1642,7 @@ BOOST_AUTO_TEST_CASE(test_cname_long_loop)
   const DNSName target3("cname3.powerdns.com.");
   const DNSName target4("cname4.powerdns.com.");
 
-  sr->setAsyncCallback([target1, target2, target3, target4, &count](const ComboAddress& ip, const DNSName& domain, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, boost::optional<const ResolveContext&> /* context */, LWResult* res, bool* /* chained */) {
+  sr->setAsyncCallback([target1, target2, target3, target4, &count](const ComboAddress& ip, const DNSName& domain, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const boost::optional<const ResolveContext&>& /* context */, LWResult* res, bool* /* chained */) {
     count++;
 
     if (isRootServer(ip)) {
@@ -1647,17 +1697,17 @@ BOOST_AUTO_TEST_CASE(test_cname_long_loop)
   }
 }
 
-BOOST_AUTO_TEST_CASE(test_cname_depth)
+BOOST_AUTO_TEST_CASE(test_cname_length)
 {
   std::unique_ptr<SyncRes> sr;
   initSR(sr);
 
   primeHints();
 
-  size_t depth = 0;
+  size_t length = 0;
   const DNSName target("cname.powerdns.com.");
 
-  sr->setAsyncCallback([target, &depth](const ComboAddress& ip, const DNSName& domain, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, boost::optional<const ResolveContext&> /* context */, LWResult* res, bool* /* chained */) {
+  sr->setAsyncCallback([target, &length](const ComboAddress& ip, const DNSName& domain, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const boost::optional<const ResolveContext&>& /* context */, LWResult* res, bool* /* chained */) {
     if (isRootServer(ip)) {
 
       setLWResult(res, 0, false, false, true);
@@ -1668,8 +1718,8 @@ BOOST_AUTO_TEST_CASE(test_cname_depth)
     else if (ip == ComboAddress("192.0.2.1:53")) {
 
       setLWResult(res, 0, true, false, false);
-      addRecordToLW(res, domain, QType::CNAME, std::to_string(depth) + "-cname.powerdns.com");
-      depth++;
+      addRecordToLW(res, domain, QType::CNAME, std::to_string(length) + "-cname.powerdns.com");
+      length++;
       return LWResult::Result::Success;
     }
 
@@ -1679,9 +1729,8 @@ BOOST_AUTO_TEST_CASE(test_cname_depth)
   vector<DNSRecord> ret;
   int res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
   BOOST_CHECK_EQUAL(res, RCode::ServFail);
-  BOOST_CHECK_EQUAL(ret.size(), depth);
-  /* we have an arbitrary limit at 10 when following a CNAME chain */
-  BOOST_CHECK_EQUAL(depth, 10U + 2U);
+  BOOST_CHECK_EQUAL(ret.size(), length);
+  BOOST_CHECK_EQUAL(length, SyncRes::s_max_CNAMES_followed + 1);
 }
 
 BOOST_AUTO_TEST_CASE(test_time_limit)
index 1d65c48436ec0d9cbbc8ca1165236ad8d835621c..287fcc290be85a34bfb995711989a2a2df665d9c 100755 (executable)
@@ -3,7 +3,7 @@
 
 [realms]
         EXAMPLE.COM = {
-                kdc = 127.0.0.1:1188
-                admin_server = 127.0.0.1:1749
+                kdc = kerberos-server:1188
+                admin_server = kerberos-server:1749
         }
 
index dfc6ead966b6e629a105f4518f3d4187dae7f09b..ce3e3ccf819d9eeda2280a2e3c5ddd84911954f6 100644 (file)
@@ -764,7 +764,7 @@ class DNSDistTest(AssertEqualDNSMessageMixin, unittest.TestCase):
         return result.decode('UTF-8')
 
     @classmethod
-    def sendConsoleCommand(cls, command, timeout=1.0):
+    def sendConsoleCommand(cls, command, timeout=5.0):
         ourNonce = libnacl.utils.rand_nonce()
         theirNonce = None
         sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
index 1a110e483dad7ba7162a7bf2f0a3a4a149ebbf9c..5c3efbb5016d46060440a261c73de04f10d5f3e7 100644 (file)
@@ -394,6 +394,9 @@ class TestAdvancedGetLocalAddressOnAnyBind(DNSDistTest):
         self.assertEqual(receivedQuery, query)
         self.assertEqual(receivedResponse, response)
 
+        if 'SKIP_IPV6_TESTS' in os.environ:
+          return
+
         # a bit more tricky, UDP-only IPv6
         sock = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM)
         sock.settimeout(1.0)
index a88d4a46e46a30617c6778b64bbf4a5316be6742..8647f5d9c35be509b382e87de109c88ff3660b92 100755 (executable)
@@ -4,11 +4,17 @@ if [ "${PDNS_DEBUG}" = "YES" ]; then
   set -x
 fi
 
+local_address="127.0.0.1,::1"
+
+if [ -n "${SKIP_IPV6_TESTS}" ]; then
+  local_address="127.0.0.1"
+fi
+
 port=5600
 
 rm -f pdns*.pid
 
-$PDNS --daemon=no --local-address=127.0.0.1,::1 \
+$PDNS --daemon=no --local-address=$local_address \
   --local-port=$port --socket-dir=./ --no-shuffle --launch=bind --no-config \
   --module-dir=../regression-tests/modules --bind-config=counters/named.conf &
 
@@ -20,11 +26,13 @@ $SDIG 127.0.0.1 $port test.com SOA >&2 >/dev/null
 $SDIG 127.0.0.1 $port server1.test.com A tcp >&2 >/dev/null
 $SDIG 127.0.0.1 $port test.com SOA tcp >&2 >/dev/null
 
-$SDIG ::1 $port server1.test.com A >&2 >/dev/null
-$SDIG ::1 $port server1.com A tcp >&2 >/dev/null
+if [ -z "${SKIP_IPV6_TESTS}" ]; then
+  $SDIG ::1 $port server1.test.com A >&2 >/dev/null
+  $SDIG ::1 $port server1.com A tcp >&2 >/dev/null
 
-$SDIG ::1 $port test.com SOA >&2 >/dev/null
-$SDIG ::1 $port test.com SOA tcp >&2 >/dev/null
+  $SDIG ::1 $port test.com SOA >&2 >/dev/null
+  $SDIG ::1 $port test.com SOA tcp >&2 >/dev/null
+fi
 
 # NXDOMAIN
 $SDIG 127.0.0.1 $port nx.test.com A >&2 >/dev/null
diff --git a/regression-tests.nobackend/counters/expected_result.noipv6 b/regression-tests.nobackend/counters/expected_result.noipv6
new file mode 100644 (file)
index 0000000..650b2c9
--- /dev/null
@@ -0,0 +1,71 @@
+
+corrupt-packets=0
+deferred-cache-inserts=0
+deferred-cache-lookup=0
+deferred-packetcache-inserts=0
+deferred-packetcache-lookup=0
+dnsupdate-answers=0
+dnsupdate-changes=0
+dnsupdate-queries=0
+dnsupdate-refused=0
+incoming-notifications=0
+key-cache-size=0
+meta-cache-size=1
+noerror-packets=1
+nxdomain-packets=1
+open-tcp-connections=0
+overload-drops=0
+packetcache-size=7
+qsize-q=0
+query-cache-size=4
+rd-queries=0
+recursing-answers=0
+recursing-questions=0
+recursion-unanswered=0
+ring-logmessages-capacity=10000
+ring-logmessages-size=0
+ring-noerror-queries-capacity=10000
+ring-noerror-queries-size=0
+ring-nxdomain-queries-capacity=10000
+ring-nxdomain-queries-size=0
+ring-queries-capacity=10000
+ring-queries-size=0
+ring-remotes-capacity=10000
+ring-remotes-corrupt-capacity=10000
+ring-remotes-corrupt-size=0
+ring-remotes-size=0
+ring-remotes-unauth-capacity=10000
+ring-remotes-unauth-size=0
+ring-servfail-queries-capacity=10000
+ring-servfail-queries-size=0
+ring-unauth-queries-capacity=10000
+ring-unauth-queries-size=0
+security-status=0
+servfail-packets=0
+signature-cache-size=0
+signatures=0
+tcp-answers-bytes=128
+tcp-answers=2
+tcp-cookie-queries=0
+tcp-queries=2
+tcp4-answers-bytes=128
+tcp4-answers=2
+tcp4-queries=2
+tcp6-answers-bytes=0
+tcp6-answers=0
+tcp6-queries=0
+timedout-packets=0
+udp-answers-bytes=321
+udp-answers=5
+udp-cookie-queries=0
+udp-do-queries=0
+udp-queries=5
+udp4-answers-bytes=321
+udp4-answers=5
+udp4-queries=5
+udp6-answers-bytes=0
+udp6-answers=0
+udp6-queries=0
+unauth-packets=1
+xfr-queue=0
+zone-cache-size=1
index c96f3770b68f106f9cbd4f7717b6abe217870900..ec37c64da86e8af575b79f03da6f068800879684 100755 (executable)
@@ -5,11 +5,17 @@ if [ "${PDNS_DEBUG}" = "YES" ]; then
   set -x
 fi
 
+local_address="127.0.0.1,::1"
+
+if [ -n "${SKIP_IPV6_TESTS}" ]; then
+  local_address="127.0.0.1"
+fi
+
 port=5600
 
 rm -f pdns*.pid
 
-$PDNS --daemon=no --local-address=127.0.0.1,::1 \
+$PDNS --daemon=no --local-address=$local_address \
   --local-port=$port --socket-dir=./ --no-shuffle --launch=pipe --no-config \
   --module-dir=../regression-tests/modules --pipe-command=$(pwd)/distributor/slow.pl \
   --pipe-abi-version=5 \
index 1026001d9ecc35a4e9ff832f6707808dd760ee2e..fcc915ab53d1abcb8dcc6bfd723ecc325075b787 100644 (file)
@@ -30,9 +30,9 @@ devonly-regression-test-mode
         cls.generateRecursorConfig(confdir)
         cls.startRecursor(confdir, cls._recursorPort)
 
-    def testA(self):
-        expected = dns.rrset.from_text('www.powerdns.com.', 0, dns.rdataclass.IN, 'A', '188.166.104.92')
-        query = dns.message.make_query('www.powerdns.com', 'A', want_dnssec=True)
+    def testTXT(self):
+        expected = dns.rrset.from_text('dot-test-target.powerdns.org.', 0, dns.rdataclass.IN, 'TXT', 'https://github.com/PowerDNS/pdns/pull/12825')
+        query = dns.message.make_query('dot-test-target.powerdns.org', 'TXT', want_dnssec=True)
         query.flags |= dns.flags.AD
 
         res = self.sendUDPQuery(query)
index 96f22089744b98a0da1fff383cbde3bd333e729d..426a428f3b2eefb019651754ca941c2c5472c088 100644 (file)
--- a/tasks.py
+++ b/tasks.py
@@ -5,6 +5,10 @@ import os
 import sys
 import time
 
+auth_backend_ip_addr = os.getenv('AUTH_BACKEND_IP_ADDR', '127.0.0.1')
+
+clang_version = os.getenv('CLANG_VERSION', '13')
+
 all_build_deps = [
     'ccache',
     'libboost-all-dev',
@@ -59,7 +63,7 @@ rec_bulk_deps = [
     'libcap2',
     'libfstrm0',
     'libluajit-5.1-2',
-    'libsnmp35',
+    '"libsnmp[1-9]+"',
     'libsodium23',
     'libssl1.1',
     'libsystemd0',
@@ -91,7 +95,7 @@ auth_test_deps = [   # FIXME: we should be generating some of these from shlibde
     'gawk',
     'krb5-user',
     'ldnsutils',
-    'libboost-serialization1.71.0',
+    '"libboost-serialization1.7[1-9]+"',
     'libcdb1',
     'libcurl4',
     'libgeoip1',
@@ -148,29 +152,29 @@ doc_deps_pdf = [
 def apt_fresh(c):
     c.sudo('sed -i \'s/azure\.//\' /etc/apt/sources.list')
     c.sudo('apt-get update')
-    c.sudo('apt-get -qq -y --allow-downgrades dist-upgrade')
+    c.sudo('apt-get -y --allow-downgrades dist-upgrade')
 
 @task
 def install_clang(c):
     """
-    install clang-12 and llvm-12
+    install clang and llvm
     """
-    c.sudo('apt-get -qq -y --no-install-recommends install clang-12 llvm-12')
+    c.sudo(f'apt-get -y --no-install-recommends install clang-{clang_version} llvm-{clang_version}')
 
 @task
 def install_clang_tidy_tools(c):
-    c.sudo('apt-get -qq -y --no-install-recommends install clang-tidy-12 clang-tools-12 bear python-yaml')
+    c.sudo(f'apt-get -y --no-install-recommends install clang-tidy-{clang_version} clang-tools-{clang_version} bear python3-yaml')
 
 @task
 def install_clang_runtime(c):
     # this gives us the symbolizer, for symbols in asan/ubsan traces
-    c.sudo('apt-get -qq -y --no-install-recommends install clang-12')
+    c.sudo(f'apt-get -y --no-install-recommends install clang-{clang_version}')
 
 def install_libdecaf(c, product):
     c.run('git clone https://git.code.sf.net/p/ed448goldilocks/code /tmp/libdecaf')
     with c.cd('/tmp/libdecaf'):
         c.run('git checkout 41f349')
-        c.run('CC=clang-12 CXX=clang-12 '
+        c.run(f'CC=clang-{clang_version} CXX=clang-{clang_version} '
               'cmake -B build '
               '-DCMAKE_INSTALL_PREFIX=/usr/local '
               '-DCMAKE_INSTALL_LIBDIR=lib '
@@ -184,15 +188,15 @@ def install_libdecaf(c, product):
 
 @task
 def install_doc_deps(c):
-    c.sudo('apt-get install -qq -y ' + ' '.join(doc_deps))
+    c.sudo('apt-get install -y ' + ' '.join(doc_deps))
 
 @task
 def install_doc_deps_pdf(c):
-    c.sudo('apt-get install -qq -y ' + ' '.join(doc_deps_pdf))
+    c.sudo('apt-get install -y ' + ' '.join(doc_deps_pdf))
 
 @task
 def install_auth_build_deps(c):
-    c.sudo('apt-get install -qq -y --no-install-recommends ' + ' '.join(all_build_deps + git_build_deps + auth_build_deps))
+    c.sudo('apt-get install -y --no-install-recommends ' + ' '.join(all_build_deps + git_build_deps + auth_build_deps))
     install_libdecaf(c, 'pdns-auth')
 
 def setup_authbind(c):
@@ -221,7 +225,7 @@ def install_auth_test_deps(c, backend): # FIXME: rename this, we do way more tha
     extra=[]
     for b in backend:
         extra.extend(auth_backend_test_deps[b])
-    c.sudo('apt-get -y -qq install ' + ' '.join(extra+auth_test_deps))
+    c.sudo('DEBIAN_FRONTEND=noninteractive apt-get -y install ' + ' '.join(extra+auth_test_deps))
 
     c.run('chmod +x /opt/pdns-auth/bin/* /opt/pdns-auth/sbin/*')
     # c.run('''if [ ! -e $HOME/bin/jdnssec-verifyzone ]; then
@@ -240,12 +244,12 @@ def install_auth_test_deps(c, backend): # FIXME: rename this, we do way more tha
 
 @task
 def install_rec_bulk_deps(c): # FIXME: rename this, we do way more than apt-get
-    c.sudo('apt-get --no-install-recommends -qq -y install ' + ' '.join(rec_bulk_deps))
+    c.sudo('apt-get --no-install-recommends -y install ' + ' '.join(rec_bulk_deps))
     c.run('chmod +x /opt/pdns-recursor/bin/* /opt/pdns-recursor/sbin/*')
 
 @task
 def install_rec_test_deps(c): # FIXME: rename this, we do way more than apt-get
-    c.sudo('apt-get --no-install-recommends install -qq -y ' + ' '.join(rec_bulk_deps) + ' \
+    c.sudo('apt-get --no-install-recommends install -y ' + ' '.join(rec_bulk_deps) + ' \
               pdns-server pdns-backend-bind daemontools \
               jq libfaketime lua-posix lua-socket bc authbind \
               python3-venv python3-dev default-libmysqlclient-dev libpq-dev \
@@ -256,13 +260,13 @@ def install_rec_test_deps(c): # FIXME: rename this, we do way more than apt-get
     setup_authbind(c)
 
     c.run('sed "s/agentxperms 0700 0755 recursor/agentxperms 0777 0755/g" regression-tests.recursor-dnssec/snmpd.conf | sudo tee /etc/snmp/snmpd.conf')
-    c.sudo('systemctl restart snmpd')
+    c.sudo('/etc/init.d/snmpd restart')
     time.sleep(5)
     c.sudo('chmod 755 /var/agentx')
 
 @task
 def install_dnsdist_test_deps(c): # FIXME: rename this, we do way more than apt-get
-    c.sudo('apt-get install -qq -y \
+    c.sudo('apt-get install -y \
               libluajit-5.1-2 \
               libboost-all-dev \
               libcap2 \
@@ -273,7 +277,7 @@ def install_dnsdist_test_deps(c): # FIXME: rename this, we do way more than apt-
               libh2o-evloop0.13 \
               liblmdb0 \
               libnghttp2-14 \
-              libre2-5 \
+              "libre2-[1-9]+" \
               libssl-dev \
               libsystemd0 \
               libsodium23 \
@@ -282,17 +286,17 @@ def install_dnsdist_test_deps(c): # FIXME: rename this, we do way more than apt-
               protobuf-compiler \
               python3-venv snmpd prometheus')
     c.run('sed "s/agentxperms 0700 0755 dnsdist/agentxperms 0777 0755/g" regression-tests.dnsdist/snmpd.conf | sudo tee /etc/snmp/snmpd.conf')
-    c.sudo('systemctl restart snmpd')
+    c.sudo('/etc/init.d/snmpd restart')
     time.sleep(5)
     c.sudo('chmod 755 /var/agentx')
 
 @task
 def install_rec_build_deps(c):
-    c.sudo('apt-get install -qq -y --no-install-recommends ' +  ' '.join(all_build_deps + git_build_deps + rec_build_deps))
+    c.sudo('apt-get install -y --no-install-recommends ' +  ' '.join(all_build_deps + git_build_deps + rec_build_deps))
 
 @task
 def install_dnsdist_build_deps(c):
-    c.sudo('apt-get install -qq -y --no-install-recommends ' +  ' '.join(all_build_deps + git_build_deps + dnsdist_build_deps))
+    c.sudo('apt-get install -y --no-install-recommends ' +  ' '.join(all_build_deps + git_build_deps + dnsdist_build_deps))
 
 @task
 def ci_autoconf(c):
@@ -361,8 +365,8 @@ def get_base_configure_cmd():
         f'CFLAGS="{get_cflags()}"',
         f'CXXFLAGS="{get_cxxflags()}"',
         './configure',
-        "CC='clang-12'",
-        "CXX='clang++-12'",
+        f"CC='clang-{clang_version}'",
+        f"CXX='clang++-{clang_version}'",
         "--enable-option-checking=fatal",
         "--enable-systemd",
         "--with-libsodium",
@@ -509,13 +513,13 @@ def ci_dnsdist_configure(c, features):
     sanitizers = ' '.join('--enable-'+x for x in os.getenv('SANITIZERS').split('+')) if os.getenv('SANITIZERS') != '' else ''
     cflags = '-O1 -Werror=vla -Werror=shadow -Wformat=2 -Werror=format-security -Werror=string-plus-int'
     cxxflags = cflags + ' -Wp,-D_GLIBCXX_ASSERTIONS ' + additional_flags
-    res = c.run('''CFLAGS="%s" \
+    res = c.run(f'''CFLAGS="%s" \
                    CXXFLAGS="%s" \
-                   AR=llvm-ar-12 \
-                   RANLIB=llvm-ranlib-12 \
+                   AR=llvm-ar-{clang_version} \
+                   RANLIB=llvm-ranlib-{clang_version} \
                    ./configure \
-                     CC='clang-12' \
-                     CXX='clang++-12' \
+                     CC='clang-{clang_version}' \
+                     CXX='clang++-{clang_version}' \
                      --enable-option-checking=fatal \
                      --enable-fortify-source=auto \
                      --enable-auto-var-init=pattern \
@@ -533,7 +537,7 @@ def ci_auth_make(c):
 def ci_auth_make_bear(c):
     # Needed for clang-tidy -line-filter vs project structure shenanigans
     with c.cd('pdns'):
-        c.run('bear --append make -j8 -k V=1 -C ..')
+        c.run('bear --append -- make -j8 -k V=1 -C ..')
 
 @task
 def ci_rec_make(c):
@@ -542,7 +546,7 @@ def ci_rec_make(c):
 @task
 def ci_rec_make_bear(c):
     # Assumed to be running under ./pdns/recursordist/
-    c.run('bear --append make -j8 -k V=1')
+    c.run('bear --append -- make -j8 -k V=1')
 
 @task
 def ci_dnsdist_make(c):
@@ -551,14 +555,14 @@ def ci_dnsdist_make(c):
 @task
 def ci_dnsdist_make_bear(c):
     # Assumed to be running under ./pdns/dnsdistdist/
-    c.run('bear --append make -j4 -k V=1')
+    c.run('bear --append -- make -j4 -k V=1')
 
 @task
 def ci_auth_install_remotebackend_test_deps(c):
     with c.cd('modules/remotebackend'):
       # c.run('bundle config set path vendor/bundle')
       c.run('sudo ruby -S bundle install')
-    c.sudo('apt-get install -qq -y socat')
+    c.sudo('apt-get install -y socat')
 
 @task
 def ci_auth_run_unit_tests(c):
@@ -587,17 +591,13 @@ def ci_make_install(c):
     res = c.run('make install') # FIXME: this builds auth docs - again
 
 @task
-def add_auth_repo(c):
-    dist = 'ubuntu' # FIXME take these from the caller?
-    release = 'focal'
-    version = '44'
-
-    c.sudo('apt-get install -qq -y curl gnupg2')
-    if version == 'master':
+def add_auth_repo(c, dist_name, dist_release_name, pdns_repo_version):
+    c.sudo('apt-get install -y curl gnupg2')
+    if pdns_repo_version == 'master':
         c.sudo('curl -s -o /etc/apt/trusted.gpg.d/pdns-repo.asc https://repo.powerdns.com/CBC8B383-pub.asc')
     else:
         c.sudo('curl -s -o /etc/apt/trusted.gpg.d/pdns-repo.asc https://repo.powerdns.com/FD380FBB-pub.asc')
-    c.run(f"echo 'deb [arch=amd64] http://repo.powerdns.com/{dist} {release}-auth-{version} main' | sudo tee /etc/apt/sources.list.d/pdns.list")
+    c.run(f"echo 'deb [arch=amd64] http://repo.powerdns.com/{dist_name} {dist_release_name}-auth-{pdns_repo_version} main' | sudo tee /etc/apt/sources.list.d/pdns.list")
     c.run("echo 'Package: pdns-*' | sudo tee /etc/apt/preferences.d/pdns")
     c.run("echo 'Pin: origin repo.powerdns.com' | sudo tee -a /etc/apt/preferences.d/pdns")
     c.run("echo 'Pin-Priority: 600' | sudo tee -a /etc/apt/preferences.d/pdns")
@@ -610,7 +610,7 @@ def test_api(c, product, backend=''):
             c.run(f'PDNSRECURSOR=/opt/pdns-recursor/sbin/pdns_recursor ./runtests recursor {backend}')
     elif product == 'auth':
         with c.cd('regression-tests.api'):
-            c.run(f'PDNSSERVER=/opt/pdns-auth/sbin/pdns_server PDNSUTIL=/opt/pdns-auth/bin/pdnsutil SDIG=/opt/pdns-auth/bin/sdig MYSQL_HOST="127.0.0.1" PGHOST="127.0.0.1" PGPORT="5432" ./runtests authoritative {backend}')
+            c.run(f'PDNSSERVER=/opt/pdns-auth/sbin/pdns_server PDNSUTIL=/opt/pdns-auth/bin/pdnsutil SDIG=/opt/pdns-auth/bin/sdig MYSQL_HOST={auth_backend_ip_addr} PGHOST={auth_backend_ip_addr} PGPORT=5432 ./runtests authoritative {backend}')
     else:
         raise Failure('unknown product')
 
@@ -687,11 +687,11 @@ backend_regress_tests = dict(
 
 godbc_mssql_credentials = {"username": "sa", "password": "SAsa12%%"}
 
-godbc_config = '''
+godbc_config = f'''
 [pdns-mssql-docker]
 Driver=FreeTDS
 Trace=No
-Server=127.0.0.1
+Server={auth_backend_ip_addr}
 Port=1433
 Database=pdns
 TDS_Version=7.1
@@ -699,7 +699,7 @@ TDS_Version=7.1
 [pdns-mssql-docker-nodb]
 Driver=FreeTDS
 Trace=No
-Server=127.0.0.1
+Server={auth_backend_ip_addr}
 Port=1433
 TDS_Version=7.1
 
@@ -727,17 +727,18 @@ def setup_godbc_sqlite3(c):
     c.sudo('sed -i "s/libsqlite3odbc.so/\/usr\/lib\/x86_64-linux-gnu\/odbc\/libsqlite3odbc.so/g" /etc/odbcinst.ini')
 
 def setup_ldap_client(c):
-    c.sudo('DEBIAN_FRONTEND=noninteractive apt-get install -qq -y ldap-utils')
-    c.sudo('sh -c \'echo "127.0.0.1 ldapserver" | tee -a /etc/hosts\'')
+    c.sudo('DEBIAN_FRONTEND=noninteractive apt-get install -y ldap-utils')
+    c.sudo(f'sh -c \'echo "{auth_backend_ip_addr} ldapserver" | tee -a /etc/hosts\'')
 
 @task
 def test_auth_backend(c, backend):
-    pdns_auth_env_vars = 'PDNS=/opt/pdns-auth/sbin/pdns_server PDNS2=/opt/pdns-auth/sbin/pdns_server SDIG=/opt/pdns-auth/bin/sdig NOTIFY=/opt/pdns-auth/bin/pdns_notify NSEC3DIG=/opt/pdns-auth/bin/nsec3dig SAXFR=/opt/pdns-auth/bin/saxfr ZONE2SQL=/opt/pdns-auth/bin/zone2sql ZONE2LDAP=/opt/pdns-auth/bin/zone2ldap ZONE2JSON=/opt/pdns-auth/bin/zone2json PDNSUTIL=/opt/pdns-auth/bin/pdnsutil PDNSCONTROL=/opt/pdns-auth/bin/pdns_control PDNSSERVER=/opt/pdns-auth/sbin/pdns_server SDIG=/opt/pdns-auth/bin/sdig GMYSQLHOST=127.0.0.1 GMYSQL2HOST=127.0.0.1 MYSQL_HOST="127.0.0.1" PGHOST="127.0.0.1" PGPORT="5432"'
+    pdns_auth_env_vars = f'PDNS=/opt/pdns-auth/sbin/pdns_server PDNS2=/opt/pdns-auth/sbin/pdns_server SDIG=/opt/pdns-auth/bin/sdig NOTIFY=/opt/pdns-auth/bin/pdns_notify NSEC3DIG=/opt/pdns-auth/bin/nsec3dig SAXFR=/opt/pdns-auth/bin/saxfr ZONE2SQL=/opt/pdns-auth/bin/zone2sql ZONE2LDAP=/opt/pdns-auth/bin/zone2ldap ZONE2JSON=/opt/pdns-auth/bin/zone2json PDNSUTIL=/opt/pdns-auth/bin/pdnsutil PDNSCONTROL=/opt/pdns-auth/bin/pdns_control PDNSSERVER=/opt/pdns-auth/sbin/pdns_server SDIG=/opt/pdns-auth/bin/sdig GMYSQLHOST={auth_backend_ip_addr} GMYSQL2HOST={auth_backend_ip_addr} MYSQL_HOST={auth_backend_ip_addr} PGHOST={auth_backend_ip_addr} PGPORT=5432'
 
     if backend == 'remote':
         ci_auth_install_remotebackend_test_deps(c)
 
     if backend == 'authpy':
+        c.sudo(f'sh -c \'echo "{auth_backend_ip_addr} kerberos-server" | tee -a /etc/hosts\'')
         with c.cd('regression-tests.auth-py'):
             c.run(f'{pdns_auth_env_vars} WITHKERBEROS=YES ./runtests')
         return
@@ -772,7 +773,10 @@ def test_auth_backend(c, backend):
             c.run(f'{pdns_auth_env_vars} ./start-test-stop 5300 {variant}')
 
     if backend == 'gsqlite3':
+        if os.getenv('SKIP_IPV6_TESTS'):
+            pdns_auth_env_vars += ' context=noipv6'
         with c.cd('regression-tests.nobackend'):
+            c.run(f'echo {pdns_auth_env_vars}')
             c.run(f'{pdns_auth_env_vars} ./runtests')
         c.run('/opt/pdns-auth/bin/pdnsutil test-algorithms')
         return
@@ -793,7 +797,7 @@ def test_dnsdist(c):
 @task
 def test_regression_recursor(c):
     c.run('/opt/pdns-recursor/sbin/pdns_recursor --version')
-    c.run('PDNSRECURSOR=/opt/pdns-recursor/sbin/pdns_recursor RECCONTROL=/opt/pdns-recursor/bin/rec_control SKIP_IPV6_TESTS=y ./build-scripts/test-recursor')
+    c.run('PDNSRECURSOR=/opt/pdns-recursor/sbin/pdns_recursor RECCONTROL=/opt/pdns-recursor/bin/rec_control ./build-scripts/test-recursor')
 
 @task
 def test_bulk_recursor(c, threads, mthreads, shards):
@@ -819,7 +823,7 @@ def install_coverity_tools(c, project):
 
 @task
 def coverity_clang_configure(c):
-    c.sudo('/usr/local/bin/cov-configure --template --comptype clangcc --compiler clang++-12')
+    c.sudo(f'/usr/local/bin/cov-configure --template --comptype clangcc --compiler clang++-{clang_version}')
 
 @task
 def coverity_make(c):